
Writing PHP Unit test friendly code
This is not another article telling you why should you use unit tests and it’s advantages.
These are just some “bullet” points on how to write (PHP) code that will make your life easier when you have to write the tests. It’s not an absolute guide and all the concepts are presented on a high level approach. Most of the concepts are language agnostic!
So, cut to the chase.
Dependency injection
The use of dependency injection does not only allow you to write better tests, it will also make your codebase more maintainable over the time.
Let’s look at quick (bad) code example. Imagine we are developing a software that manages table reservations for restaurants. For sake of simplicity, we will show only constructors of the Client and Reservation classes.
Code Example 1class Client
{
private $firstName;
private $lastName;
public function __construct($firstName, $lastName)
{
$this->firstName = $firstName;
$this->lastName = $lastName;
}
// other method declarations would go here
}class Reservation
{
private $numberOfPersons;
private $client;
public function __construct($numberOfPersons, $firstName, $lastName)
{
$this->client = new Client($firstName, $lastName);
$this->numberOfPersons = $numberOfPersons;
}
// other method declarations would go here}
This short code example is enough to demonstrate the problem: If you want to test the Reservation class, you will have to test the Client class and, with this, your Reservation tests may fail due to errors on the Client class.
This will also hurt code maintainability. Client is tightly coupled with the Reservation. So, if you change the Client constructor signature you would also need to change the Reservation class.
So, a better example of the Reservation constructor would be:
Code Example 2public function __construct($numberOfPersons, Client $client)
{
$this->client = $client;
$this->numberOfPersons = $numberOfPersons;
}
This way, in your tests, you can create a mock object of the Client class and you will be testing only the Reservation class as it should be!
This same principle should be applied to global variables. If you use global variables inside any of your classes you will be coupling variables on your application and you will be running into problems during testing because you will have external dependencies that you will also need to test and guarantee they are present.
The global keyword should not be used, and once again you should inject any dependencies from the outside of your methods and not the other way around.
So, wrapping it up, avoid coupling as much as you can, this will make your code more testable, you will use mock objects, and will also save you a lot of trouble in future when you need to refactor (and you know you will need to do it).
Each function has one function
Functions should do one thing. They should do it well. They should do it only.
If you respect Single Responsibility Principle, you will know exactly what you are testing. Your functions will be smaller and your codebase simpler.
Here’s some of the problems you face when a function does a lot of stuff:
- The function can have side effects you wouldn’t assume by it’s name
- The function code get’s too complex and hard to maintain over time
- There’s an high probability that some of the functionality there would be repeated somewhere in the code because you don’t have a separate method to do it (see DRY principle)
- And, more important for the context of this article, it will be more difficult to write unit tests for it because it will be harder to evaluate what happens inside that function
So let’s look at another bad code example of a method on the Reservation class:
Example code 3public function saveReservation()
{
$this->idClient = $this->client->save();
$this->save();
$this->email->sendEmail();
}
This function clearly does more than one thing. When you call the method saveReservation, it is also saving the Client and sending an email.
So, in fact, when you test this method you will be testing more stuff than the saving of the reservation. You will also be testing saving a Client and sending an email. Even if you correctly injected all the dependencies necessary for these actions, the effort to write tests is much bigger because of the increased complexity of the method.
Functions should have a minimum number of arguments
The more arguments your methods have, the more complex will be the test because you will have to test all the possible combinations of things that might change in with each one of that arguments combinations.
Code example 4class Reservation
{
private $numberOfPersons;
private $client;
private $db;
public function __construct(Database $db)
{
global $db;
}
public function setClient(Client $client)
{
$this->client = $client;
}
public function setNumberOfPersons($numberOfPersons)
{
$this->numberOfPersons = $numberOfPersons;
}
// other method declarations would go here
}
On Code Example 4. Each method has only one function, the number of arguments is small and no external coupling.
Do not access superglobals directly
The same way you should not use global variables, you should not access directly superglobals like the common $_SESSION, $_GET, $_POST, etc. because if you do it, you will have to guarantee that all the necessary values are available during your tests. This, off course, will add complexity to the task of writing tests.
For example, if you access directly to $_SESSION, you will have to initialise it during your tests and set all the necessary values.
A better way would be directly inject the necessary values on your methods or use a session helper class that you can mock for testing.
Final words
This is should be a good principle to make writing tests easier and improve software quality. Do you have more tips? Please leave them on comments.