Writing isolated unit tests



Writing isolated unit tests


A guide to writing unit tests


Introduction

When it comes to writing good unit tests it is good to realize that a unit test should test the unit and nothing more. In our case a “unit” would be a class. The class we want to test is called the “class under test” or “CUT” for short.

You can imagine that when our application grows the number of unit tests will grow as well, the main reason is because we will have more classes. In order to keep the test suite running fast and efficient we will need to make sure not to test the entire framework together with our own logic. Most frameworks will have their own test suite and this is something we have to rely on. We do not want to test code we have not written.

In order to do this we will need to follow SOLID design principles and use mocking.

SOLID

When following these principles we will have classes that have a single purpose, use interface segregation and use dependency inversion. Of course the Open/closed and Liskov principles are important too, but not directly relevant to this subject.
Just to refresh your memory we will go trough the necessary principles and explain why they are important for writing proper test code.

Single purpose


A class with a single purpose is important because we chose to work in an Object Oriented environment. We chose this way of working in part because of its modular structure. It enables us to easily re-use or replace parts of the code in the application.
Another advantage is that the logic in our classes will be relatively simple, which means we are conforming to the KISS principle (Keep It Simple Stupid). This makes the code in the application or module easy to understand and easy to use.
A single purpose class will, as the name implies, in most cases have a simple interface.
For example:

interface StringValidatorInterface
{
     /**
     * Method that validates that a variable is a string
     * @return boolean
     */
    public function validateString();
}


The interface states that there will be classes capable of validating that a variable is of primitive type “string” and nothing more.

A possible implementation of this interface could be:

class NativeStringValidator implements StringValidatorInterface
{
    /** @var mixed */
    private $value;

    /**
     * Constructor
     * @param mixed $value The value to be validated
     */
    public function __construct($value)
    {
        $this->value = $value;
    }

    /**
     * {@inheritDoc}
     */
    public function validateString()
    {
        return is_string($this->value);
    }
}

Interface Segregation

As can be seen in the section above, we have an interface and an implementation (class). The main reason for this is to avoid our business logic from depending on the implementation. Our business logic does not have anything to do with how we validate a string value, it just needs to somehow be able to validate such values.
An added advantage is that we can easily swap the implementation for another one. For example an implementation that uses hamcrest:

class HamcrestStringValidator implements StringValidatorInterface
{
    /** @var Hamcrest\Type\IsString */
    private $matcher;

    /** @var mixed */
    private $value;

    /**
     * Constructor
     * @param Hamcrest\Type\IsString
     * @param mixed $value
     */
    public function __construct(IsString $matcher, $value)
    {
        $this->matcher = $matcher;
        $this->value = $value;
    }

    /**
     * {@inheritdoc}
     */
    public function validateString()
    {
        return $this->matcher->matches($this->value);
    }
}


So when our code relies on the interface in stead of the implementation we are not bound to a specific class or module. This is called "loose coupling".
Loose coupling has many advantages. For example imagine we write some business logic and at some point we would like it to work in a different framework, with loosely coupled business logic all we have to do is provide our code with new implementations of the already defined interfaces.

Dependency Inversion


When we need a new instance of a class we could just instantiate one by using the new keyword. Doing this in our business logic has drawbacks. One of which being that when we want to test our class we will always test the dependencies as well. As mentioned earlier this could mean that we are testing large parts of the framework we are using as well as the code we actually want to test.
These dependencies should have their own tests and as such we should be able to replace them with “test doubles” in our tests. We will discuss the use of test doubles later.
For now it is important to know that in order to test our implementations we need to use dependency inversion to be able to test our logic in isolation.
So let’s imagine we have some business logic we need to test (at some point our logic needs to verify that a variable is of primitive type string):

class SomeBusinessLogic implements SomeBusinessLogicInterface
{
    /**
     * Const
ructor
     */
    public function __construct()
    {
    }

