Showing posts with label Peter Lafferty. Show all posts
Showing posts with label Peter Lafferty. Show all posts

Thursday, 9 March 2017

PHPUnit: setMethods and how I've been writing too much code

G'day:
Seems our technical architect is becoming my PHP muse. Good on ya, Pete.

Anyway, he just sent out what I thought was a controversial email in which he said (paraphrase) "we're calling setMethods way too often: in general we don't need to call it". To contextualise, I mean this:

$dependency = $this
    ->getMockBuilder(MyDependency::class)
    ->setMethods(['someMethod'])
    ->getMock();

$dependency
    ->method('someMethod')
    ->willReturn('MOCKED RESULT');


See here I'm explicitly saying "mock out the someMethod method". Pete's contention is that that's generally unnecessary, cos getMockBuilder mocks 'em all by default anyhow. This goes against my understanding of how it works, and I've written bloody thousands of unit tests with PHPUnit so it surprised me I was getting it wrong. I kinda believed him cos he said he tested it, but I didn't believe him so much as to not go test it myself.

So I knocked out a quick service and dependency for it:

namespace me\adamcameron\myApp;

class MyService
{

    private $myDecorator;

    public function __construct(MyDecorator $myDecorator)
    {
        $this->myDecorator = $myDecorator;
    }

    public function decorateMessage($message)
    {
        $message = $this->myDecorator->addPrefix($message);
        $message = $this->myDecorator->addSuffix($message);
        return $message;
    }

}

namespace me\adamcameron\myApp;

class MyDecorator
{

    public function addPrefix($message)
    {
        return "(ACTUAL PREFIX) $message";
    }

    public function addSuffix($message)
    {
        return "$message (ACTUAL SUFFIX)";
    }

}

Nothing surprising there: the service takes that decorator as a constructor arg, and then when calling decorateMessage the decorator's methods are called.

In our tests of decorateMessage, we don't want to use the real decorator methods, we want to use mocks.

First I have a test the way I'd normally mock things: explicitly calling setMethods with the method names:

public function testWithExplicitMock()
{
    $decorator = $this
        ->getMockBuilder(MyDecorator::class)
        ->setMethods(['addPrefix', 'addSuffix'])
        ->getMock();

 $decorator
  ->method('addPrefix')
  ->willReturn('(MOCKED PREFIX)');

 $decorator
  ->method('addSuffix')
  ->willReturn('(MOCKED SUFFIX)');

 $service = new MyService($decorator);

 $result = $service->decorateMessage('TEST MESSAGE');

 $this->assertSame(
        '(MOCKED SUFFIX)',
        $result
    );
}

And this mocks out the method correctly. This is my baseline.

Secondly, I still call setMethods, but I give it an empty array:

public function testWithImplicitMock()
{
    $decorator = $this
        ->getMockBuilder(MyDecorator::class)
        ->setMethods([])
        ->getMock();

 $decorator
  ->method('addPrefix')
  ->willReturn('(MOCKED PREFIX)');

 $decorator
  ->method('addSuffix')
  ->willReturn('(MOCKED SUFFIX)');

 $service = new MyService($decorator);

 $result = $service->decorateMessage('TEST MESSAGE');

 $this->assertSame(
        '(MOCKED SUFFIX)',
        $result
    );
}

This also mocks out the method correctly. Passing an empty array mocks out all the methods in the mocked class.

Next I try passing null to setMethods:

public function testWithNull()
{
    $decorator = $this
        ->getMockBuilder(MyDecorator::class)
        ->setMethods(null)
        ->getMock();

    $decorator
        ->method('addPrefix')
        ->willReturn('(MOCKED PREFIX)');

    $decorator
        ->method('addSuffix')
        ->willReturn('(MOCKED SUFFIX)');

    $service = new MyService($decorator);

    $result = $service->decorateMessage('TEST MESSAGE');

    $this->assertSame(
        '(ACTUAL PREFIX) TEST MESSAGE (ACTUAL SUFFIX)',
        $result
    );
}

