In the previous article, we discussed cloud based PHP web technologies, settled on Google’s App Engine for our project, and created a simple “Hello World” application to test our setup. In this article, we will discuss the PHP test based development approach we will use. While test based development can be slower than a regular development pattern, it provides a wide range of advantages, and can help you build rock solid applications.

What is Test Based Development?

Test Based Development, or Test Driven Development, is simply a different approach to software development. When many start out in software development, they design, code and build their application, and then try to test the application for errors. While this works for some projects, it does not leave the code base in a very extensible state. If you later wanted to expand on a piece of code, how can you test that these changes will not affect any other part of the application? There is no simple way!

Test Based Development is an approach where you first develop a test case for the piece of the application you are working on, and you then build your code in order to pass the test. Let’s look at a quick example. If we were writing a class to handle information about our users, instead of jumping in and coding our Users class, we would first develop a test case for the class. So that we can develop a set of tests, or specifications that our class must pass.

To start, let’s imagine the purpose of our class is to simply store the user’s username. In PHP it is quite a simple task to write a class that stores a string and returns it to the application on request. But what do we actually need the class to do?

  1. Store the Username
  2. Return the Username

With these requirements of our class, let’s now think about some of the things we can test to ensure these 2 requirements are met.

  1. Does the class User have an attribute named Username?
  2. Can I set the Username?
  3. Can I retrieve the Username and does it match the Username I set?
  4. Does the Username only accept a string?

Test based development allows us to write executable tests for these 4 tests, which we can then run against our User class to ensure our class does everything we need it to do, and it performs as we expect it to. Once these tests are written, they can be executed against our code at any time, meaning if we ever have to change our User class, we can run the tests, and ensure the class is still behaving as expected.

PHPUnit

PHPUnit is the tool we will utilise to test our project. It provides a simple, easy to use, command line tool that can run our tests for us. PHPUnit can be installed a number of ways, however the simplest would have to be downloading a PHAR archive from phpunit.de. Once downloaded you can either install it locally in your project directory, or globally and make it executable  using the following terminal commands (mac).

chmod +x phpunit.phar
sudo mx phpunit.phar /usr/local/bin/phpunit

You can then test PHPUnit is running correctly using:

phpunit --version

Note, that when using Google App Engine, we will be using PHP 5.5 (as at March 2016). The latest version of PHPUnit requires PHP 5.6, so you will need to download the latest version of PHPUnit 4, which will also be listed on the downloads page.

Once you have PHP unit installed, and working, we can begin looking at setting up our project for testing. This first task we will tackle, is using composer to autoload our classes, as this will save a great deal of work including all the relevant files we will need. To install composer, simply follow the instructions at getcomposer.org, we will be using composer to install some additional libraries later, so getting familiar with composer now would be a good idea.

To autoload any classes we make, we can add a classmap to the composer.json file. If you do not currently have a composer.json file in your project directory, you can create one using the composer installer, or simply create a file and call it composer.json. Within this file, we need to add a classmap which will allow composer to find and autoload our classes. Currently, I have a project structure that looks like buzilog/library/core/class, with the class folder storing all of my classes. At a later stage, we will most likely have class files in a different location as well, and we can simply update our composer.json file to accommodate the new class locations. Currently, we will need to add our one class directory to composer.json as shown below.

"autoload": {
    "classmap": [
	"library/core/class"
    ]
}

You can now run composer’s install command which will generate a vendor directory and a vendor/autoload.php file. It is this autoload.php file we need to include to enable all our classes to autoload. There are 2 approaches to this, you can have an

 include('vendor/autoload.php');

in each test file, or we can use phpunit’s configuration file to bootstrap the file to each request. Let’s use this approach, as we then have no need to include additional files in each of our tests. To create a PHPUnit configuration file, in the root directory of your project create a file called phpunit.xml, and add the following lines to the file.

<phpunit bootstrap="vendor/autoload.php" colors="true">
</phpunit>

We have now added our vendor/autoload.php file as a bootstrap in the phpunit.xml file. Each time PHPUnit is run, it will now first run the vendor/autoload.php file, allowing all of our classes in the library/core/class directory to autoload. The colors=”true” attribute, simply highlights the results of our tests in terminal, with red showing a failure, and green showing a success.

We now have PHPUnit installed, and we have composer helping us with autoloading of our classes, so we can now begin writing a test case for our User class.

Writing our Test Case

