How I'm doing TDD with PHP

I've been watching the Destroy All Software back catalog over the last couple of months and it's really inspired me to up my TDD game. I'm still fairly new to TDD, I've written tests for a long time, but never really let it lead my development...

Outside-In or Middle-Out

I think I'm a middle-out kind of guy, I usually start with the business domain and try and decide what my API needs to offer. This means writing unit tests for some sort of Service class. From there, I'll branch out in to TDDing this service's collaborators.

Unit Tests

PHPUnit Logo

I use PHPUnit for my unit test needs, I think it's pretty much the defacto xUnit framework for PHP, I just did a quick search for SimpleTest and it's still hosted in SVN on SourceForge, enough said!

Granular

I try and write unit tests that are unit tests, not integration or functional tests. They take small (granular) pieces of code and test them.

Complete Isolation

I write my unit tests in almost complete isolation. It can get pretty messy, particular if you have a lot of 'glue' like services, bending several collaborators to their will. I make heavy use of dependency injection in the design of the code, and use lots of mock objects to isolate the SUT, then verifying a mixture of both behaviour and state, depending on how complex the SUT or it's collaborators are.

I've recently been putting in a lot of effort to relax the expectation constraints on the mock objects I use, so they're wired for expectations, but I don't verify them. This means I can test the behaviour of a method in individual tests if necessary, without having to repeat test code. I use mockery for this especially with it's shouldIgnoreMissing method, which essentially gets the mock to act like a Null Object. I'm hoping this will have the effect of making my tests easier to understand, in that you can see I'm only trying to verify the behaviour described by this test , and also I'm hoping it will lead to my tests being slightly less tied to the implementation.

Speed

Writing tests in isolation like this means my unit tests are fast. I don't hit any external services, I don't load any fixtures. My current test suite runs in less than 2 seconds.

Fast tests

Easy access

Gary from destroyallsoftware.com knows his way around vim. I had a look through his .vimrc and ended up copying loads of stuff from it. One of the most useful things I found is a few functions that will try and work out the test file for the current class you are editing and vice-versa. <leader>. will now switch between my production code and my unit tests. <leader>t will write the current file and run those tests (and will always remember the last run file, incase you switch back to production code or another file). The same mapping with uppercase T will run the tests and show PHPUnit's ascii code coverage report. This makes running tests so easy, there's no excuse not to. Checkout my .vimrc.

Fail first

I've been a lazy unit tester in the past, and probably wouldn't have made the effort to ensure a test can and will fail under the right circumstances, particularly when retro-fitting tests. With the nice easy access, mutating a test so that it will fail and running can easily be accomplished with less than a dozen keystrokes.

Fake it 'til you make it

Red -> Green -> Refactor

Something else I've not done before, this involves writing a test, writing the minimum amount of code to make that test pass. Write another test, write the minimum amount of code to make both tests pass, and so on.

Craving RSpec's DSL

Watching those videos on destroyallsoftware.com makes me crave the nice DSL that comes with RSpec. Duck punching the should and should_not methods on to everything makes the expectations look swell, same goes for the stub method.

For example, here's the example spec file given on the rspec website for a bowling game

# bowling_spec.rb
require 'bowling'

describe Bowling, "#score" do
  it "returns 0 for all gutter game" do
    bowling = Bowling.new
    20.times { bowling.hit(0) }
    bowling.score.should eq(0)
  end
end

PHPSpec goes some way to trying to match it, but doesn't do enough to make me want to switch from phpunit. The same example from the PHPSpec website:

class DescribeNewBowlingGame extends \PHPSpec\Context  
{  

    private $_bowling = null;  

    public function before()  
    {  
        $this->_bowling = $this->spec(new Bowling);  
    }  

    public function itShouldScore0ForGutterGame()  
    {  
        for ($i=1; $i<=20; $i++) {  
            // someone is really bad at bowling!  
            $this->_bowling->hit(0);  
        }  
        $this->_bowling->score->should->equal(0);  
    }  

}

That's before getting into stubs and mocks! There's not a real lot that PHPSpec can do, Ruby's syntax and flexibility plays straight in to rspec's hands. I don't actually like the idea of duck punching in production code, but it's use in a testing tool like this leads to an enjoyable experience.

I've also gone to great lengths over the last few years to make proper use of Dependency Injection or Inversion of Control in my code to really enable isolated unit testing, Ruby's ability to duck punch objects and in this case the so called 'singleton' object, makes it look pointless. Rubyists seem to write class methods everywhere, probably because they're so easy to mock and therefore easy to test as a collaborator, but also I think there's a lot to do with the libraries they use, like ActiveRecord. I'm sure there are other benefits of my approach (de-coupling is generally a good thing), but perhaps it would be nice to apply it a litte less liberally.

Integration Tests

Behat Logo

Writing unit tests in the way I've mentioned above simply isn't enough for most types of system. Heavy use of mock objects can easily hide cracks in the itegration between different systems, these need testing. For these purposes, I use Behat. These tests tend to test most of the stack, with only a few external services, such as mail or third party APIs that are stubbed out somehow. As such, these tests are fairly slow, taking over a minute to run. At some point I intend to make a push and start testing some the of the UI interaction, which will require a javascript enabled browser for these tests, which will slow them down even further, despite recent breakthroughs with things like Zombie.js and PhantomJS.

Not quite as fast tests

Breaking the cucumber rules

I usually cuke it wrong and I don't write my features in the proper 'story BDD' style. My features aren't really acceptance tests, they have fairly imperative setups and steps, and quite often involve mutliple actors, interacting as they would as part of a workflow. I make full use of the standard Mink DSL in order to rapidly develop features, rather than grafting my own DSL on top to create nicer looking feature files and a layer of abstraction that I probably wont use.

Continuous Integration

I'm a lone gunman in my current position, so continuous integration is downplayed a little, but I have Sismo configured to run composer, phpunit and then behat after each commit. It plays sounds on success/failure and does me for now. I did have Jenkins setup and pimped out to the max via Jenkins-PHP, but I found I wasn't getting much out of it.

That's all for now, will maybe follow up in a couple more months with more about my progress. I've recently picked up GOOS, I'm sure I'll learn how I'm doing it wrong when I get through that.