Quality-Assurance with PHP – Unit-Testing

Unit-Testing

There has been much said about the advantages or disadvantages of unit-testing. So I will not recap on that.
But where to go after you have choosen to use unit-testing?

The following short tutorial will give you an ovberview of how to install the necessary software, how to come to unit-tests from your requirements and how to write your tests and your code simultaneously.

Install PHPUnit

So the first think would clearly be to install the necessary software.

That is an altogether complicated task that involves a terminal/shell, rights to install pear-packages, a connection to the internet and about 70 keys on your keyboard.

So in your terminal/shell type the following:
beteigeuze ~ $ pear channel-discover http://phpunit.de
beteigeuze ~ $ pear install phpunit/PHPUnit

Now you should have installed the current version of PHPUnit. Test it with the following comand:
beteigeuze ~ $ phpunit --version
PHPUnit 3.4.5 by Sebastian Bergmann.

Now you have successfully installed PHPUnit.

Start test-driven development

Where to go from there?
How do you develop software? For me it normaly starts with someone asking me to write a bit of code that does something.

For instance
Hey, I need a function that multiplies two values with one another.

So what does that tell me?

  • I have to write a function, not a method.
  • The function takes two parameters, not more not less.
  • The function returns the result of the multiplikation of the two values.

Some months ago I would have started coding the function right away. But stop.
I want to use unit-testing.

So I start right away with coding the necessary tests.

What tests do I need? Obviously I need a test, that asserts that the method can only be called with two parameters and another one that asserts, that the result of the function is equal to the expected parameter.

So I start my favourite Editor and off we go.

<?php
/** Include the TestCase-Class of PHPUnit which we will extend */
require_once 'PHPUnit/Framework/TestCase.php';

/** To test the function I need to include the file with my function */
require_once 'myFunction.php';

class FunctionTest extends PHPUnit_Framework_TestCase
{

	/**
	 * Call the function as you like but the name
	 * has to be prepend with 'test'
	 */
	public function testFunctionReturnsExpectedResult() {
		$result = multiply(2,2);
		$this -> assertEquals ( 4, $result );
	}
}

Save the whole stuff as ‘FunctionTest.php’ in a new folder of say name ‘multiplyFunction’, go to your Terminal/Shell, change to that folder and start testing right away.
beteigeuze ~ $ cd multiplyFunction
beteigeuze multiplyFunction $ phpunit FunctionTests

The result is somehow irritating but expectable:
PHP Warning: require_once(myFunction.php): failed to open stream: No such file or directory in (path)
I have not yet created the file to define the function in. So lets do that with the minimum fuzz I need. In the folder ‘multiplyFunction’ I create a file with name myFunction.php and type the following:

<?php
function multiply($a,$b){}

Now I test again with the following result:
beteigeuze multiplyFunction $ phpunit FunctionTest
PHPUnit 3.4.5 by Sebastian Bergmann.
 
F
 
Time: 0 seconds, Memory: 7.00Mb
 
There was 1 failure:
 
1) ClassTest::testFunctionReturnsExpectedResult
Failed asserting that matches expected .
 
multiplyFunction/FunctionTest.php:10
 
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