This creates the mocked object, but the methods are not mocked. So the result here is using the actual methods.

In the last example I mock one of the methods (addPrefix), but not the other (addSuffix):

public function testWithOneExplicitMock()
{
    $decorator = $this
        ->getMockBuilder(MyDecorator::class)
        ->setMethods(['addPrefix'])
        ->getMock();

    $decorator
        ->method('addPrefix')
        ->willReturn('(MOCKED PREFIX)');

    $service = new MyService($decorator);

    $result = $service->decorateMessage('TEST MESSAGE');

    $this->assertSame(
        '(MOCKED PREFIX) (ACTUAL SUFFIX)',
        $result
    );
}

Here we see the important bit: when one explicitly mocks one or a subset of methods, then the other methods are not mocked. Which is kinda obvious now I say it, but I guess we had been thinking one needed to call setMethods to be able to then mock the behaviour of the method. And, TBH, I don't think we thought through what was happening to the methods we were not mocking. I think we started calling setMethods all the time because we were slavishly following the PHPUnit docs, which always call it too, which is a less than ideal precedent to "recommend".

Generally speaking, one should want to mock all methods of a dependency, and it should really be the exception when explicitly not wanting to mock a method: ie, to leave it actually callable.

The win here is that we can now de-clutter our tests a bit. We really seldom need to call setMethods, and we sure want to make it clear when we do want to only mock some of a dependency's methods. So this will make our code cleaner and clearer, and easier to code review and maintain. Win!

Cheers Pete!

Righto.

--
Adam

Tuesday, 7 February 2017

TDD: still testing too much

G'day:
A while back I wrote this article - Maintaining untested code, and mocking too much when backfilling test (with JavaScript & Jasmine) - wherein my colleague pulled me up for testing too much when I'm doing my TDD. I'm working with PHP this time, and I've just been pulled up on it again. For much the same reason. It created an interesting thought exercise... well interetsing ish... so I'll write it up here.

Firstly, I cannot use the actual code I am working with due to the usual over-baked "commercial sensitivity" considerations, so this is simply an analogy, but it captures the gist of what I was doing.

We have an adapter that handles HTTP calls. It's basically a generic wrapper that masks the API differences between Guzzle 5 and Guzzle 6, both of which we are still using, but we want a unified adapter to work with both. It's very generic, and basically has methods for GET, POST, PUT etc, and doesn't encompass any of our business logic.

Business reality is that we use this mostly to transmit JSONified objects, so we're adding a layer in front of it to take the raw text responses and rehydrate them. I'm writing the GET method.

When I start designing my solution, I know that the happy path will return me a Response object that has JSON as its content. I just need to deserialise the JSON and return it. This is easy enough:

public function testGetAsObject() {
    $expectedResult = ['result' => 'valid JSON'];
    $expectedContent = json_encode($expectedResult);

    $adapter = $this->mockAdapterWithResponse(200, $expectedContent);

    $client = new HttpClient($adapter);
    $result = $client->getWithJson($this->uri);

    $this->assertEquals($expectedResult, $result);
}

mockAdapterWithResponse just does this:

private function mockAdapterWithResponse($statusCode, $content){
    $response = $this->getMockBuilder(Response::class)
        ->setMethods(['getStatusCode', 'getContent'])
        ->getMock();
        
    $response->expects($this->once())
        ->method('getStatusCode')
        ->willReturn(statusCode);

    $response->expects($this->once())
        ->method('getContent')
        ->willReturn(content);

    $adapter = $this->getMockBuilder(Adapter::class)
        ->setMethods(['get'])
        ->getMock();
        
    $adapter->expects($this->once())
        ->method('get')
        ->willReturn($this->response);
        
    return $adapter;
}