First, we need a file to contain the tests for our User class. the PHPUnit convention for test files is to name the file with the class name, with Test appended to the end. In our case, we will name the test file UserTest.php, and store it in a directory named tests in our project directory. We then write a class in this file named UserTest, which extends PHPUnit_Framework_TestCase, giving us access to the features of PHPUnit’s TestCase.

<?PHP
	class UserTest Extends PHPUnit_Framework_TestCase{

	}
?>

We identified 4 tests we wanted to perform on our User class, to ensure we are able to store a username, and retrieve the username from our User class.

  1. Does the class User have an attribute named Username?
  2. Can I set the Username?
  3. Can I retrieve the Username and does it match the Username I set?
  4. Does the Username only accept a string?

In each of these tests, we are going to need an instance of our User class. We could create one within each test, or we can create an instance that we can share between all tests. PHPUnit allows us to do some work before testing in a setUp() function. Let’s use it to create an instance of out User class.

<?PHP
	class UserTest extends PHPUnit_Framework_TestCase{

		public $TestUser;

		public function setUp(){
			$this->TestUser = new User('mattpresland');
		}
	}
?>

You can see above, that we have created a public attribute named TestUser, and in our setUp function we have created a new User object, and passed in the Username mattpresland. You may not wish to set your username this way in production, however it will serve our purposes here. We can now write our test cases for the 4 tests we identified earlier.

<?PHP
	class UserTest extends PHPUnit_Framework_TestCase{

		public $TestUser;

		public function setUp(){
			$this->TestUser = new User('mattpresland');
		}

		public function testUserHasUsername(){
			$this->assertClassHasAttribute('Username', 'User', 'The Class User does not have an attribute named Username');
		}

		public function testUsernameSet(){
			$this->assertAttributeNotEmpty('Username', $this->TestUser, 'Username is Empty');
		}

		public function testGetUsername(){
			$this->assertEquals('mattpresland', $this->TestUser->getUsername(), 'Username returned from getUsername does not match the expected Username');
		}

		public function testThrowExceptionForNonStringUsername(){ 
			$this->setExpectedException('InvalidArgumentException');
			$TestUser = new User(1234);
		}
	}
 ?>

Each test function name must begin with “test” in order for PHP unit to find it and run it as a test. The rest of the function name should be very descriptive in order to help you identify the errors when testing. If you named your tests test1, test2, test3….test3051 and you had an error arise from test156, it would mean nothing to you. However, if we had an error from testGetUsername(), we instantly know that we have an error retrieving our username.

Let’s break down what each of our tests is doing.

public function testUserHasUsername(){
    $this->assertClassHasAttribute('Username', 'User', 'The Class User does not have an attribute named Username');
}

Here we have created a function named testUserHasUsername(). Here we are testing to ensure that the User class has an attribute named Username that can be used to store our data. We have used PHPUnit’s assertClassHasAttribute() function to test our User class. The first parameter we passed was the name of the attribute we are looking for, in our case Username, the second was the name of the class User, and then we can provide a message should our test fail. Again, it is wise to add a descriptive message, to assist you in debugging when a test fails. You can find all of the possible assert function for PHPUnit in the documentation at phpunit.de.

public function testUsernameSet(){
    $this->assertAttributeNotEmpty('Username', $this->TestUser, 'Username is Empty');
}

Here, we are testing that the Username attribute is not empty. We set the attribute in our setUp function when we created an instance of our User class, so let’s ensure that the attribute is no longer empty. To achieve this, we used the assertAttributeNotEmpty() function, and passed in the name of the attribute we are testing, our instance of the User class, and we set a message to help us if the test fails.

public function testGetUsername(){
    $this->assertEquals('mattpresland', $this->TestUser->getUsername(), 'Username returned from getUsername does not match the expected Username');
}

Our third test, was to ensure we could retrieve the Username from our User class. In our class we are going to use a getUsername() function to return the User’s Username. We can test the value of the returned Username using assertEquals, and passing in our expected result, which was “mattpresland”, and our actual value from $this->TestUser->getUsername(), and we have again set a message to help us debug.

public function testThrowExceptionForNonStringUsername(){
    $this->setExpectedException('InvalidArgumentException');
    $TestUser = new User(1234);
}

Our last test was to ensure we handle the cases where a Username is passed to our User class that is not a valid sting. We only want strings to be allowed, so we need to ensure that an exception is thrown is a non-string is detected. We do this by setting an expected exception, in this case we will set an InvalidArgumentException. To test this, we will then create a new user, with an integer provided for the username, if this test is to succeed, this should result in an exception being thrown from our User class.

