Unit Testing Best Practices
From Joomla! Documentation
Introduction[edit]
This article lists best practices for writing unit tests for Joomla. These practices have not been implemented in all tests of the existing test suite yet. If you find a place where this is not implemented you are invited to change it.
Use Joomla's Test Case Classes[edit]
When writing unit tests for Joomla, extend your test class from TestCase or TestCaseDatabase instead the default PHPUnit_Framework_Testcase. The Joomla cases provide a few ready-to-use mock objects and also provide some special exception handling related to the legacy JError classes.
One Test Per Method[edit]
Make sure that each method tests exactly one thing. This makes it easier to find the cause of a failed test, makes test methods more readable and gives more meaningful Testdox outputs (see below).
Let's assume we want to test this class:
<?php
class Foo
{
public function bar($string)
{
if (is_int($string))
{
return false;
}
$string = strtoupper($string);
return $string;
}
}
Here's a bad example of a test for this method. It tests two things (convert to uppercase, return false on int) in the same method:
<?php
class FooTest extends TestCase
{
public function testBar()
{
$object = new foo();
$this->assertEquals('EXAMPLE', $object->bar('example'));
$this->assertEquals(false, $object->bar(4));
}
}
A better approach would be to split these two assertions into two methods:
<?php
class FooTest extends TestCase
{
public function testBar1()
{
$object = new foo();
$this->assertEquals('EXAMPLE', $object->bar('example'));
}
public function testBar2()
{
$object = new foo();
$this->assertEquals(false, $object->bar(4));
}
}
Use Indicative Names for Test Methods[edit]
Indicative names for your test methods increase readability and add extra documentation to your test.
Using the example above, indicative method names could be:
<?php
class FooTest extends TestCase
{
public function testStringIsConvertedToUppercase()
{
$object = new foo();
$this->assertEquals('EXAMPLE', $object->bar('example'));
}
public function testFalseIsReturnedWhenIntIsUsedAsArgument()
{
$object = new foo();
$this->assertEquals(false, $object->bar(4));
}
}
Use the Most Specific Assertion Possible[edit]
PHPUnit offers a range of different assertion methods. By using the most specific one available, you reduce code and make your tests more strict. That leads to more meaningful results. Let's improve the example used above:
<?php
class FooTest extends TestCase
{
public function testStringIsConvertedToUppercase()
{
$object = new foo();
// assertSame is type safe. In this case, only strings are accepted.
$this->assertSame('EXAMPLE', $object->bar('example'));
}
public function testFalseIsReturnedWhenIntIsUsedAsArgument()
{
$object = new foo();
// reduced code
$this->assertFalse($object->bar(4));
}
}
Run Your Tests with --strict and --verbose[edit]
By running your tests with the --strict and --verbose parameters, PHPUnit will provide much useful information. For example:
- Tests that don't make any assertions and therefor are useless
- Tests that have a todo
- Tests that are skipped
Generating Testdox[edit]
When test methods have indicative names, the --testdox parameter is a useful instrument to get a human-readable overview of passing and falling tests. Running our example from above with --testdox outputs:
PHPUnit 4.3.1 by Sebastian Bergmann Foo [x] String is converted to uppercase [x] False is returned when int is used as argument
To generate this output, PHPUnit uses the method name, strips the "test" at beginning and converts each uppercase letter into a space.
If you want to override the default testdoc generated for a test method, it's also possible to override this using the @testdox annotation in the docblock:
/**
* @testdox Test retrieving an instance of JDocumentHTML
*/
public function testRetrievingAnInstanceOfTheHtmlDocument()
{
$this->assertInstanceOf('JDocumentHTML', JDocument::getInstance());
}
Note Use this only for documentation and not while you are working on the tests. It doesn't give you any information on why a test failed.