(there are other tests that already needed this). All it's doing is enabling us to set what the content for the call to the HttpClient will return, because we don't care how the client goes about doing this; we just care what we do with the content afterwards.

So back to my test code:

public function testGetAsObject() {
    $expectedResult = ['result' => 'valid JSON'];
    $expectedContent = json_encode($expectedResult);

    $adapter = $this->mockAdapterWithResponse(200, $expectedContent);

    $client = new HttpClient($adapter);
    $result = $client->getWithJson($this->uri);

    $this->assertEquals($expectedResult, $result);
}

All I'm doing here is mocking the adapter to return a predetermined result, and I then test that what comes back from my function is the deserialisation of that value. That's it. I then implement some code to make that test pass:

public function getAsObject($uri, $params = [], $headers = []) {
    $response = $this->get($uri, $params, $headers);

    $object = json_decode($response->getContent(), true);

    return $object;
}

And that's green: all good.

I've already done too much in my test, but have not noticed (and not put it in for code review, so no-one's pointed it out).

The next requirement for this method is that if the content coming back from the adapter isn't JSON, then just throw an exception instead of trying to deserialise it. Again, pretty easy to TDD that:

/** @expectedException \Exception */
public function testGetAsObjectThrowsExceptionOnNonJsonContent() {
    $content = 'NOT_VALID_JSON';

    $adapter = $this->mockAdapterWithResponse(200, $content);

    $client = new HttpClient($adapter);
    $client->getWithJson($this->uri);
}

Note there's not even any explicit assertions in there: the @expectedException annotation takes care of it.

This test fails at present: no exception is thrown thanks to PHP's stupid-arse behaviour if json_decode is given non-JSON: it just returns null. Bravo, PHP. But that's what we want... we've designed our code and have tests to prove the design, now we need to implement the code:

public function getAsObject($uri, $params = [], $headers = []) {
    $response = $this->get($uri, $params, $headers);

    $object = json_decode($response->getContent(), true);
    
    if (is_null($object)) {
        throw new \Exception('Unable to decode JSON result');
    }
    return $object;
}

The tests are green, so all good.

But what have I done wrong? Nothing major, but once again I'm testing too much. Actually my tests are fine. It's my helper method that's "wrong":

private function mockAdapterWithResponse($statusCode, $content){
    $response = $this->getMockBuilder(Response::class)
        ->setMethods(['getStatusCode', 'getContent'])
        ->getMock();
        
    $response->expects($this->once())
        ->method('getStatusCode')
        ->willReturn(statusCode);

    $response->expects($this->once())
        ->method('getContent')
        ->willReturn(content);

    $adapter = $this->getMockBuilder(Adapter::class)
        ->setMethods(['get'])
        ->getMock();
        
    $adapter->expects($this->once())
        ->method('get')
        ->willReturn($this->response);
        
    return $adapter;
}

Why am I testing that those methods are called once? It is completely irrelevant to what I'm testing how many times they're called.

What got into my head is I was being too dogmatic when I was writing my code, and I also cheated a bit with the TDD. Instead of designing the test variation first, and then implemented the code to pass the test, I was doing both at the same time. And it shows from my test implementation. Basically I didn't design my solution first, I designed it as I went, so - whilst tricking myself into thinking I was doing TDD - I was asking myself "right, what does my code need to do? It needs to call get on the adapter. OK, so I'll test I call it. What next... I need to get the content from the response. Right: I'll make sure that getContent gets called". All very stolid and admirable, but: wrong. All this is doing is testing my code (almost line by line!). TDD is not about testing my code; it's about testing my design. And it's the design and behaviour of the API of the method I'm testing. As I've said before, it'd be excellent it TDD was retconned to mean "Test Driven Design", not "Test Driven Development". The former is closer to the intent of the exercise.

I don't need to test those methods are called once: there's no logic in my function that will vary how many times they might be called, so they will be called once. We can trust PHP to do what we tell it to do by giving it source code.

