Most of my career, documentation and tests were something that happened after the project was done. They were both an afterthought and annoyance. “Why should we do this? We have so many bugs to do.”
Given this background, when I was approached about Test Driven Development (TDD), I thought my college was crazy. “Tests are something you do at the end, if you have time. Why would I want to do tests BEFORE I code?” My protests were met with mandatory tests, so I figured I’d give this new way of doing things a shot. Boy was I wrong.
Test Driven Development—Why it Is Awesome
For those unfamiliar, Test Driven Development is the process of writing unit tests first (expecting them to fail) and then writing the code to make the tests pass. The benefits of doing this include:
• forces the programmer to plan out the functionality head of time
• forces the functions to be small, concise, and testable
• leaves the programmer with a full unit test suite with 100% test coverage
These points really change the way you think as a developer. My functions are now very small and to the point. Generally, when I find it difficult to test something, it means I’ve made it too complicated and should break it into smaller parts.
You have my attention, now what?
One of the best unit testing suites for PHP is PHPUnit. This is a 100% free framework and is very powerful. While it can be a bit difficult to learn to use, it gives you complete control over your code.
For example, consider you have a class A with two functions, foo and bar. Foo is a standalone function, and Bar calls Foo before processing. I bring up this structure because it gives us a simple example for Mock Objects.
Mock Objects are the unit test bread and butter. Essentially, Mock Objects allow you to create a class and stub out different functions. For example, if you have a call to an external API or Database, you can use a mock object of your class to hardcode the return values of these external calls to test the units of code that consume them—this is super powerful. I have written an entire application utilizing an API I didn’t have access to at the time, but utilized unit tests to mock out the responses from the API (according to their docs). This allowed me to write the application while we waited for access. Once I was granted access, my application worked flawlessly with the API—all thanks to the unit tests.
Using PHPUnit
So, back to our example class. To start, we want to write a test for the Foo function. Since this function doesn’t call other functions, we can use a standard mock object.
$fooclass = $this->getMockBuilder('FooClass') ->disableOriginalConstructor() ->getMock();
This call creates a mock object of the FooClass, but creates it without calling the constructor of FooClass. This is useful if you have setup code that connects to a database. As a side note, a good unit test only tests a unit of code, not external resources (unless that test is to test the database layer, for instance)
With the mock object, we can then call foo, and assert that it returns what we expect.
$this->assertEquals('foo', $fooclass->foo(1));
This line asserts that if you call foo with 1, it returns “foo”. If this is expected behavior, then your test will pass. You could also add another line to test different conditions:
$this->assertEquals('foo2', $fooclass->foo(2));
At this point, you may be saying to yourself “hey now, I’m copying and pasting code… surely there’s another way to do this…”—and you’re RIGHT!
Using DataProviders
A DataProvider is a quick and dirty way to reuse test code with large datasets. You simply annotate the test function with @dataProvider <function> to have that test use the dataProvider for the test Let’s apply this to our Foo Test:
/**
* @dataProvider fooTestProvider
*/
public function fooTest($return, $call){
$fooclass = $this->getMockBuilder('FooClass') ->disableOriginalConstructor()->getMock();
$this->assertEquals($return, $fooclass->foo($call));
}
public function fooTestProvider(){
return array(
array("foo", 1),
array("foo2", 2)
);
Now, when fooTest is called, it will be called with the arrays defined in fooTestProvider. We have set our two test cases as arrays to be returned. Each element in these arrays are passed in as parameters to the test function utilizing the provider. In the first case, “foo” is passed through $return, and 1 is passed through $call. DataProviders provide a means to test many different situations with lots of different inputs and outputs.
What’s Next?
As I said before, PHPunit supports lots of complex structures that will help you test your code in all situations. Familiarize yourself with the documentation and some of those examples. In the next article, we will cover some more advanced topics such as stubbing functions, and how we are going to test Bar (which calls Foo).