Wednesday, September 23, 2009

Practical testing in php part 1: PHPUnit usage

What is unit testing and why a php programmer should adopt it? It may seem simple, but testing is the only way to ensure your work is completed and you will not called in the middle of the night by a client whose website is going nuts. The need for quality is particularly present in the php environment, where it is very simple to deploy an interpreted script, but it is also very simple to break something: a missing semicolon in a common file can halt the entire application.
Unit testing is the process of writing tests which exercise the basic functionality of the smallest cohesive unit in php: a class. You can also write tests for procedures and functions, but unit testing works at its best with cohesive and decouple classes. Thus, object-oriented programming is a requirement; this process is contrapposed to functional and integration testing, which build medium and large graphs of objects when run. Unit testing istances one, or very few, units at the time, and this implies that unit tests are tipically easy to run in every environment and do not burden the system with heavy operations.

When the time comes for unit and functional testing, there's only one leader in the php world: PHPUnit. PHPUnit is the xUnit instance for the average and top-class php programmer; born as a port of JUnit, has quickly filled the gap with the original Java tool thanks to the potential of a dynamic language such as php. It even surpassed the features and the scope of JUnit providing a simple mocking framework (whose utility will be discovered during this article series).

The most common and simplest way to install PHPUnit is as Pear package. On your development box, you have to be available the php binary and the pear executable.
sudo pear channel-discover pear.phpunit.de
sudo pear install phpunit/PHPUnit
Use a root shell (or a administrator on if you develop on other systems) instead of sudo if you prefer. These commands tell pear to add the channel of phpunit developers as it was a package repository, and to install the PHPUnit package from the phpunit channel. The grabbed release is the latest stable; at the time of this writing, the 3.4 version.
If the installation is successful, you now have a phpunit executable available from the command line. This is where you will run tests; if you use an IDE, probably there is a menu for running tests that will show you the command line output (and you should also install phpunit from the IDE interface to make it discover the new tool).

Before exploring the endless possibilities of testing, let's write our first one: the simplest test that could possibly work. I saved this php code in MyTest.php:
class MyTest extends PHPUnit_Framework_TestCase
{
public function testComparesNumbers()
{
$this->assertTrue(1 == 1);
}
}
What is a test? And a test case? A test case is constituted by a method by a class which extends PHPUnit_Framework_TestCase, which as its name tells is an abstract test case class provided by the PHPUnit framework. When developing a object-oriented application, you may want to start with one test case per every class you want to test (and if you're going the TDD way every class will be tested), thus there will be a 1:1 correspondence between classes and test cases. For the moment, we don't want to go too fast and we simply write a class that tests php common functionality.
Every test is a method which by convention starts with the keyword 'test'. Also for convention, the method name should tell what operation the system under test is capable, in this test .
Every method will be run independently in an isolated environment, and will make some assertion on what should happen. assertTrue() is one of the many assert*() method inherited from the abstract test case, which declares the test failed if an argument different from true is passed to it. The test as it is written now should pass. In fact, we can simply run it and find out:
[giorgio@Marty:~]$ phpunit MyTest.php
PHPUnit 3.4.0 by Sebastian Bergmann.

.

Time: 1 second

OK (1 test, 1 assertion)
Instant feedback is one of the pillars of TDD and of unit testing in general: the code in tests should instance your classes and exercising their functionality to ensure they don't blow up and respect the specifications. With the phpunit script, it's very simple and fast to run a test case or a group of them after you have made a change to your class and make sure you haven't break an existing feature.
The result of phpunit run is easily interpretable: a dot (.) for every test method which passed (without failed assertions), and a statistic of the number of tests and assertions encountered.
Let's try to make it fail, changing 1==1 to 1==0:
[giorgio@Marty:~]$ phpunit MyTest.php
PHPUnit 3.4.0 by Sebastian Bergmann.

F

Time: 0 seconds

There was 1 failure:

1) MyTest::testComparesNumbers
Failed asserting that  is true.

/media/sdb1/giorgio/MyTest.php:6

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
For every failed test, you get an F instead of the point in the summary. Other letters can be encountered, for instance the E if the test caused no assertion to fail but it raised an exception or a php error. The only passed tests are the one which present a dot: you should strive for a long list of dots that fill more than one row.
You also get a description of the assertion which has failed and in what test case, method and line it resides. Since you can run many test cases as a single command, this is very useful to localize defects and regression.
This time the test has failed because it is bad written: zero is not equal to one and php is right in giving out false. But assertTrue() does not know this and in the next part we'll write some tests which works upon userland code and it is in fact useful to detect if production classes are still satisfying all their requirements.

You may want to subscribe to the feed to not miss the subsequent parts of this php testing series. Feel free to ask clarifications in the comments or to raise testing topics you particularly care for.

4 comments:

Anonymous said...
This comment has been removed by a blog administrator.
Sean said...

You should checkout Netbeans for it's PHPUnit integration ;)

Giorgio said...

I used NetBeans for Java development, but currently I still work best with vim and the command line.. :)

Sean said...

Fair enough... never could get the hang of vim myself though I do use it on occasion.

ShareThis