The logic flaw here was demonstrated by my code reviewer observed: if you think you need to check how many times that functionality is called, why are you also not concerned with how many times json_decode is called too? It's an important part of your functionality which definitely needs to be called. Ha... yeah "duh", I thought. This drove home I was testing too much.

Another thing is that I was getting too distracted by what my implementation was whilst writing my test. The requirements of that function are:

  • make a request, the response of which might be JSON:
  • if it is: return the deserialised content;
  • if it's not: raise an exception.

That first step is implementation detail from the POV of testing. What we're testing with TDD is the outcome of the function call. Which is: an object, or an exception. We're also only supposed to be testing the logic in the function the test is actually calling. I emphasise "logic" there because we're not testing the code. TDD is not about that. We're testing the logic. This is why we have two test variations: one wherein the logic to throw the exception is not true, so we get our object back; another when that if condition is true, so we get the exception.

We only need to test logic branching code (conditionals and loops, basically).

We do not need to prove every single statement is executed.

I was reminded a rule of thumb to apply when considering whether to use expects or not. Basically it boils down to if you have test variations which might result in a dependency being called a different amount of times, and that variation is germane to the result, then verify the call count.

Another good point made by my other colleague Brian was that when mocking stuff... only mock the bare minimum to get the test passing. Then stop. And that's probably the right place to leave it be.

An example of this might be if we had this logic in our method:

public function getAsObject($uri, $params = [], $headers = []) {
    $response = $this->get($uri, $params, $headers);

    $object = json_decode($response->getContent(), true);
    
    if (is_null($object)) {
        $this->logger("Bung content returned: $object");
        throw new \Exception('Unable to decode JSON result');
    }
    return $object;
}

Logging is an optional outcome of that method call, so we might want to test that the logger definitely gets called once in that second test. We could also be belt-and-braces about it and test it's not called at all in the happy-path test.

Or let's say the code was a caching proxy and one is testing the uncached and cached variations:

function get($id){
    if ($this->cache->exists($id)){
        return $this->cache->get($id);
    }
    return $this->dao->get($id);
}

In the uncached variation, the data is not found in the cache, so we want to make sure that the DAO is called to get the data from the DB. In the cached variation, we want to make sure the DAO isn't called. Even here, in the first example we're not really testing the DAO call was made specifically; we're testing the logic around the cache check didn't trigger the early return. We're testing the if statement, not the DAO call.

Bottom line, I think that if I find myself starting to do a call count on a mocked method, I need to stop to think about whether I will have cases where the expected call count will be different, and only then ought I even be bothering if the call-count variation is important to the outcome of the function call. If I'm only checking for one call count value... I'm doing it wrong.

And now rather than talking about my day job... I should actually be doing it...

Righto.

--
Adam

Tuesday, 1 November 2016

PHP: what exactly are we testing?

G'day:
"the rumours of this blog's demise... [etc]". Yeah. I'm still here. More about that in a separate article.

Here's a completely fictitious scenario which bears absolutely no relation to a code review discussion I had at work today. I don't even know why I mention work. This, after all, has nothing to do with my day job. Or my colleagues. I'm just making it all up.

Here's some code I just made up for the sake of discussion:

<?php 

namespace me\adamcameron\someApp\service;

use \me\adamcameron\someApp\exception;

class SomeService {

    public function getTheThingFromTheWebService($id){
        // bunch of stuff elided
        
        try {
            $result = $this->connector->getThing($id);
            
            // bunch of stuff elided
            
            return $finalResult;
        } catch (exception\NotFoundException $e) {
            // bunch of stuff elided
            
            throw new exception\ServiceException("The Thing with ID $id could not be retrieved");
         }
    }
}


