Unit Testing Best Practices: Difference between revisions

From Joomla! Documentation

Created page with "==Introduction== This article lists a few best practices related to writing unit tests for Joomla. These best practices have not been implemented in all tests of the existing ..."
 
Cmb (talk | contribs)
Some markup, capitalization and spelling changes.
 
(8 intermediate revisions by 2 users not shown)
Line 1: Line 1:
==Introduction==
== Introduction ==
This article lists a few best practices related to writing unit tests for Joomla. These best practices have not been implemented in all tests of the existing testsuite yet, so consider this as a work in progress.
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.


==One test per method==
== Use Joomla's Test Case Classes ==
Make sure, that each test method tests exactly one thing. This makes it much easier to find out what causes a failed test, makes test methods more readable and also gives more meaningful Testdox outputs (see below).
When writing unit tests for Joomla, extend your test class from ''TestCase'' or ''[[Automated Testing With Database|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 ==
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:
Let's assume we want to test this class:
<source lang="php">
<syntaxhighlight lang="php">
<?php
<?php
class Foo
class Foo
Line 12: Line 15:
public function bar($string)
public function bar($string)
{
{
if(is_int($string))
if (is_int($string))
{
{
return false;
return false;
Line 22: Line 25:
}
}
}
}
</source>
</syntaxhighlight>


Below you can find a bad example of a test for this method, because it tests two different things (convert to uppercase, return false on int) in the same method:
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:
<source lang="php">
<syntaxhighlight lang="php">
<?php
<?php
class FooTest extends PHPUnit_Framework_Testcase
class FooTest extends TestCase
{
{
public function testBar()
public function testBar()
Line 37: Line 40:
}
}
}
}
</source>
</syntaxhighlight>


A better approach would be to split these two assertions into two test methods:
A better approach would be to split these two assertions into two methods:
<source lang="php">
<syntaxhighlight lang="php">
<?php
<?php
class FooTest extends PHPUnit_Framework_Testcase
class FooTest extends TestCase
{
{
public function testBar1()
public function testBar1()
Line 58: Line 61:
}
}
}
}
</source>
</syntaxhighlight>


==Use meaningful names for test methods==
==Use Indicative Names for Test Methods==
Meaningful names for your test methods increase readability and add a bit of extra documentation to your test. Meaningful tests also give a really nice output when using the --testdox option of PHPUnit (see below).
Indicative names for your test methods increase readability and add extra documentation to your test.


Using the example above, meaningful method names could look like this:
Using the example above, indicative method names could be:


<source lang="php">
<syntaxhighlight lang="php">
<?php
<?php
class FooTest extends PHPUnit_Framework_Testcase
class FooTest extends TestCase
{
{
public function testStringIsConvertedToUppercase()
public function testStringIsConvertedToUppercase()
Line 83: Line 86:
}
}
}
}
</source>
</syntaxhighlight>


==Use the most specific assertion possible==
== Use the Most Specific Assertion Possible ==
PHPUnit offers a wide range of different [https://phpunit.de/manual/current/en/appendixes.assertions.html assertion methods]. By using the most specific one that's available, you reduce code in your tests and also make your tests more strict which leads to more meaningful results. Again, let's improve the example used above:
PHPUnit offers a range of different [https://phpunit.de/manual/6.5/en/appendixes.assertions.html 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:


<source lang="php">
<syntaxhighlight lang="php">
<?php
<?php
class FooTest extends PHPUnit_Framework_Testcase
class FooTest extends TestCase
{
{
public function testStringIsConvertedToUppercase()
public function testStringIsConvertedToUppercase()
Line 96: Line 99:
$object = new foo();
$object = new foo();


// assertSame is type safe, so in this case only strings are accepted
// assertSame is type safe. In this case, only strings are accepted.
$this->assertSame('EXAMPLE', $object->bar('example'));
$this->assertSame('EXAMPLE', $object->bar('example'));
}
}
Line 108: Line 111:
}
}
}
}
</source>
</syntaxhighlight>


==Run your tests with --strict and --verbose==
== Run Your Tests with ''--strict'' and ''--verbose'' ==
By running your tests with the --strict and --verbose parameters, PHPUnit will provide you a lot of useful informations, for example:
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 don't make any assertions and therefor are useless
* Tests that have a todo
* Tests that have a ''todo''
* Tests that are skipped
* Tests that are skipped


==Generating Testdox==
== Generating Testdox ==
When test methods have meaningful names, the --testdox parameter is a very useful instrument to get a human readble, nicely looking overview of passing and falling tests. Running our example from above with --testdox outputs:
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.
  PHPUnit 4.3.1 by Sebastian Bergmann
  Foo
  Foo
   [x] String is converted to uppercase
   [x] String is converted to uppercase
Line 126: Line 128:


To generate this output, PHPUnit uses the method name, strips the "test" at beginning and converts each uppercase letter into a space.
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:
<syntaxhighlight lang="php">
/**
* @testdox  Test retrieving an instance of JDocumentHTML
*/
public function testRetrievingAnInstanceOfTheHtmlDocument()
{
$this->assertInstanceOf('JDocumentHTML', JDocument::getInstance());
}
</syntaxhighlight>
'''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.
[[Category:Bug Squad]] [[Category:Development]] [[Category:Testing]] [[Category:Automated Testing]]

Latest revision as of 15:27, 21 October 2022

Introduction

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

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

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

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

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

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

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.