January 20, 2016

1432 words 7 mins read

Getting started with PHPUnit

PHPUnit is a testing framework that can do a lot of things all related to testing and reports about our code. Keep in mind this is an introduction and hopefully, the first part of a series about testing. Mocking, coverage, annotations and reports are concepts that will not be introduced here.

PHPUnit is mainly famous for being the standard way to write unit tests for PHP applications, of course there are other types of tests that PHPUnit can handle but here we’ll just look at unit tests, that means having a test (or multiple) for every one of our functions. A common approach to structure tests in our project is to mirror the src directory structure in the tests folder, whenever I start a PHP project I setup the following directory structure:

  • src/
  • tests/
  • phpunit.xml
  • composer.json

The src folder is where all of our application code lives, inside this folder we use the PSR-4 standard to organize our namespaces.

Of course the tests folder is where all our test code lives, as I’ve said, this folder mirrors the structure of the src folder, but all the tests use the same namespace that the application code uses, some people prefer to have them in a separated namespace and that’s fine, it’s just a matter of preferences.

The phpunit.xml file is where we should store all configurations, flags and arguments that I want to pass to the phpunit command, we’ll see more about this in short.

As of the composer.json I’ll not explain what it is, if you’re reading this it’s very likely you already know about Composer.

Making a simple Json parser app

It’s better to explain testing on a example, so we’re going to build a simple app that can parse an object to json (serialize).

Let’s require phpunit in our project:

$ composer require phpunit/phpunit --dev  # Pull phpunit for 'dev' environment only

Inside our composer.json let’s define our namespace, following the best practices we need a vendor name (Antares will do just fine) and a package name (I’ll choose Serializers). Remember that the vendor is something unique that will identify all of your packages, as a popular rule you should use your Github username or your domain name.

Ok, let’s add the autoload object:

...
"autoload": {
  "psr-4": {
    "Antares\\Serializers": "src/"
  }
}
...

Note: Every time you add a new class or change the namespace definition run composer dump-autoload to refresh the autoloader.

With that we just tell composer that the code inside src should be under the Antares\Serializers namespace.

Now put this in to the phpunit.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit
    colors="true"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    processIsolation="false"
    stopOnFailure="false"
    syntaxCheck="false"
    bootstrap="tests/bootstrap.php">
    <testsuites>
        <testsuite name="Antares serializers Test Suite">
            <directory>tests/</directory>
        </testsuite>
    </testsuites>
</phpunit>

As I said earlier, this file passes options to be used by default when running phpunit. The main thing to grasp here is the <testsuite> tag definition, this tells phpunit that our test files are located in the tests/ directory, thanks to this we don’t have to specify the folder as an argument every time we run the tests.

Another important definition in this file is the bootstrap attribute, this tells to phpunit to run that file to setup our test suite, and as the name implies the code in that file will serve as a bootstrapping mechanism.

Let’s create this tests/bootstrap.php file now and put this in there:

<?php
// tests/bootstrap.php

// Get the path to the autoload file
$autoload = dirname(__DIR__) . '/vendor/autoload.php';

// The autoload file only exists if "composer install" was run
if (! file_exists($autoload)) {
    exit("Please install project by running:\n\tcomposer install\n\n");
}

// Get the loader by including the autoload file
$loader = include $autoload;

// Add the tests directory to the "Antares\Serializers" namespace
$loader->addPsr4('Antares\\Serializers\\', __DIR__);

As you can read in the comments all we do in this file is add our tests directory to the Antares\Serializers namespace. This will only happen when running the tests since it’s a bad practice to have our tests loaded in our application namespace when we are not developers testing the code (i.e. when the application is in production mode).

Since we’re using TDD lets define our expectations in a test file, go ahead and create the tests/JsonSerializerTest.php file, with the following code in it:

<?php

namespace Antares\Serializers;

class JsonSerializerTest extends \PHPUnit_Framework_TestCase
{
}

The key point here is that all test classes along with the test filename must end with the word Test, better yet if we use the same name in both the test file and the test class. At this point see what happens when we run phpunit:

$ vendor/bin/phpunit

Sample output:

PHPUnit 5.1.4 by Sebastian Bergmann and contributors.

W 1 / 1 (100%)

Time: 42 ms, Memory: 3.25Mb

There was 1 warning:

  1. Warning

No tests found in class “Antares\Serializers\JsonSerializerTest”.