What happened: The test has failed (The single “F” in Line 4 marks the single Test that has been found to fail while executed.
As the basic method currently does not return anything at all the test had to fail.

So to satisfy the test, I simply adapt my function in the following way:

function multiply($a, $b){
	return 4;
}

It does not actually multiply anything, but it does satisfy the test, doesn’t it? Test it and voila:
beteigeuze multiplyFunction $ phpunit FunctionTest
PHPUnit 3.4.5 by Sebastian Bergmann.
 
.
 
Time: 0 seconds, Memory: 7.00Mb
 
OK (1 test, 1 assertion)

Looks good! So: test fulfilled – function finished?

Needles to say that the function does not nearly what anyone would expect it to do. That brings me to one of the base principles of unit testing: The tested code is only as good as the tests that cover it.
If you write insufficcent tests, your tested code will be insufficcent. Tested but insufficcient!

So lets continue testing:

Until now I expect the function to return 4 when multiplying 2 and 2. What happens, if we multiply 3 and 3? We would expect the funtion to return 9. So I’m going to extend the test to cover that as well.

	public function testFunctionReturnsExpectedResult() {
		$result = multiply(2,2);
		$this->assertEquals(4, $result);
		$result = multiply(3,3);
		$this->assertEquals(9, $result);
	}

When I now run the test, It will fail, as it can not assert, that 4 is equals to 9. So I have to adapt the function in the following way:

function multiply($a, $b){
	return $a * $b;
}

So I test it again and everything works out as expected. Testing is successfull.
Am I finished?
Well, I could say so. The function does what it shall do. Take two values and multiply them. I know and you know, that the values should only be numerial values. But does the strange guy next door know that?

So let’s just make the thing a bit more robust.

Again I start off with writing a second test to simulate the stupidity of users.

	public function testFunctionWorksWithNumbersOnly() {
		$this->setExpectedException('InvalidArgumentException');
		$result = multiply('two','two');
		$result = multiply(2,'two');
		$result = multiply('two',2);
		$result = multiply(false,2);
		$result = multiply(2,new stdObject() );

	}

So running the tests against my function will fail, as I told the test to expect an InvalidArgumentException as soon as a value other than a numeric one is given. For that I called the function with strings, boolean values and even an object.

So I have to implement that feature now into my function the following way.

function multiply($a, $b){
	if(!is_numeric($a)){
		throw new InvalidArgumentException( 'The first value has to be a numeric value');
	}
	if(!is_numeric($b)){
		throw new InvalidArgumentException( 'The second value has to be a numeric value');
	}
	return $a * $b;
}

And now the result of running the test should look something like that:
beteigeuze multiplyFunction $ phpunit FunctionTest
PHPUnit 3.4.5 by Sebastian Bergmann.
 
..
 
Time: 0 seconds, Memory: 7.00Mb
 
OK (2 tests, 4 assertions)

So both of my tests with 4 assertions and the expected Exceptions succeeded.

So now I can say, the function does what I expect it to do under all tested circumstances. And let’s recap the requirements:

I have to write a function, not a method.
Yep! But we should think about that one later on!
The function takes two parameters, not more not less.
Yep! Not tested, but PHP will whince, if the function is called with less parameters and any excess parameters will not be used.
The function returns the result of the multiplikation of the two values.
It does so, if the input is somewhat making sense. That is the case with numeric values, but other value-types will result in throwing an exception, as I can not say, what the user expects the function to do.

So. Task fullfilled I’d say.

Conclusion:

So why the hassle with writing tests and stuff? Most of the time developers that use unit-testing are confronted with the same questions over and over again:

Q: The whole thing could have been written much faster without the tests
A: Yes, that is correct. And especially in this rather simple case. But would you have sworn that the function does what you or someone else expects? Your Auftraggeber would have had only your word, that the function does what he paid you to do. Now you have proof! You can show that the method is tested. And in a short while I will show you how to visualize that also.
Q: Isn’t unit-testing in PHP-Development a nice thing that only a tiny fraction of developers do?
A: Yes and No. Lots of People that hack something together in PHP do not use unit-testing. Most of them don’t even know that there is such a thing as unit-testing. But in professional development you won’t come around it as it gives everyone a comfortable feeling about the code-quality. And together with some other Quality-Assurance-Tools unit-testing helps in maintaining a code-standard that makes it easy to maintain large-scale developments without loosing the grip.
Q: Unit-testing is only necessary for large-scale development.
A: Why do you think so? Unit-testing makes it easy for you to forget about what you need to change at 25 different places when you do a change at a specific code-part. Well you WILL have to do the changes, but by running a unit test after your change, you will see immediately where your change has impact, as your tests will fail and tell you where you have to have a look. ANd the best thing is: Unit-testing will even show you the tree places you have to adapt also you already had forgotten about. And that is a nice feature even in a small scale project. Besides, how do you define ‘large’ and ‘small’?

Links for further reading