Get up to 80 % extra points for free! More info:

Lesson 2 - Introduction to unit tests in PHP and PHPUnit installation

In the previous lesson, Introduction to web application testing in PHP, we made a relatively solid introduction to the issue. We also introduced the v-model that illustrates the relationship between the individual design phase outputs and the relevant tests.

We always write tests based on design, not the implementation. In other words, we create them based on the expected functionality. It can be obtained either directly from the customer (and that's in the case of acceptance tests) or from the programmer (architect), where he specifies how different methods should behave. Today, we're going to focus on these tests, that are called unit tests, and which test the detailed specification of the application, thus its classes.

Remember that we never write tests according to how something is programmed inside! It could very easily seduce our thinking in just one way, and we could forget that the method might get some other inputs which it isn't expecting. In fact, testing isn't related with implementation at all, we always test whether the requirements are met.

Which classes to test

Unit tests test individual methods in classes. For sure, I repeat that it doesn't make much sense to test single-purpose methods, for example, in models that just pick something from the database. To be more specific, there is no point in testing a method like this:

public function insertItem($name, $price)
{
    $this->db->query("INSERT INTO item (name, price) VALUES (?, ?)", $name, $price);
}

The method inserts an item to the database. Typically, it's only used in some form, and if it doesn't work, it'll be detected by acceptance tests as the new item wouldn't appear on a list. There is a lot of the similar methods in applications and we would unnecessarily waste our time covering something we can easily cover in other tests.

Unit tests are most often found with libraries, the tools the programmer uses in many places or even in multiple projects and should be 100% functional. You may remember when you used a library downloaded, for example, from GitHub. Most likely, there were also tests included, which were probably located in the "tests" folder next to the "src" folder in the project's directory structure. For example, if we write an application in which we often need some mathematical calculations, such as factorials and other probability functions, it's a good practice to create a library for these calculations, and also it's a good idea to cover such library by tests.

Example

As you probably expected, we're going to create a similar class and try to test it. Not to waste our time, we'll just create a simple calculator that will be able to:

  • sum
  • subtract
  • multiply
  • divide

Creating the project

In reality, there would be some more complicated calculations in the class, but we won't deal with it here. Create a new project called calculator and add the Calculator class with the following implementation:

<?php

/**
 * Represents a simple calculator
 */
class Calculator
{

    /**
     * Sums up 2 numbers
     * @param int|float $a The first number
     * @param int|float $b The second number
     * @return int|float The sum of the numbers
     */
    public function sum($a, $b)
    {
        return $a + $b;
    }

    /**
     * Subtracts 2 numbers
     * @param int|float $a The first number
     * @param int|float $b The second number
     * @return int|float The difference of the numbers
     */
    public function subtract($a, $b)
    {
        return $a - $b;
    }

    /**
     * Multiplies 2 numbers
     * @param int|float $a The first number
     * @param int|float $b The second number
     * @return int|float The product of the numbers
     */
    public function multiply($a, $b)
    {
        return $a * $b;
    }

    /**
     * Divides 2 numbers
     * @param int|float $a The first number
     * @param int|float $b The second number
     * @return int|float The quotient of the numbers
     */
    public function divide($a, $b)
    {
        if ($b == 0)
            throw new \InvalidArgumentException("Cannot divide by zero!");
        return $a / $b;
    }

}

The interesting thing in the code is just the divide() method, which throws an exception if we divide by zero. The default PHP behavior is causing a script error, which the user should never see in the application. The class could be even in a namespace, we'd import it later in the tests by the use keyword.

PHPUnit

In PHP, unit tests are mostly written in the PHPUnit framework, which each PHP programmer should know. There are, of course, alternative tools, such as Nette tester, which all work very similarly.

PHPUnit - Testing in PHP

While PHPUnit can be installed separately, both here in the course and also in your own applications, we will later need other types of tests, at least the acceptance tests. Because of them, you need to work with other tools and to install everything separately would create quite a lot of work. Therefore, install PHPUnit using the Codeception framework.

Codeception

Codeception is a comprehensive test framework for PHP that includes:

  • PHPUnit
  • Acceptance package for selenium
  • Another testing frameworks, insignificant for us now

We can install it either via a composer or by simply downloading a single .phar file. If you have not heard of .phar files yet, they are executable archives with PHP applications that can be run e.g. by your IDE.

Installation

I'll use the first option via the .phar file here, the installation via Composer can be seen below in case you're interested in it. Go to the quickstart web page at http://codeception.com/quickstart and download the codecept.phar file, ideally save it to the folder with today's project.

These articles are tested for Codeception version 2.5.6. This version can be downloaded here: https://codeception.com/builds (just click on the Download latest 2.5 Release link).

Since the tests are an advanced topic, we'll use the more advanced IDE - PhpStorm. Of course, you can also do testing with NetBeans if you want for some reason.

Now we create an alias for the .phar file, so we can simply run it from the console. In the File menu, choose Settings and type "Command Li" into the search box above to open the Command Line Tool Support tool.

Command Line Tools Support in PhpStorm - Testing in PHP

Use the "+" button at the top right to add a new "Custom tool" item with visibility for this project. Then fill in the form:

  • Tool path: C:\xampp\php\php.exe codecept.phar, you can eventually modify the path to your PHP interpreter. On Linux, just enter the php instead.
  • Alias: test (that's the name of the command through which we'll run the archive)
Adding a Command Line Tool in PhpStorm - Testing in PHP

Confirm all windows.

Installation via Composer

This passage describes how to install Codeception using Composer. If you don't use it, skip it. If you have Composer, you can install Codeception using the composer require "codeception/codeception" --dev command.

Articles are tested using Codeception version 2.5.6 which was the latest version when we were writing them. This specific version can be downloaded using the following command composer require "codeception/codeception:2.5.6".

I recommend linking PhpStorm with Composer and Codeception, otherwise the code autocompletion might not work.

The IDE should be set automatically, however, I provide screenshots of my settings to be sure:

PhpStorm Composer Settings - Testing in PHP

Setting the Composer autoloader in PhpStorm - Testing in PHP

Codeception setting in PhpStorm - Testing in PHP

In this case, the Composer command path will be set as the path to the batch file generated by Composer. It should look like this: project_path\Calculator\vendor\bin\codecept.bat. Use the file without the .bat extension on Linux.

Bootstrap

Now click on the menu Tools -> Run command and enter the following code into the console (be careful not to mess it up with the terminal, which PhpStorm also contains):

test bootstrap

This will generate tests into our project.

Notice that the tests' folder has been added, which contains several other files and subfolders. The `unit subfolder will be important to us so far, we'll generate new unit tests into it. Because tests use classes from our application, they need us to define at least the autoloader. This is done in _bootstrap.php files, which are here either separate for each type of test or for all tests together.

In our case, we'll create a _bootstrap.php in the unit folder, where we'll define a simple autoloader:

<?php

function autoloader($class_name)
{
    if (!file_exists(__DIR__ . '/../../' . $class_name . '.php'))
        return false;
    require(__DIR__ . '/../../' . $class_name . '.php');
}

spl_autoload_register('autoloader');

Notice that the autoloader returns false if it fails to load the class. This is very important, because this process can be followed by other Codeception autoloaders that are used for its own files.

It can happen that these files won't be enabled by default. Be sure to check the codeception.yml file located in the parent folder of the tests folder. It should contain the following lines:

settings:
    bootstrap: _bootstrap.php

Let me show you how mine file looks like for the completness' sake:

paths:
    tests: tests
    output: tests/_output
    data: tests/_data
    support: tests/_support
    envs: tests/_envs
actor_suffix: Tester
settings:
    bootstrap: _bootstrap.php
extensions:
    enabled:
        - Codeception\Extension\RunFailed

Generating tests

We generate a new unit test using a console command as well:

test generate:test unit CalculatorTest

The test name is usually assembled from the name of the test class + the word "Test", in our case, "CalculatorTest". In the command, always edit the test name by the name of the class you are testing.

If you omit the word "Test" at the end of the name, it'll be added automatically. So the following command can also be used: test generate:test unit Calculator and the result will be the same.

If you ever needed to generate tests into another folder (for example, you have multiple tests in one project, one in the app folder and the other in vendor), you can specify it as follows:

test --config=vendor/SomeFrameworkWithTestsFolder generate:test unit SomeTest

The --config parameter specifies the path to the folder that contains the codeception.yml configuration file, so the folder must contain tests (a structure generated by the bootstrap command). This allows us to generate and run tests of different submodules of the system.

In the unit folder, a new file with the following code has been generated:

<?php

class CalculatorTest extends \Codeception\Test\Unit
{
    /**
     * @var \UnitTester
     */
    protected $tester;

    protected function _before()
    {
    }

    protected function _after()
    {
    }

    // tests
    public function testSomeFeature()
    {
    }
}

Alternatively, you can have a version of PHPUnit that uses namespaces, then it'll inherit from a class named just TestCase and above it, there'll be import from the appropriate space.

It's probably not surprising that the (scenario) class test is also represented by the class and individual tests by the methods :) What is more interesting is the fact that we can find the pre-prepared methods there. The last one, with the "test" word at the beginning, will be started automatically as every "test" method is. We will explain these other two methods in the next lesson, PHP Testing - Finishing Unit Tests.


 

Previous article
Introduction to web application testing in PHP
All articles in this section
Testing in PHP
Skip article
(not recommended)
PHP Testing - Finishing Unit Tests
Article has been written for you by David Capka Hartinger
Avatar
User rating:
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn university David learned IT at the Unicorn University - a prestigious college providing education on IT and economics.
Activities