PHPUnit

Unit test for PHP

Rafa Chacón @ Everis

Table of contents

  • Introduction
  • How PHPUnit works
    • Getting started
    • Installation from scratch
    • Some conventions
    • Create testable code
    • Testing!
    • Installation as part of FCM
  • Appendix
    • Composer
    • Mock objects, Stub methods and Mock methods
    • Dependency injection
    • PHPUnit and PHPStorm
    • Command line cheatsheet
    • Download PHPUnit Tutorial files
    • bibliography and references

Introduction

PHPUnit is a testing framework for PHP.

 

It is based on unit test, what means PHPUnit is intended to test code fragments.

 

A code fragment can be a function, method or anything that could be considered a small piece of code covering some functionality.

 

In terms of FCM, PHPUnit is not suitable for testing complete screens or functionalities but you can use it to test things like a set of vehicles are in status "sold" after some action, the prices are well calculated, the VINs are correct...

 

In order to see PHPUnit in action, this presentation covers the installation from scratch and as part of FCM.

How PHPUnit works

Getting started

First of all, we need to download and install PHPUnit as part of a new project.

 

In this section we will build a new project from scratch that will be the PHPUnit Tutorial project.

 

For this example we will use Composer. If you need to learn more about Composer, go to the Composer appendix in this presentation.

 

From now on, we will suppose that composer is installed.

Installation from scratch

Create a directory called PHPUnit in your server.

 

In this directory we are going to install PHPUnit using composer:

 

In composer.json, add this:

{
  "require-dev": {
    "phpunit/phpunit": "3.7.14"
  }
}

Update your project (yet a blank project) with composer:

$ php composer update

After this, your project will have a new directory called vendor. This directory stores external libraries like PHPUnit.

 

PHPUnit is now installed in vendor/bin/phpunit.

 

At this point you can execute PHPUnit in your project:

$ .\vendor\bin\phpunit

Since you don't have any test created yet you will see no significant results from this command.

When you are performing tests you have to tell PHPUnit about the file dependencies. Our files will be in a directory called phpUnitTutorial, so create this directory.

 

To manage the file dependencies you need an autoloader. In this example, we are going to tell PHPUnit that all the files will be in the phpUnitTutorial directory. To do this, just open your composer.json file and update it with the following:

{
  "require": {
  },
  "require-dev": {
    "phpunit/phpunit": "3.7.14"
  },
  "autoload": {
    "psr-0": {
      "phpUnitTutorial": ""
    }
  }
}

After this, just update your project with composer:

$ php composer update

At this point you should have the file vendor/autoload.php that is our autoloader for PHPUnit in this project.

 

Now, you have to configure PHPUnit in order to set where the test files are.

 

To do this, create a file called phpunit.xml in the project root, with te following:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>./phpUnitTutorial/Test/</directory>
        </testsuite>
    </testsuites>
</phpunit>

This sets the test directory in phpUnitTutorial/Test.

 

That means all test files will be stored in this directory and nowhere else.

Some conventions

A test file is just a class that follows some conventions:

 

  • The class must be named as *Test and has to inherit from PHPUnit_Framework_TestCase.
  • The methods (actually, the tests) must be named as test* and that name should be something that can be answered as yes or no, by example:
    • testNumberIsFloat
    • testMyConversorConverts
    • testMyTransformerReturnsSomethingTransformed
  • The testing methods must to have public access.

Create testable code

Ok, PHPUnit will be a plus in the project and the development will be better, more efficient and more reliable.

 

Even more, when I start a new project I should keep in mind a test driven development.

 

In order to achieve this, write your code...

 

  • Don't repeat yourself
  • Avoid dependencies
  • Use interfaces and containers

In the real world sometimes you have to write tests with PHPUnit for a project that was built without any intention of performing tests.

 

PHPUnit provides funcionality to approach this reality without too much pain but you need to understand that not everything will be tested.

Testing!

Basically, you will need a class for every separated thing you need to test and, inside this class, a method for every single test you want to make.

 

  • Don't create a single class to test everything (you can do it though)
  • Don't create a single test method (you can do it though)

 

When to perform a test? Well, ideally you have to do a 100% code coverage but this is far from reallity.

There's a common advice on what to test:

Here's our first test:

<?php

namespace phpUnitTutorial\Test;

class StupidTest extends \PHPUnit_Framework_TestCase
{
    public function testTrueIsTrue()
    {
        $foo = true;
        $this->assertTrue($foo);
    }

}

Note that this test does nothing, in the sense of the assert true will say always ok in this example.

This simple test teaches us a lot:

 

  • We're using the namespace phpUnitTutorial\Test.
  • We're extending the PHPUnit_Framework_TestCase.
  • We're using the assertTrue method from PHPUnit.

 

To test this, just go to the console and write the following:

$ .\vendor\bin\phpunit phpUnitTutorial/Test/StupidTest

This will show the results in the console.

 

Note that this will only test the class StupidTest, but not any other tests in the Test folder.

Installation as part of FCM

So, we have a brand new project built from scratch with PHPUnit and unit testing in mind.

 

What about PHPUnit inside the FCM project?

 

Well, FCM has some particularities:

 

  • FCM has its own autoloader for everything
  • FCM has A LOT of dependencies
  • FCM has no composer...
  • ... but had it at some point
  • FCM has the Zend Framework PHPUnit version...
  • ... but doesn't work very well

 

This last point is very interesting because FCM has a test directory (Tests/) where you can find some tests, one of them based on PHPUnit but if you try to execute them, they will not work, resulting in a crash.

Despite this, we can install PHPUnit in FCM:

 

  • Install composer in the FCM root directory.
  • Change the composer.json just to include PHPUnit.
  • Update the project using composer.

 

This will bring the PHPUnit package in the FCM vendor/ directory.

 

Now you can write test in the FCM Tests/ directory and execute them.

 

Note that you can write simple tests easily but, what happens when you try to test something specific from FCM?

Let's start with a quite simple test: Assert if a vehicle VIN is valid or not.

 

A VIN is valid when it is 17 chars lenght and have only letters and numbers.

 

In the FCM test folder, create a file named VehicleTest and fill it as follows:

<?php

namespace FCM\Tests;

require_once './Logic/Functions.php';

use Functions;

class VehicleTest extends \PHPUnit_Framework_TestCase
{
    public function testIsValidVIN()
    {
        $vin = '000000000003379801';
        $this->assertTrue(
			Functions::checkVIN($vin),
			"VIN $vin is not correct!\n"
		);
    }

}

In this example, the PHPUnit::assertTrue method will throw true, so the test is passed.

 

Note that we have added a message to the ::assertTrue method just to inform the VIN xxx is not correct, if the test fails.

 

Let's move with a bit more complexity: a test that accepts a lot of VIN's and check everyone of them:

<?php

namespace FCM\Tests;

require_once './Logic/Functions.php';

use Functions;

class VehicleTest extends \PHPUnit_Framework_TestCase
{

    public function testIsValidVIN()
    {
        $vins = [
            'VTTEST0000000007',
            'VTTEST00000000075',
            'VTTEST00000000076',
            'VTTEST000000000760',
            'VTTEST00000000077',
	    // more vins...
        ];

        foreach ($vins as $vin) {
            $this->assertTrue(
				Functions::checkVIN($vin),
				"VIN $vin is not correct!\n"
			);
        }
    }

}

We can rewrite this in a better way with data providers:

<?php

namespace FCM\Tests;

require_once './Logic/Functions.php';

use Functions;

/**
 * Class VehicleTest
 * @package FCM\Tests
 */
class VehicleTest extends \PHPUnit_Framework_TestCase
{

    /**
     * @dataProvider providerTestIsValidVIN
     * @param $vin
     */
    public function testIsValidVIN($vin)
    {
        $this->assertTrue(
			Functions::checkVIN($vin),
			"VIN $vin is not correct!\n"
		);
    }

    /**
     * @return array
     */
    public function providerTestIsValidVIN()
    {
        return [
            array('VTTEST0000000007'),
            array('VTTEST00000000075'),
            array('VTTEST00000000076'),
            array('VTTEST000000000760'),
            array('VTTEST00000000077'),
            array('VTTEST00000000078'),
            array('VTTEST00000000078'),
            // more vins...
        ];
    }

}

Appendix

Composer

Composer is a dependency manager, not a package manager.

 