Basically we're calling a web service, and the connector we use wraps up all the HTTP bumpf, and with stuff like 404 responses it throws a NotFoundException up to the service tier so that the service doesn't need to concern itself with the fact the connector is dealing with a REST web service. It could just as easily be connecting straight to a DB and the SELECT statement returned zero rows: that's still a NotFoundException situation. That's cool. We're all-good with this approach to things. For reasons that are outwith this discussion (TBH, I'm slightly contriving the situation in the code above, but maintaining the correct context) we don't want to let the NotFoundException bubble any further, we want to throw a different exception (one that other upstream code is waiting to catch), and provide a human-friendly message with said exception.

But for testing, I need to actually make sure that the Service handles this exception properly, and the two elements of this are that it chucks its own exception, and it includes that human-friendly message when it does.

So I've tested both of those.

Because I'm a well-behaved, team-playing dev, I TDD my code as I go, and having just added that exception-handling, I need to test itbeing about to add that exception-handling code, I need to write my tests first. So, anyway, whether I wrote the test before or after I wrote the code is neither here nor there. I came up with this test case:

<?php

namespace me\adamcameron\someApp\test\service;

use \namespace me\adamcameron\someApp\service\SomeService;

/** @coversDefaultClass \me\adamcameron\someApp\service\SomeService */
class SomeServiceTest extends PHPUnit_Framework_TestCase {

    public function setup() {
        $this->setMockedConnector();
        $this->testService = new SomeService($this->mockedConnector);
    }

    /**
        @covers ::getTheThingFromTheWebService
        @expectedException \me\adamcameron\someApp\exception\ServiceException
        @expectedExceptionMessage The Thing with ID 1 could not be retrieved
    */
    public function testGetTheThingsFromTheWebServiceWillThrowServiceExceptionWhenIdNotFound() {
        $testId = 1;

        $this->mockedConnector
            ->method('getThing')
            ->with($testId)
            ->will($this->throwException('\me\adamcameron\someApp\exception\NotFoundException'));
            
        $this->testService->getTheThingFromTheWebService($testId);
    }
    
    private function setMockedConnector(){
        $this->mockedConnector = $this->getMockBuilder('\me\adamcameron\connector\MyConnector')
            ->disableOriginalConstructor()
            ->setMethods(['getThing'])
            ->getMock();
    }

}

I've left a bunch of contextual and mocking code in there so you can better see what's going one. But the key parts of this test are the annotations regarding the exception.

