01 Introduction

Unit Testing Concepts

Unit Tests Overview

The objective of Unit Testing is to ensure the code in the application behaves exactly as intended.

PHPUnit Tests do this using PHP code written to call the methods in your application, and running tests ("assertions") that what comes back is what's expected.

A simple pseudo-code example of this is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
...

    public function addingNumbersWorks() : void
    {
        $expected = 5;
        $actual = MathsOperations::add(3, 2);

        $this->assertSame($expected, $actual);
    }

...

Each assert- method is considered to be one test, and has its own definition of what passes and what fails.

Using PHPUnit

PHPUnit provides a framework to implement these Unit Tests by providing classes to inherit from.

To write Unit Tests using PHPUnit, you just create a class that inherits PHPUnit's TestCase class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php

// Make sure your test class extends the PHPUnit TestCase
class MathsOperationsTest extends \PHPUnit\Framework\TestCase
{
    public function addingNumbersWorks() : void
    {
        // Load up the application class to be tested
        $mathsOperations = new \App\Maths\MathsOperations();

        // Call the method and capture the result
        $actual = $mathsOperations->add(3, 2);

        // Specify what you expect the method to return
        $expected = 5;

        // The test passes and fails based on if $expected is the same as $actual
        $this->assertSame($expected, $actual);
    }
}

Test Types

The traditional test types are "unit", "integration" and "functional", with each test type having in theory a defined purpose. In reality though we've found their definitions and rules contentious and unclear.

Instead we use "small", "medium" and "large", as inspired by this blog post

The tests are defined by what is NOT allowed to be done.

  • The test should always be in the smallest name possible
  • For each class and method, there should always be at least some small tests, for example testing what happens when invalid parameters are fed in.

Each Test Class should mention which type it is using either an @small, @medium, @large annotation at the top of the class.

Small

Should be very fast and test a small piece of functionality, eg a single method.

The test and the code being tested can not:

  • make any network or web requests
  • access a database
  • access a caching system (eg Redis)
  • write any changes to the filesystem

A single small test should execute in less than 1 second

Small tests should have no side effects or cleanup required

Medium

Medium test are generally a bit slower and have fewer restrictions

They can:

  • access the database (localhost or not)
  • access the filesystem
  • access caching services (localhost or not)

They can not:

  • access anything on the network that is not localhost

A single medium test should execute in less than 5 seconds

Large

Large tests have no restrictions and can run for an effectively unlimited amount of time, though of course you should always try to make your tests as fast and efficient as possible.

By default, the PHPQA configuration is 300 seconds which should be more than enough for a single test to run.

They can directly access as much or as little of the code as required.

Generally they are testing large pieces of functionality as a whole.

Naming Conventions

Test Namespace and Class Names

These are in the format Tests\{Test_Type}\{Path}\{To}\{Application}\{Namespace}.

So a Small test for the App\Maths\MathsOperator class will be under the App\Tests\Small\Maths namespace.

These should be named the same as the application class you're testing, suffixed with Test

So a Small Unit Test testing the App\Maths\MathsOperations class should be named App\Tests\Small\Maths\MathsOperationsTest, and stored in tests/Small/Maths/MathsOperationsTest.php

Test Method Names

There are two conventional ways to name tests:

  1. Mapping test methods to application methods by naming the test method the same as the application method, and prefixing it with test
  2. Writing methods that describe what they're doing, and using an @test annotation to indicate that these are to be run as tests.

We've chosen the second one. Combined with the required method naming, this prints out more verbose test outputs which gives good visibility on what is happening. This is detailed in the PHPUnit TestDox documentation

If you're aiming for the Gold standard, you can additionally link up the test to specify which Application method is being tested. This is detailed later, but is optional for the Bronze and Silver levels.

Variables

Your standard checks should use the variables $expected and $actual

When making multiple assertions in one test, you can disambiguate using either $expectedFoo and $actualFoo, or $notExpected as required.