This means composer install dependencies in every single project where it is installed.

 

Every project should have its own copy of composer working on it.

 

Composer requires PHP 5.3.2 or higher to work.

 

Dependencies are declared in the file composer.json.

To install composer you can proceed in different ways:

$ php -r "readfile('https://getcomposer.org/installer');" | php
$ curl -sS https://getcomposer.org/installer | php

Or just go to https://getcomposer.org/installer and the the file. This is a phar file, a "PHP executable" file.

 

After getting the installer you have to do this:

$ php installer

Now you should have the composer.phar file that can be renamed to just "composer".

 

Use it with the following command:

$ php composer <commands>

Composer creates an autoload in vendor/autoload.php.

 

You can include this autoload wherever you want:

<?php

require __DIR__ . '/vendor/autoload.php';

To manage composer you have to know how to write things in the composer.json file.

 

This file uses JSON language.

 

There are a lot of things that you can put into this file but there are two main keywords you may know:

 

require: package/version

 

This will install the given package in that version.

 

name: vendor/package

 

This will give a name to the project in the sense that will make the whole project an installable package, at least theoretically.

 

The dependencies that Composer can manage are described in the website packagist.org, the de facto standard package repository.

Mock objects, Stub methods and Mock methods

A Mock Object is an object that mimics another one, copying its interface in order to allow use this object instead of the external, original one.

$authorizeNet = $this->getMockBuilder('\AuthorizeNetAIM')
            ->setConstructorArgs(array($payment::API_ID, $payment::TRANS_KEY))
            ->getMock();

A Mock Object can use two kinds of methods:

Stub methods

This kind of method has the same method interface but with no code, always returning what you want:

$authorizeNet = $this->getMockBuilder('\AuthorizeNetAIM')
->setMethods(null)
->getMock();

Mock Methods

The same method as in the original object with the exact same code inside:

$authorizeNet->expects($this->once())
->method('authorizeAndCapture')
->will($this->returnValue($response));

Using setMethods(null), all methods of the Mock Object will be Mock Methods instead of Stub Methods.

 

Also you can pass array(method1, method2, ...) and everyone of them will be Stub and the rest, Mock.

 

Be carefull because in Mock Methods you cannot overwrite the return values.

 

You can't do a Stub method with a constructor!!.

 

But you can add ::disableOriginalConstructor() to the ::getMockBuilder, keeping the setMethods(array('__construct')) because, if not, every method will be Stub.

Dependency injection

What is Dependency Injection?

 

The easiest way to understant what is Dependency Injection is consider an object with a constructor that does this:

public function __construct() {
    $foo = new Foo();
    
    $this->doSomething($foo->getFooledhAgain());
}

Note that there's a Foo object instantiation, what means this object will depend on it. Instead of this, consider doing things in this way:

public function __construct(Foo $foo) {
    $this->doSomething($foo->getFooledhAgain());
}

Now, the Foo object is passed to the constructor as an object already built.

 

This reduces the object dependecies and actually "inject" the dependency in the constructor.

 

Also, consider using Mock Objects to reduce the dependencies in your objects.

PHPUnit and PHPStorm

If you're using PHPStorm as development IDE, you can integrate PHPUnit on it.

 

Open the PHPStorm settings and follow this steps:

 

  • Locate the PHP interpreter and put in Languages & Frameworks > PHP
  • In Languages & Frameworks > PHP > PHPUnit, select "use custom autoloader" and aim it to vendor/autoload.php. Also, check the "default configuration file" and aim it to the phpunit.xml file.
  • In the menu Run > Debug configurations add a new configuration with the name you want (in this example, FCM PHPUnit) and select "test runner" as "Defined in the configuration file" and aim it to the file phpunit.xml in the "Use alternative configuration file".

 

Now you should be able to run tests from the PHPStorm IDE.

Command line cheatsheet

Install composer

$ php install

Update project with composer

$ php composer update

Run PHPUnit

$ .\vendor\bin\phpunit
$ .\vendor\bin\phpunit path/to/my/file.php

Download PHPUnit Tutorial files

Go to ITPM, Task 5767. There should be a zip with the files.

bibliography and references

PHPUnit

By Rafa Chacón

PHPUnit

PHPUnit training presentation

  • 585