    /**
     * Method that executes the business logic
     * @throws Exception
     */
    public function executeLogic()
    {
        ...
        $stringValidator = new NativeStringValidator($value);
        if ($stringValidator->validateString() === false) {
            throw new Exception('Value is not of primitive type string!');
        }
        ...
    }
}



Instantiating the object in this way binds our logic with the NativeStringValidator implementation. So if we want to changed this in the future we would have to modify the logic itself. When we want to test this class, we have no other option but to test the NativeStringValidator as well. Even though this class will probably have its own test.
It would be better to “inject” this dependency into the logic:

class SomeBusinessLogic implements SomeBusinessLogicInterface
{
    /** @var Scuti\SampleCode\StringValidatorInterface */
    private $stringValidator;

    /**
     * Constructor
     * @param Scuti\SampleCode\StringValidatorInterface
     */
    public function __construct(StringValidatorInterface $stringValidator)
    {
        $this->
stringValidator = $stringValidator;
    }

    /**
     * Method that executes the business logic
     * @throws Exception
     */
    public function executeLogic()
    {
        ...
        if ($this->stringValidator->validateString() === false) {
            throw new Exception('Value is not of primitive type string!');
        }
        ...
    }
}

The advantage of this should be fairly obvious. We have our loose coupling in place (because we now rely on the interface) and we have inverted our dependency. Our constructor now requires an instance of an object implementing the StringValidatorInterface.
However our string validator can only be instantiated if we already have the value that needs to be tested. This could pose a problem because maybe the value will only be available after executing part of the logic.
There are two solutions to that problem. One of them is designing the string validators in such a way that they can be reused (by moving the parameter $value from the constructor to the method defined by the interface). The other is using a "factory" that produces validators and allows you to pass the value to the factory method:

class NativeStringValidatorFactory implements StringValidatorFactoryInterface
{
    /**
     * Produces a NativeStringValidator for the specified value
     * @param mixed $value
     * @return NativeStringValidator
     */
    public function getStringValidator($value)
    {
        return new NativeStringValidator($value);
    }
}

Our business logic class would look something like:

class SomeBusinessLogic implements SomeBusinessLogicInterface
{
    /** @var Scuti\SampleCode\StringValidatorFactoryInterface */
    private $stringValidatorFactory;

    /**
     * Constructor
     * @param Scuti\SampleCode\StringValidatorFactoryInterface
     */
    public function __construct(
        StringValidatorFactoryInterface $stringValidatorFactory
    ){
        $this->stringValidatorFactory = $stringValidatorFactory;
    }

    /**
     * Method that executes the business logic
     * @throws Exception
     */
    public function executeLogic()
    {
        ...
        $stringValidator =
        $this->stringValidatorFatory->getStringValidator($value);
        if ($stringValidator->validateString() === false) {
            throw new Exception('Value is not of primitive type string!');
        }
        ...
    }
}


This is a lot of work you might think, before you can even use the business logic you need to set up and instantiate all dependencies, which sounds like it is a lot of hassle. However let's think about it. Is it really such a hassle? Would we be able to use the class without these dependencies? The dependencies are needed anyway, but injecting them makes for transparant situation. We can actually see what our class needs by looking at the constructor.
Most modern frameworks offer solutions to make this easier for us. Often they allow us to somehow specify what objects to inject and take care of injecting them for us when we need them to.

Writing the unit tests


So far we have discussed how using certain SOLID principles can greatly help us out in writing testable classes. But what would the actual tests look like? And what the test doubles we mentioned earlier?
Suppose we want to test the NativeStringValidator. After all we are going to use it in our business logic so we need to make sure it will actually do what we expect it to. The unit test for this class could look something like:

class NativeStringValidatorTest extends TestCase
{
    const TEST_STRING = 'some string value';

    public function testCanInstantiate()
    {
        $validator = new NativeStringValidator(self::TEST_STRING);
        $this->assertInstanceOf(
            'Scuti\SampleCode\StringValidatorInterface',
            $validator
        );
    }

    public function testWillValidateString()
    {
        $validator = new NativeStringValidator(self::TEST_STRING);
        $this->assertTrue($validator->validateString());
    }

