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
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.
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
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
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.
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.