5. How to write test cases

After you have created your first test and understood how to run it<lancement-des-tests>, you will want to write better tests. In this section you will see how to sprinkle on some syntactic sugar to help you write better test more easily.

There are several ways to write unit test with atoum, one of them is to use keywords like given, if, and and even then, when or assert so you can structure your tests to make them more readable.

5.1. given, if, and and then

You can use these keywords very intuitively:

<?php
$this
    ->given($computer = new computer())
    ->if($computer->prepare())
    ->and(
        $computer->setFirstOperand(2),
        $computer->setSecondOperand(2)
    )
    ->then
        ->object($computer->add())
            ->isIdenticalTo($computer)
        ->integer($computer->getResult())
            ->isEqualTo(4)
;

It’s important to note that these keywords don’t have any another purpose than presenting the test in a more readable format. They don’t serve any technical purpose. The only goal is to help the reader, human or more specifically developer, to quickly understand what’s happening in the test.

Thus, given, if and and specify the prerequisite assertions that follow the keyword then to pass.

However, there are no rules or grammar that dictate the order ot syntax of these keywords in atoum.

As a result, the developer should use the keywords wisely in order to make the test as readable as possible. If used incorrectly you could end up with tests like the following :

<?php
$this
    ->and($computer = new computer())
    ->if($computer->setFirstOperand(2))
    ->then
    ->given($computer->setSecondOperand(2))
        ->object($computer->add())
            ->isIdenticalTo($computer)
        ->integer($computer->getResult())
            ->isEqualTo(4)
;

For the same reason, the use of then is also optional.

Notice that you can write the exact same test without using any of the previous keywords:

<?php
$computer = new computer();
$computer->setFirstOperand(2);
$computer->setSecondOperand(2);

$this
    ->object($computer->add())
        ->isIdenticalTo($computer)
    ->integer($computer->getResult())
        ->isEqualTo(4)
;

The test will not be slower or faster to run and there is no advantage to use one notation or another, the important thing is to choose one format and stick to it. This facilitates maintenance of the tests (the problem is exactly the same as coding conventions).

5.2. when

In addition to given, if, and and then, there are also other keywords.

One of them is when. It has a specific feature introduced to work around the fact that it is illegal to write the following PHP code :

<?php # ignore
$this
    ->if($array = array(uniqid()))
    ->and(unset($array[0]))
    ->then
        ->sizeOf($array)
            ->isZero()
;

In this case the language will generate a fatal error: Parse error: syntax error, unexpected 'unset' (T_UNSET), expecting ')'

It is impossible to use unset() as an argument of a function.

To resolve this problem, the keyword when is able to interpret the possible anonymous function that is passed as an argument, allowing us to write the previous test in the following way:

<?php
$this
    ->if($array = array(uniqid()))
    ->when(
        function() use ($array) {
            unset($array[0]);
        }
    )
    ->then
      ->sizeOf($array)
        ->isZero()
;

Of course, if when doesn’t receive an anonymous function as an argument, it behaves exactly as given, if, and and then, namely that it does absolutely nothing functionally speaking.

5.3. assert

Finally, there is the keyword assert which also has a somewhat unusual operation.

To illustrate its operation, the following test will be used :

<?php
$this
    ->given($foo = new \mock\foo())
    ->and($bar = new bar($foo))
    ->if($bar->doSomething())
    ->then
        ->mock($foo)
            ->call('doOtherThing')
                ->once()

    ->if($bar->setValue(uniqid()))
    ->then
        ->mock($foo)
            ->call('doOtherThing')
                ->exactly(2)
;

The previous test has a disadvantage in terms of maintenance, because if the developer needs to add one or more new calls to bar::doOtherThing() between the two calls already made, it will have to update the value of the argument passed to exactly(). To resolve this problem, you can reset a mock in 2 different ways :

  • either by using $mock->getMockController()->resetCalls() ;
  • or by using $this->resetMock($mock).