Running the Test!

We have now completed our test case for our User class. We can run it in terminal, by calling

phpunit tests/UserTest.php

Our test failed! We had a fatal error stating our class could not be found. We have not yet written our class, just our test case, or our target specification for our class. So let’s start our class now! We need to create a new file called user.class.php in our library/core/class directory. Let’s define our class. And then run our test again.

<?PHP
    class User {
        
    }
?>

You will see we still get an error stating that our User class does not contain a getUsername() method. Lets fix this by adding the getUsername method, and testing again.

<?PHP
    class User{
	
		public function getUsername(){

		}
	
    }
	
?>

You can see above, that our tests are now running, as we now have the class end the required functions in place. We are however getting 1 error and 3 failed assertions. Let’s start by correcting the error. We are getting a PHPUnit_Framework_Exception, which tells us the attribute “Username” is not found in the object. Lets add the Username attribute declaration to our User class and see what happens.

<?PHP
    class User{
 
        private $Username;
 
        public function getUsername(){
 
        }
 
    }
?>

We are now starting to make some progress! We now have no errors, but we still have 3 failed assertions. Our first failed assertion is telling us that Username is Empty. And if you look at our User class, we have not set the username! We know that in this class, we want to be able to set the username when we create an instance of our class. So let’s go and add a __construct method that sets our username when the instance is created.

<?PHP
    class User{
	
		private $Username;

		public function __construct($Username){
			$this->Username = $Username;
		}

		public function getUsername(){

		}
	
    }
?>

After testing again, we can see we now only have 2 assertions failing. The first one tells us that “Username returned from getUsername does not match the expected username. If we look back to our class, we haven’t yet allowed our method to return the username. Let’s do that and re-run our test.

<?PHP
    class User {
        private $Username;

        public function __construct($Username){
            $this->Username = $Username;
        }

        public function getUsername(){
            return $this->Username;
        }
    }
?>

We now have only 1 failed assertion left, and it comes from our testThrowExceptionForNonStringUsername test. In this test, we want an exception to be thrown any time a user is created and the username provided is not a string. Let’s implement this in our User class construct method.

<?PHP
    class User {
        private $Username;

        public function __construct($Username){
            if(!is_string($Username)){
                throw new InvalidArgumentException('Username is not a valid string!');
            }
            $this->Username = $Username;
        }

        public function getUsername(){
            return $this->Username;
        }
    }
?>

We have now passed all our tests! If you were running these tests in a real scenario, you would need to test with multiple sets of possible usernames, and pass in an example of each type of data to ensure exceptions were working as expected. Passing all of these tests means that our class now satisfies the requirements we set for this class. Using PHPUnit, you can now get a list of our class’s abilities and limitations! It’s nearly like documentation for your class, or a specification of your class. It works using the names of your test functions. To see this, you can run phpunit using the –testdox option.

phpunit --testdox tests/UserTest.php

This is a pretty nice feature that shows our User class passes our tests and shows the test specifications of the User class. If a test was to fail when running generating this information, the checkbox would show as empty. You can use this information to compare to your original design specification, and can even show this information to a client or non-technical person so they can understand what our class can and cannot do. This can help with clarification on project requirements etc. You will notice that the items listed above are good, but they could be named a little nicer to produce a more readable result for documentation. I will go ahead and re-name my functions now, so they are more readable in this format, but also useful for testing.

The test case, and our User class in this example were drawn out a little to illustrate the concept of test based development. In reality, once you have designed a test case, you can design and build your class much quicker than we did here. The advantage of the test based approach, is that no matter how we change our application, or our User class, we can simply run our phpunit test and instantly ensure we have not broken any functionality. For example, what if we wanted to look up our username from a database, instead of setting it in our constructor? We can implement the database lookup, and run our test case to ensure everything is still working in the same manner.

In a real project, you can also set up test suites with PHPUnit, to test modules and components of your project. you can also run all test units at the same time, by simply specifying the directory that contains the tests. In our case, you could just run

phpunit tests/

to run all of the project tests. If you are making big changes on any given day, or implementing new features, this makes it incredibly easy to ensure your new modifications do not affect any other part of your application. PHPUnit also has many other features, some of which we will see as we further develop this project, and others that are available in the PHPUnit documentation.

Next Steps

Today we have briefly touched on Test Based Development, using PHPUnit for our testing. In the next article, we will look at some of the frameworks available for PHP development, and when and why you would want to use a framework. We will also look at some of the features we will need moving forward with the development of our application.