CakePHP 2.2
I personally consider Test Driven Development (TDD) as a “must do”, rather than a “nice to have”. Unfortunately as developers we often suffer from tight deadlines, tons of requirements and deliverables and sometimes a simple lack of understanding as to why TDD is so important.
Granted, TDD requires certain time and effort to be spent before “real” development begins (I am willing to argue this point), therefore unit testing becomes an afterthought. This is rather sad, because in the long run it will save you time, frustration and money. No longer will you have to litter your application code with debug statements or try to trace logic of an application because something unforeseen and inexplicable is suddenly happening.
PHPUnit manual (a de facto standard in PHP unit testing and the testing framework leveraged by CakePHP) puts it quite nicely:
The sooner you test for a mistake the greater your chance of finding it and the less it will cost to find and fix. This explains why leaving testing until just before releasing software is so problematic. Most errors do not get caught at all, and the cost of fixing the ones you do catch is so high that you have to perform triage with the errors because you just cannot afford to fix them all.
Let’s take our favorite blog tutorial and approach it the TDD way.
I will presume that you are familiar with CakePHP and have read about testing within CakePHP environment already, and have setup your app to be ready for testing (i.e. installed PHPUnit, setup test DB… and possibly installed Xdebug. For details please refer to the CakePHP manual in the link above).
If we follow the blog tutorial, you’ll see that first we will be creating a Post Model. Since we are doing things via TDD, this sort of translates into the idea that we’ll need a Post Fixture. You could work with the live data instead, but since we are just getting started fixtures is a straight-forward and solid approach to unit testing.
So, app/Test/Fixture/PostFixture.php:
class PostFixture extends CakeTestFixture {
public $fields = array(
'id' => array('type' => 'integer', 'key' => 'primary'),
'title' => array('type' => 'string', 'length' => 255, 'null' => false),
'body' => 'text',
'created' => 'datetime',
'updated' => 'datetime'
);
public $records = array(
array('id' => 1, 'title' => 'The title', 'body' => 'This is the post body.', 'created' => '2012-07-04 10:39:23', 'updated' => '2012-07-04 10:41:31'),
array('id' => 2, 'title' => 'A title once again', 'body' => 'And the post body follows.', 'created' => '2012-07-04 10:41:23', 'updated' => '2012-07-04 10:43:31'),
array('id' => 3, 'title' => 'Title strikes back', 'body' => 'This is really exciting! Not.', 'created' => '2012-07-04 10:43:23', 'updated' => '2012-07-04 10:45:31')
);
}
We’ll keep things simple and create the fixture with some test records, just like the blog tutorial suggests.
If you’ve read and tried the blog tutorial already, you’ll see that all of the logic is really contained in the controller. Additionally all the heavy lifting is delegated to the core methods such as save() and find().
It would be silly to test these core methods (as cake developers have already done a great job at that), therefore I am going to create wrapper methods in the model, so that we have something to test. First, because models are generally easier to test than controllers, and secondly because even in such a simple app we can yet again benefit from the golden rule of MVC “fat models, skinny controllers”.
Let us modify the code just a little to see what I mean.
Instead of writing out the controller just yet, I am going to create a method in our Post Model to get Post.id and Post.title for our soon-to-be-created index view.
(Since we’ve read the blog tutorial, we are aware of the expectations and can therefore approach our application with a TDD mindset).
app/Model/Post.php
Our model now has one method, which I can test to be sure that my expectation of how it should behave matches the reality.
Thus, comes our first test app/Test/Case/Model/PostTest.php:
App::uses('Post', 'Model');
class PostTest extends CakeTestCase {
public $fixtures = array('app.post');
public function setUp() {
parent::setUp();
$this->Post = ClassRegistry::init('Post');
}
public function testGetAllPosts() {
$result = $this->Post->getAllPosts();
$expected = array(
array('Post' => array('id' => 1, 'title' => 'The title')),
array('Post' => array('id' => 2, 'title' => 'A title once again')),
array('Post' => array('id' => 3, 'title' => 'Title strikes back'))
);
$this->assertEquals($expected, $result);
}
}
If we execute our test, the following output should be shown: “1/1 test methods complete: 1 passes, 0 fails, 1 assertions and 0 exceptions.” (If Xdebug is installed the coverage should be at 100%, w00t!).
This is all good news… but what have we accomplished?
First of all, without having to populate our application table with any testing data we were able to make sure that our getAllPosts(); method is working as expected, thanks to our Post Fixture. Secondly, I am quite confident now that once I start writing the controller code I will not run into any trouble.
Pfft… all this coding just to make sure that “The title” which I wrote in the fixture matches my expectation of “The title” in test case. Well, yeah. Yet, imagine a slightly more complicated app. I might already have some data in the DB, and now by using TDD and fixtures adding a new method to the model will not require me to add fake data to the table, mess up my pretty code with debug statements and so on and so forth.
Just like tight coupling is generally bad for your system architecture, coupling testing and production code is just as bad (if not worse) and wasteful. Also, this sets us up for a solid foundation and proper mindset for further growth and easy development.
Let us move forward, now that we have prepared our model/method to build an index() action.
What comes next? Eventually, we would like to build a view() action, which is going to display a single post based on a given $id. With our TDD mindset, let us extend our Post Model and Post Test.
First we’ll add a new method to our Post Model
If you’ve been around CakePHP for a little while, chances are this method is going to work perfectly well. Although after years of development I can definitely misspell “conditinos” (ooops!). I am not taking any chances, not when my work comes down to 0 and 1 eventually, and therefore I will be sure to write a test case for it. If we were to analyze our code coverage at this point we only have tests for 62.5% of the application… bad developer.
Let’s add a test for the new method:
$result = $this->Post->getSinglePost(2);
$expected = array(
'Post' => array(
'id' => 2,
'title' => 'A title once again',
'body' => 'And the post body follows.',
'created' => '2012-07-04 10:41:23',
'updated' => '2012-07-04 10:43:31'
)
);
$this->assertEquals($expected, $result);
Once again, if we were to run to the test the output should show that our app is at 100% coverage and that our method is indeed returning the record where ID = 2. For the sake of discussion try to write “condition” instead of “conditions” in our Post Model. The test immediately fails.
Yet, without unit testing I could’ve clicked on the link in the browser to see my post and possibly not even realize that cake returned the wrong record, in such case it would simply return the first record (clicked a link, saw a post, quickly move to the next step). Thus a perfect example of how a simple bug can slip by a developer, especially after hours of coding. Also, by testing in small increments I can quickly narrow down my attention to the specific method, and hopefully catch my mistake quicker. We all know that such issues become hard to spot after coding for hours and jumping all over the app.
Great, we are now ready to setup our add() method.
Let’s extend the Post Model even further:
return $this->save($postData);
}
And let’s add a new test for this rather simple method:
$postData = array(
'title' => 'Test Post Title',
'body' => 'We love TDD. Yeah!',
'created' => '2012-07-04 10:43:23',
'updated' => '2012-07-04 10:45:31'
);
$numRecordsBefore = $this->Post->find('count');
$result = $this->Post->addPost($postData);
$numRecordsAfter = $this->Post->find('count');
$expected = array(
'Post' => array(
'id' => 4,
'title' => 'Test Post Title',
'body' => 'We love TDD. Yeah!',
'created' => '2012-07-04 10:43:23',
'updated' => '2012-07-04 10:45:31'
)
);
$this->assertEquals(4, $numRecordsAfter);
$this->assertTrue($numRecordsBefore != $numRecordsAfter);
$this->assertEquals($expected, $result);
}
Essentially we are just re-testing the save() method, but there are some interesting things happening here nonetheless. I know that my original record-set contained 3 rows, I would like to verify that by counting the number of rows before and after the save() operation. Because I am saving a single record from my $postData array, my expectation would be to now have four records.
So, as you see, in a single test I can make a number of “assertions” to really bullet-proof my expectations.
1. I expect the number of records to increase by one.
2. The number of records before and after the save() operation should not be the same.
3. My saved record will have a new ID, which is equal to 4 (since our primary key column is auto-incremented). Notice the difference between my data array and my expected array, which now has an ‘id’ column.
This demonstrates how a very simple method can be thoroughly tested using multiple assertions, so that I can peacefully sleep at night knowing that my code is working exactly as I expect it too.
Last, but not least, we have an edit() method to worry about. Let us proceed with creating an editPost() method and a relevant test case.
In app/Model/Post.php, we’ll add:
return $this->save($postData);
}
And our test case:
$this->Post->id = 3;
$postData = array(
'title' => 'Test Post Title. Updated.',
'body' => 'We love TDD. Yeah! Yeah!',
'created' => '2012-07-04 10:43:23',
'updated' => '2012-07-04 10:49:51'
);
$recordBeforeEdit = $this->Post->read();
$numRecordsBefore = $this->Post->find('count');
$result = $this->Post->editPost($postData);
$numRecordsAfter = $this->Post->find('count');
$expected = array(
'Post' => array(
'id' => 3,
'title' => 'Test Post Title. Updated.',
'body' => 'We love TDD. Yeah! Yeah!',
'created' => '2012-07-04 10:43:23',
'updated' => '2012-07-04 10:49:51'
)
);
$this->assertEquals($expected, $result);
$this->assertTrue($numRecordsBefore == $numRecordsAfter);
$recordCompare = array_diff($recordBeforeEdit['Post'], $result['Post']);
$expectedArrayDiffResult = array(
'title' => 'Title strikes back',
'body' => 'This is really exciting! Not.',
'updated' => '2012-07-04 10:45:31'
);
$this->assertEquals($expectedArrayDiffResult, $recordCompare);
}
Whoa, again we have a rather simple method and rather thorough test with some important expectations. In this case we are modifying our record with ID = 3.
1. Since we are presuming an SQL UPDATE, our expected result should contain ‘id’ => 3 (as evident from the $expected array).
2. We need to be certain that, unlike in our addPost() operation, in this case the number of records remains the same.
3. Comparing the existing record to an updated one, we should have “title”, “body” and “updated” fields changed, while our “id” and “created” fields remain the same. We verify this by using PHP’s array_diff() and comparing our expectation to the actual output.
What about data validation?
Surely we can test that as well. Let’s add validation rules to our Post Model, just like suggested in the blog tutorial.
And a test case, to seal the deal:
$postData = array(
'title' => '',
'body' => 'Oh no, this post has and empty title!'
);
$result = $this->Post->addPost($postData);
$invalidFields = $this->Post->invalidFields();
$this->assertFalse($result);
$this->assertContains('This field cannot be left blank', $invalidFields['title']);
$postData = array(
'title' => 'No body in the post? Impossible.',
'body' => ''
);
$result = $this->Post->addPost($postData);
$invalidFields = $this->Post->invalidFields();
$this->assertFalse($result);
$this->assertContains('This field cannot be left blank', $invalidFields['body']);
$postData = array(
'title' => 'Title...',
'body' => '... and body.'
);
$result = $this->Post->addPost($postData);
$invalidFields = $this->Post->invalidFields();
$this->assertFalse(empty($result));
$this->assertTrue(empty($invalidFields));
}
First we are trying to save a post with an empty “title”, which should not be allowed. We prove that by asserting that our $result will be false. We will also verify that the proper validation error was triggered for the given field, “title”. We do exactly the same for the “body” of the post.
Of course, we should also make sure that given some “title” and some “body” the post will be saved, otherwise we could not be certain that our validation rules are not preventing a perfectly valid data to be saved at all. In this case we could compare $result to the actual expected array, but we have done so already while testing the addPost() method. Just a couple of simple false/true assertion will solidify our testing scenarios.
In theory, and this is something I would recommend for a more complex case, we could break up our testValidation() method into testEmptyTitle(), testEmptyBody(), testGoodTitleBody() methods.
In summary, our business logic of the application has been thoroughly tested, we can list multiple posts, we can view a single post, we can add and edit posts and we’ve made sure that our validation rules are working as expected.
Now I am content to commit my code and continue working on the application or pass it onto front-end engineers to build views or another developer to work on controllers. Talk about taking good care of your teammates and taking good care of your coding. Also, we didn’t even write a single line of code in the controllers or the views, but we can be certain that the logic of the application is quite solid. Do you see how TDD is playing so well with MVC paradigm of keeping our business logic in the Model?
In the next part we will continue building and putting finishing touches onto our awesome TDD app.