    public function testWillInvalidateInteger()
    {
        $validator = new NativeStringValidator(1234);
        $this->assertFalse($validator->validateString());
    }
}

Surely there are more tests we can think of, but this will do for now. It is a straight forward test class and let's assume that it covers what we want it to cover.Although this class is quite small, it actually tests some internals of PHP as well. The implementation uses the is_string() function remember? Because this is such a small operation it is not really worth trying to replace this part with some sort of test double, so in this case it might be okay to test a small part of the PHP internals along with the class.

Test Doubles


In the last section we have discussed how we don’t really mind testing a small part of the PHP internals along with our class. The situation might be different when we would write tests for the HamcrestStringValidator, which uses the hamcrest package. The hamcrest package has its own tests and we should be able to rely on this fact.
A basic test for the HamcrestStringValidator could look very similar to the NativeStringValidatorTest:


class HamcrestStringValidatorTest extends TestCase
{
    const TEST_STRING = 'some string value';
    private function getIsString()
    {
        return new IsString();
    }

    public function testCanInstantiate()
    {
        $validator = new HamcrestStringValidator(
            $this->getIsString(),
            self::TEST_STRING
        );
        $this->assertInstanceOf(
            'Scuti\SampleCode\StringValidatorInterface',
            $validator
        );
    }

    public function testWillValidateString()
    {
        $validator = new HamcrestStringValidator(
            $this->getIsString(),
            self::TEST_STRING
        );
        $this->assertTrue($validator->validateString());
    }

    public function testWillInvalidateInteger()
    {
        $validator = new HamcrestStringValidator($this->getIsString(), 1234);
        $this->assertFalse($validator->validateString());
    }
}


While this would do the trick, we are also testing the hamcrest class IsString. In order for our test suite to keep running fast and isolated we need to use a test double:

class HamcrestStringValidatorTest extends TestCase
{
    const TEST_STRING = 'some string value';
    const TEST_INTEGER = 12345;

    private function getIsString($expectations = false)
    {
        $mock = $this->getMockBuilder('\Hamcrest\Type\IsString')
                     ->getMock();

        if ($expectations) {
            $map = [
                [self::TEST_STRING, true],
                [self::TEST_INTEGER, false]
            ];

            $mock->expects($this->once())
                 ->method('matches')
                 ->will($this->returnValueMap($map));
        }

        return $mock;
    }

    public function testCanInstantiate()
    {
        $validator = new HamcrestStringValidator(
        $this->getIsString(),
            self::TEST_STRING
        );
        $this->assertInstanceOf(
            'Scuti\SampleCode\StringValidatorInterface',
            $validator
        );
    }

    public function testWillValidateString()
    {
        $validator = new HamcrestStringValidator(
            $this->getIsString(true),
            self::TEST_STRING
        );
        $this->assertTrue($validator->validateString());
    }

    public function testWillInvalidateInteger()
    {
        $validator = new HamcrestStringValidator(
            $this->getIsString(true),
            self::TEST_INTEGER
        );
        $this->assertFalse($validator->validateString());
    }
}

When you compare these tests you can see that what changed is mostly the getIsString method. In stead of instantiating the hamcrest class, we now call on some PHPUnit methods to retrieve a so called "mock object". By default this mock object will have all the same methods as the class we would use, but all of them will return null. It will also match any instance of checks, which is good because we are using type hints. In our case it is necessary to have them return a boolean value based on the input (parameter), so in the last two test cases we also set up some expectations. We basically tell the mock that for each instance we expect the matches method to be called once and use a return value map to determine what the mock should return.
This way we can test that our implementation does the things we are expecting it to do.

Now since we know the behavior of the class we are able to make the mock object behave the same as the original for the given values, which is all we want to test.

If you liked this article

Let's subscribe the updates of Scuti!
Share on Google Plus

About Youri Westerman

This is a short description in the author block about the author. You edit it by entering text in the "Biographical Info" field in the user admin panel.
    Blogger Comment
    Facebook Comment

0 Comments:

Post a Comment