(we're using an older version of PHPUnit, so we need to do this via annotations, not expectations, unfortunately. But anyway).

This went into code review, and I got some feedback (this is a paraphrase. Of, obviously, a completely fictitious conversation. Remember this is not really work code I'm discussing here):

  • Not too sure about the merits of testing the whole message string here. For the purposes of testing, we don't care about text, we just care about the $id being correct. Perhaps use @expectedExceptionMessageRegExp instead, with like "/.*1.*/" as the pattern.
  • not sure 1 is a great ID to mock, TBH

Well I can't fault the latter bit. If one is gonna be actually checking for values, then use values that are really unlikely to accidentally occur as side-effects of something else going on. Basically 0 and 1 are dumb test values. There's always gonna be a risk they'll end up bubbling up via an accident rather than on purpose. I usually use random prime numbers (often taken from this list of the first 1000 primes). They're just not likely to coincidentally show up somewhere else in one's results at random.

But the first bit is interesting. My first reaction was "well what's the important bit here? The number or the error message?" We wanna check the error message cos given that suggested regex pattern the exception could simply return 1, which is not help to anyone. And yet that'd pass the test. Surely the important thing is the guidance: "The Thing with ID [whatever] could not be retrieved".

But that's woolly thinking on my part. I'm not doing tests for the benefit of humans here. And no code is every gonna rely on the specifics of that message. TDD tests (and unit tests in general) are about testing logic, not "results". And they don't give a shit about the Human Interface. We've got QA for that.

There's no need to test PHP here. If we have this code:

throw new exception\ServiceException("The Thing with ID $id could not be retrieved");

We don't need to test:
  • that PHP knows what a string is
  • that PHP knows how to interpolate a variable
  • that one can pass a string to an Exception in PHP
  • etc
That's testing an external system (and showing no small amount of hubris whilst we do so!), and is outwith the remit of TDD or unit testing in general. If we can't trust PHP to know what a string is: we're in trouble.

What we do need to test is this:

public function getTheThingFromTheWebService($id){
    // bunch of stuff elided
    
    try {
        $result = $this->connector->getThing($id);
        
        // bunch of stuff elided
        
        return $finalResult;
    } catch (exception\NotFoundException $e) {
        // bunch of stuff elided
        
        throw new exception\ServiceException("The Thing with ID $id could not be retrieved"); 
    }
}


  • The ID comes in...
  • ... it's used...
  • ... and when the call that uses it fails...
  • ... we use it in the exceptional scenario.

The logic here is that it's the same ID that's passed to the function is the one reported in the exception message. Not what the exception message is: that's PHP's job.

So in reality here... we just need to test the ID is referenced in the exception. TBH, I'd not even usually bother doing that. It's just we're pushing to have more useful exception messages at the moment, so we're trying out "enforcing" this behaviour. I think it's a worthwhile exercise at least whilst the devs are getting used to it. Once it's part of our standard practice, I reckon we can stop worrying about what's in the error message.

But in the interim I'm gonna fix me test and see if I can pass this code review...

Righto.

--
Adam

Friday, 29 April 2016

PHP: Getting PHPCS and PHPMD working within PHPStorm

G'day:
These are just some quick notes on installing PHP Code Sniffer and PHP Mess Detector to work within PHPStorm. There's nothing particularly elucidating in here, it's simply what I did to get it working, cos someone asked me so I sat down and worked it out. Also my flight's been delayed so I have time to kill until Aer Lingus gets its act together.

I'm running PHPStorm 10 on Windows (Windows 10, in this case, not that it matters).

First up I used composer to install both applications:

C:\Users\adam.cameron\AppData\Roaming\Composer>php c:\ProgramData\ComposerSetup\
bin\composer.phar global require phpmd/phpmd

[much stuff ensued, but it all installed OK]

C:\Users\adam.cameron\AppData\Roaming\Composer>php c:\ProgramData\ComposerSetup\
bin\composer.phar global require squizlabs/php_codesniffer


Note that I initially tried to install the fabpot/php-cs-fixer as suggested in the composer global installation examples (I can never remember how to do it, so I googled), but that version of PHPCS seems to be out of date or dead or something. It dun't work in PHPStorm, anyhow.  I removed that:

C:\Users\adam.cameron\AppData\Roaming\Composer>php c:\ProgramData\ComposerSetup\
bin\composer.phar remove fabpot/php-cs-fixer

Once in PHPStorm I then go to File > Settings > Languages & Frameworks > PHP > Code Sniffer, and use the UI to point it to C:\Users\adam.cameron\AppData\Roaming\Composer\vendor\bin\phpcs.bat And do similar for PHPMD: C:\Users\adam.cameron\AppData\Roaming\Composer\vendor\bin\phpmd.bat.

In there one can "Validate" the install, which is how I found out the fabpot version of PHPCS didn't work.

Once I've done that, I went into Settings > Editor > Code Style > Inspections, within which each option of PHPCS and PHPMD is listed, and having selected each of those, can select the rulesets to use.

I just went for PSR-2 for PHPCS, and all options checked for PHPMD. At some stage I'll probably create a decent ruleset for my work (I hate PSR-2, and and to apply some Clean-Code-esque rules too), but for the time being, that'll do.

Having done that, PHPStorm now applies the rules:


Whilst I think that particular rule is bloody stupid, it is indeed one of PSR-2's rules, and it's also identifying it's being checked by phpcs. Success.

That's it!

Righto... time for another pint...

--
Adam