WARNINGS! Tests: 1, Assertions: 0, Warnings: 1.

We can see no tests were found. Tests are just functions defined inside a TestCase class, let’s write our first test. The idea is simple, we’re going to create an instance of the JsonSerializer class (which at the moment isn’t defined) and we’ll call the serialize method on that instance expecting to pass an array and getting back a json representation of that array, this is the behaviour we’re going to test.

Inside the test class write the following function (which we’ll be considered a test):

<?php

namespace Antares\Serializers;

class TestCase extends \PHPUnit_Framework_TestCase
{
    public function testSerialize()
    {
        // Create a JsonSerializer instance
        $jsonSerializer = new JsonSerializer();

        // Define a sample array for testing the serializer
        $subject = [
            'popular-languages' => [
                'javascript', 'php', 'ruby', 'python'
            ]
        ];

        // Define how the string should look after serialization
        $jsonSubject = '{"popular-languages":["javascript","php","ruby","python"]}';

        // Serialize the sample array
        $serialized = $jsonSerializer->serialize($subject);

        // Ensure the serialized array looks equals to the $jsonSubject string
        $this->assertEquals($jsonSubject, $serialized);
    }
}

We should note a couple of things here, first, any test we’ll write is a function whose name should start with the test word, camelCase is preferred in the PHP community so testSerialize is a valid name, more over serialize is the name of the method we want to test so it makes sense to name our test this way. Second, PHPUnit includes a lot of defined assertions, here we only use assertEquals but I encourage you to take a look in the assertions page.

If you run the test of course it will fail because the JsonSerializer class doesn’t exist just yet, let’s create that class in our src folder.

<?php

// src/JsonSerializer.php

namespace Antares\Serializers;

class JsonSerializer {}

Run phpunit again, and it will fail again, now it doesn’t find the serialize method, lets add it:

<?php

namespace Antares\Serializers;

class JsonSerializer
{
    public function serialize($structure)
    {
        //
    }
}

Tip: You can temporary alias vendor/bin/phpunit to t with the command alias t="vendor/bin/phpunit". From now on just type t to run the tests.

Once more run the test, and we see the first failing test:

PHPUnit 5.1.4 by Sebastian Bergmann and contributors.

F 1 / 1 (100%)

Time: 31 ms, Memory: 3.75Mb

There was 1 failure:

  1. Antares\Serializers\TestCase::testSerialize

Failed asserting that null matches expected ‘{“popular-languages”:[“javascript”,“php”,“ruby”,“python”]}’.

_Users_antares_demo_tests/JsonSerializerTest.php:21

FAILURES! Tests: 1, Assertions: 1, Failures: 1.

Notice the format of the message “Failed asserting that x matches expected y” this tells you about the order of the arguments in the assertions. In PHP, all functions return something, if a return statement isn’t explicitly declared then null will be returned implicitly.

The easy way to make the test pass is to return the desired string:

// src/JsonSerializer.php
public function serialize($structure)
{
    return '{"popular-languages":["javascript","php","ruby","python"]}';
}

Now the test passes but this is a dummy solution, lets add another test to avoid this solution:

// tests/JsonSerializerTest.php
public function testSerialize()
{
    $jsonSerializer = new JsonSerializer();

    $subject = [
        'popular-languages' => [
            'javascript', 'php', 'ruby', 'python'
        ]
    ];

    $subject2 = ['friday', 'saturday', 'sunday'];

    $jsonSubject = '{"popular-languages":["javascript","php","ruby","python"]}';

    $serialized = $jsonSerializer->serialize($subject);
    $weekends = $jsonSerializer->serialize($subject2);

    $this->assertEquals($jsonSubject, $serialized);
    $this->assertEquals('["friday","saturday","sunday"]', $weekends);
}

This extra assertion will cause our test to fail, so lets fix the serialize function to make it pass for both assertions.

// src/JsonSerializer.php
public function serialize($structure)
{
    return json_encode($structure);
}

Run the test for the last time (in this exercise) and now it passes.

PHPUnit 5.1.4 by Sebastian Bergmann and contributors.

. 1 / 1 (100%)

Time: 26 ms, Memory: 3.50Mb

OK (1 test, 2 assertions)

Ok so for the time being I think this is enough to get you started with PHPUnit, I hope to make a follow up in the future, but you never know.

I encourage you to dive in the PHPUnit documentation to know all of the assertions available in the framework.