<?php
$this
    ->given($foo = new \mock\foo())
    ->and($bar = new bar($foo))
    ->if($bar->doSomething())
    ->then
        ->mock($foo)
            ->call('doOtherThing')
                ->once()

    // first way
    ->given($foo->getMockController()->resetCalls())
    ->if($bar->setValue(uniqid()))
    ->then
        ->mock($foo)
            ->call('doOtherThing')
                ->once()

    // 2nd way
    ->given($this->resetMock($foo))
    ->if($bar->setValue(uniqid()))
    ->then
        ->mock($foo)
            ->call('doOtherThing')
                ->once()
;

These methods erase the memory of the controller, so it’s now possible to write the next assertion like if the mock was never used.

The keyword assert avoids the need for explicit all to resetCalls() or resetMock and also it will erase the memory of adapters and mock’s controllers defined at the time of use.

Thanks to it, it’s possible to write the previous test in a simpler and more readable way, especially as it is possible to pass a string to assert that explain the role of the following assertions :

<?php
$this
    ->assert("Bar has no value")
        ->given($foo = new \mock\foo())
        ->and($bar = new bar($foo))
        ->if($bar->doSomething())
        ->then
            ->mock($foo)
                ->call('doOtherThing')
                    ->once()

    ->assert('Bar has a value')
        ->if($bar->setValue(uniqid()))
        ->then
            ->mock($foo)
                ->call('doOtherThing')
                    ->once()
;

Moreover the given string will be included in the messages generated by atoum if one of the assertions is not successful.

5.4. newTestedInstance & testedInstance

When performing tests, we must often create a new instance of the class and pass it through parameters. Writing helper are available for this specific case, it’s newTestedInstance and testedInstance

Here’s an example :

namespace jubianchi\atoum\preview\tests\units;

use atoum;
use jubianchi\atoum\preview\foo as testedClass;

class foo extends atoum
{
    public function testBar()
    {
        $this
            ->if($foo = new testedClass())
            ->then
                ->object($foo->bar())->isIdenticalTo($foo)
        ;
    }
}

This can be simplified with a new syntax:

namespace jubianchi\atoum\preview\tests\units;

use atoum;

class foo extends atoum
{
    public function testBar()
    {
        $this
            ->if($this->newTestedInstance)
            ->then
                ->object($this->testedInstance->bar())
                    ->isTestedInstance()
        ;
    }
}

As seen, it’s slightly simpler but especially this has two advantages:

  • We do not manipulate the name of the tested class
  • We do not manipulate the tested instance

Furthermore, we can easily validate that the instance is available with isTestedInstance, as explained in the previous example.

To pass some arguments to the constructor, it’s easy through newTestedInstance:

$this->newTestedInstance($argument1, $argument2);

If you want to test a static method of your class, you can retrieve the tested class with this syntax:

namespace jubianchi\atoum\preview\tests\units;

use atoum;

class foo extends atoum
{
    public function testBar()
    {
      $this
        ->if($class = $this->testedClass->getClass())
        ->then
          ->object($class::bar())
       ;
    }
 }

5.4.1. Accessing constant of the tested class

If you require to access to the constant of your tested class, you can access it in two ways:

<?php

namespace
{
    class Foo
    {
        const A = 'a';
    }
}

namespace tests\units
{
    class Foo extends \atoum\test
    {
        public function testFoo()
        {
            $this
                ->given($this->newTestedInstance())
                ->then
                    ->string($this->getTestedClassName()::A)->isEqualTo('a')
                    ->string($this->testedInstance::A)->isEqualTo('a')
            ;
        }
    }
}

Warning

You firstly need to initiate the instance with the newTestedInstance, to have access to constant.

5.5. testedClass

Like testedInstance, you can use testedClass to write more comprehensible test. testedClass allows you to dynamically assert on the class being tested:

<?php
$this
        ->testedClass
    ->hasConstant('FOO')
                ->isFinal()
;

You can go further with the class asseters.