15. Cookbook¶
15.1. Change the default namespace¶
At the execution beginning of a test class, atoum computes the name of the tested class. To do this, by default, it replaces in the class name the following regular expression #(?:^|\\\)tests?\\\units?\\#i
by char \
.
Thus, if the test class name is vendor\project\tests\units\foo
, it will deduct in that the tested class named is vendor\project\foo
. However, it may be necessary that the namespace of the test classes may not match this regular expression, and in this case, atoum then stops with the following error message:
> exception 'mageekguy\atoum\exceptions\runtime' with message 'Test class 'project\vendor\my\tests\foo' is not in a namespace which match pattern '#(?:^|\\)ests?\\unit?s\#i'' in /path/to/unit/tests/foo.php
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
We must therefore change the regular expression we used, this is possible in several ways. The easiest way is to applied the annotations @namespace
to the test class in the following way :
<?php
namespace vendor\project\my\tests;
require_once __DIR__ . '/atoum.phar';
use mageekguy\atoum;
/**
* @namespace \my\tests
*/
abstract class aClass extends atoum
{
public function testBar()
{
/* ... */
}
}
This method is quick and simple to implement, but it has the disadvantage of having to be repeated in each test class, which is not so maintainable if there is some change in their namespace. The alternative is to call the atoum\test::setTestNamespace()
method in the constructor of the test class, in this way:
<?php
namespace vendor\project\my\tests;
require_once __DIR__ . '/atoum.phar';
use mageekguy\atoum;
abstract class aClass extends atoum
{
public function __construct(score $score = null, locale $locale = null, adapter $adapter = null)
{
parent::__construct($score, $locale, $adapter);
$this->setTestNamespace('\\my\\tests');
}
public function testBar()
{
/* ... */
}
}
The atoum\test:setTestNamespace()
method indeed accepts a single argument which must be the regular expression matches the namespace of your test class. And to not have to repeat the call to this method in each test class, just do it once and for all in an abstract class in the following manner:
<?php
namespace vendor\project\my\tests;
require_once __DIR__ . '/atoum.phar';
use mageekguy\atoum;
abstract class Test extends atoum
{
public function __construct(score $score = null, locale $locale = null, adapter $adapter = null)
{
$this->setTestNamespace('\\my\\tests');
parent::__construct($score, $locale, $adapter);
}
}
Thus, you will only have to do derive your unit test classes from this abstract class:
<?php
namespace vendor\project\my\tests\modules;
require_once __DIR__ . '/atoum.phar';
use mageekguy\atoum;
use vendor\project\my\tests;
class aModule extends tests\Test
{
public function testDoSomething()
{
/* ... */
}
}
In case of unit tests namespace change, it is therefore necessary to change only the abstract class.
Moreover, it’s not mandatory to use a regular expression, either at the level of the @namespace
annotation or the method atoum\test::setTestNamespace()
a simple string can also works.
Indeed atoum by default use a regular expression so that the user can use a wide range of namespaces without the need to configure it at this level. This therefore allows it to accept for example, without any special configuration the following namespaces:
test\unit\
Test\Unit\
tests\units\
Tests\Units\
TEST\UNIT\
However, in general, the namespace used to test classes is fixed, and it’s not necessary to use a regular expression if the default isn’t suitable. In our case, it could be replaced with the string my\tests
, for example through the @namespace
annotation :
<?php
namespace vendor\project\my\tests;
require_once __DIR__ . '/atoum.phar';
use mageekguy\atoum;
/**
* @namespace \my\tests\
*/
abstract class aClass extends atoum
{
public function testBar()
{
/* ... */
}
}
15.2. Test of a singleton¶
To test a method that always returns the same instance of an object, checks that two calls to the tested method are the same.
<?php
$this
->object(\Singleton::getInstance())
->isInstanceOf('Singleton')
->isIdenticalTo(\Singleton::getInstance())
;
15.3. Hook git¶
A good practice, when using a version control system, is to never add a non-functional code in repository, in order to retrieve a version clean and usable code at any time and any place the history of the deposit.
This implies, among other things, that the unit tests must pass in their entirety before the files created or modified are added to the repository, and as a result, the developer is supposed to run the unit tests before pushed its code to the repository.
However, in fact, it is very easy for the developer to omit this step and your repository may therefore contain, more or less imminent, code which does not respect the constraints imposed by unit tests.
Fortunately, version control software in general and in particular Git has a mechanism, known as the pre-commit hook name to automatically perform tasks when adding code in a repository.
The installation of a pre-commit hook is very simple and takes place in two stages.
15.3.1. Step 1: Creation of the script to run¶
When adding code to a repository, Git looks for the file .git/hook/pre-commit
in the root of the repository and executes it if it exists and that it has the necessary rights.
To set up the hook, you must therefore create the .git/hook/pre-commit
file and add the following code:
The code below assumes that your unit tests are in files with the extension .php
and directories path contains / Tests/Units
. If this is not the case, you will need to modify the script depending on your context.
Note
In the above example, the test files must include atoum for the hook works.
The tests are run very quickly with atoum, all unit tests can be run before each commit with a hook like this :
#!/bin/sh
./bin/atoum -d tests/
15.3.2. Step 2: Add execution rights¶
To be usable by Git, the file .git/hook/pre-commit
must be executable by using the following command, executed in command line from the directory of your deposit:
$ chmod u+x `.git/hook/pre-commit`
From this moment on, unit tests contained in the directories with the path contains / Tests/Units
will be launched automatically when you perform the command git commit
, if files with the extension .php
have been changed.
And if unfortunately a test does not pass, the files will not be added to the repository. You must then make the necessary adjustments, use the command git add
on modified files and use again git commit
.
15.4. Use in behat¶
The asserters from atoum are very easy to use outside your traditional unit tests. Just import the class mageekguyatoumasserter without forgetting to load the required classes (atoum provides an autoload class available in classes/autoloader.php). The following example illustrates this usage of asserter from atoumin your Behat steps.
15.4.1. Installation¶
Simply install atoum and Behat in your project via pear, git clone, zip… Here is an example with dependency manager Composer :
"require-dev": {
"behat/behat": "2.4@stable",
"atoum/atoum": "~2.5",
}
It is obviously mandatory to update your composer dependencies with the command :
$ php composer.phar update
15.4.2. Configuration¶
As mentioned in the introduction, just import the asserter classes from atoum and ensure that they are loaded. For Behat, configuration of asserters are done inside the class FeatureContext.php (located by default in your directory /root-of-project/features/bootstrap/).
<?php
use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\TranslatedContextInterface,
Behat\Behat\Context\BehatContext,
Behat\Behat\Exception\PendingException,
Behat\Behat\Context\Step;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
use atoum\asserter; // <- atoum asserter
require_once __DIR__ . '/../../vendor/atoum/atoum/classes/autoloader.php'; // <- autoload
class FeatureContext extends BehatContext
{
private $assert;
public function __construct(array $parameters)
{
$this->assert = new asserter\generator();
}
}
15.4.3. Usage¶
After these 2 particular trivial steps, your steps can be extended with the atoum asserters :
<?php
// ...
class FeatureContext extends BehatContext
{//...
/**
* @Then /^I should get a good response using my favorite "([^"]*)"$/
*/
public function goodResponse($contentType)
{
$this->assert
->integer($response->getStatusCode())
->isIdenticalTo(200)
->string($response->getHeader('Content-Type'))
->isIdenticalTo($contentType);
}
}
Once again, this is only an example specific to Behat but it remains valid for all needs of using the asserters of atoum outside the initial context.
15.5. Use with continous integration tools (CI)¶
15.5.1. Use inside Jenkins (or Hudson)¶
It’s very simple to the results of atoum to Jenkins (or Hudson) as xUnit results.
15.5.1.1. Step1: Add a xUnit report to the configuration of atoum¶
Like other coverage report, you can use specific report from the configuration.
15.5.1.1.1. If you don’t have a configuration file¶
If you don’t have a configuration file for atoum yet, we recommend that you extract the directory resource of atoum in that one of your choice by using the following command :
- If you are using the Phar archive of atoum :
$ php atoum.phar --extractRessourcesTo /tmp/atoum-src
$ cp /tmp/atoum-src/resources/configurations/runner/xunit.php.dist /my/project/atoum.php
- If you are using the sources of atoum :
$ cp /path/to/atoum/resources/configurations/runner/xunit.php.dist /my/project/.atoum.php
- You can also directly copy the files from the Github repository
There is one last step, edit this file to set the path to the xUnit report where atoum will generate it. This file is ready to use, with him, you will keep the default report and gain a xUnit report for each launch of tests.
15.5.1.1.2. If you already have a configuration file¶
If you already have a configuration file, simply add the following lines:
<?php
//...
/*
* Xunit report
*/
$xunit = new atoum\reports\asynchronous\xunit();
$runner->addReport($xunit);
/*
* Xunit writer
*/
$writer = new atoum\writers\file('/path/to/the/report/atoum.xunit.xml');
$xunit->addWriter($writer);
15.5.1.2. Step 2: Test the configuration¶
To test this configuration, simply run atoum specifying the configuration file you want to use :
$ ./bin/atoum -d /path/to/the/unit/tests -c /path/to/the/configuration.php
Note
If you named your configuration file .atoum.php
, it will be load automatically. The -c
parameter is optional in this case.
To let atoum load automatically the .atoum.php
file, you will need to run test from the folder where this file resides or one of his childs.
At the end of the tests, you will have the xUnit report inside the folder specified in the configuration.
15.5.1.3. Step 3: Launching tests via Jenkins (or Hudson)¶
There are several possibilities depending on how you build your project :
- If you use a script, simply add the previous command.
- If you use a utility tool like phing or ant, simply add an exec task like :
<target name="unitTests">
<exec executable="/usr/bin/php" failonerror="yes" failifexecutionfails="yes">
<arg line="/path/to/atoum.phar -p /path/to/php -d /path/to/test/folder -c /path/to/atoumConfig.php" />
</exec>
</target>
Notice the addition of -p /path/to/php
that permit to atoum to know the path to the php binary to use to run the unit tests.
15.5.1.4. Step 4: Publish the report with Jenkins (or Hudson)¶
Simply enable the publication of report with JUnit or xUnit format of the plugin you are using, specifying the path to the file generated by atoum.
15.5.2. Use with Travis-CI¶
It’s simple to use atoum with a tool like Travis-CI. Indeed, all the steps are described in the travis documentation : * Create your .travis.yml in your project; * Add it the next two lines:
before_script: wget http://downloads.atoum.org/nightly/atoum.phar
script: php atoum.phar
Here is an example file .travis.yml where the unit tests in the tests folder will be run.
language: php
php:
- 5.4
- 5.5
- 5.6
before_script: wget http://downloads.atoum.org/nightly/atoum.phar
script: php atoum.phar -d tests/
15.6. Use with Phing¶
atoum test suite can be easily ran inside your phing configuration using the integrated phing/AtoumTask.php task. A valid build example can be found in the resources/phing/build.xml file.
You must register the custom task using the taskdef native phing task :
<taskdef name="atoum" classpath="vendor/atoum/atoum/resources/phing" classname="AtoumTask"/>
Then you can use it inside one of your buildfile target:
<target name="test">
<atoum
atoumautoloaderpath="vendor/atoum/atoum/classes/autoloader.php"
phppath="/usr/bin/php"
codecoverage="true"
codecoveragereportpath="reports/html/"
showcodecoverage="true"
showmissingcodecoverage="true"
maxchildren="5"
>
<fileset dir="tests/units/">
<include name="**/*.php"/>
</fileset>
</atoum>
</target>
The paths given in these examples have been taken from a standard composer installation. All the possible parameters are defined below, you can change values or omit some to rely on defaults. There is three kind of parameters:
15.6.1. atoum configurations¶
- bootstrap: Bootstrap file to be included before executing each test method
- default:
.bootstrap.atoum.php
- default:
- atoumpharpath: If atoum is used as phar, path to the phar file
- atoumautoloaderpath: Autoloader file before executing each test method
- default:
.autoloader.atoum.php
- default:
- phppath: Path to
php
executable - maxchildren: Maximum number of sub-processus which will be run simultaneously
15.6.2. Flags¶
- codecoverage: Enable code coverage (only possible if XDebug in installed)
- default:
false
- default:
- showcodecoverage: Display code coverage report
- default:
true
- default:
- showduration: Display test execution duration
- default:
true
- default:
- showmemory: Display consumend memory
- default:
true
- default:
- showmissingcodecoverage: Display missing code coverage
- default:
true
- default:
- showprogress: Display test execution progress bar
- default:
true
- default:
- branchandpathcoverage: Enable branch and path coverage
- default:
false
- default:
- telemetry: Enable telemetry report (atoum/reports-extension must be installed)
- default:
false
- default:
15.6.3. Reports¶
- codecoveragexunitpath: Path to xunit report file
- codecoveragecloverpath: Path to clover report file
- Code Coverage Basic
- codecoveragereportpath: Path to HTML report
- codecoveragereporturl: URL to HTML report
- Code Coverage Tree Map:
- codecoveragetreemappath: Path to tree map
- codecoveragetreemapurl: URL to tree map
- Code Coverage Advanced
- codecoveragereportextensionpath: Path to HTML report
- codecodecoveragereportextensionurl: URL to HTML report
- Telemetry
- telemetryprojectname: Name of telemetry report to be sent
15.7. Use with frameworks¶
15.7.1. Use with ezPublish¶
15.7.1.1. Step 1: Installation of atoum in eZ Publish¶
The eZ Publish framework have already a directory dedicated to tests, logically named tests. It’s in this directory that should be placed the PHAR archive of atoum. The unit test files using atoum will be placed in a subdirectory tests/atoum so they don’t conflict with the existing.
15.7.1.2. Step 2: Creating the class of the base tests¶
A class based on atoum must extend the class \mageekguy\atoum\test
. However, this one doesn’t take into account of eZ Publish specificities. It’s therefore mandatory to
define a base test class, derived from \mageekguy\atoum\test
, which will take into account these specificities and will derive all of the classes of unit tests. To do this, just defined the following class in the file tests\atoum\test.php
:
<?php
namespace ezp;
use mageekguy\atoum;
require_once __DIR__ . '/atoum.phar';
// Autoloading : eZ
require 'autoload.php';
if ( !ini_get( "date.timezone" ) )
{
date_default_timezone_set( "UTC" );
}
require_once( 'kernel/common/i18n.php' );
\eZContentLanguage::setCronjobMode();
/**
* @abstract
*/
abstract class test extends atoum\test
{
}
?>
15.7.1.3. Step 3: Creating a test class¶
By default, atoum asks that unit tests classes are in a namespace containing test(s)unit(s), in order to deduce the name of the tested class. For example, the namespace nameofprojet will be used in the following. For simplicity, it’s further advisable to model the test tree on the tested classes tree, in order to quickly locate the class of a tested class, and vice versa.
<?php
namespace nameofproject\tests\units;
require_once '../test.php';
use ezp;
class cache extends ezp\test
{
public function testClass()
{
$this->assert->hasMethod('__construct');
}
}
15.7.1.4. Step 4: Running the unit tests¶
Once a test class created, simply execute this command-line to start the test from the root of the project:
# php tests/atoum/atoum.phar -d tests/atoum/units
Thanks to Jérémy Poulain for this tutorial.
15.7.2. Use with Symfony 2¶
If you want to use atoum within your Symfony projects, you can install the Bundle AtoumBundle.
If you want to install and configure atoum manually, here’s how to do it.
15.7.2.1. Step 1: installation of atoum¶
If you use Symfony 2.0, download the PHAR and place it in the vendor directory which is at the root of your project.
If you use Symfony 2.1+, add atoum in your composer.json.
15.7.2.2. Step 2: create the test class¶
Imagine that we wanted to test this Entity:
<?php
// src/Acme/DemoBundle/Entity/Car.php
namespace Acme\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\DemoBundle\Entity\Car
* @ORM\Table(name="car")
* @ORM\Entity(repositoryClass="Acme\DemoBundle\Entity\CarRepository")
*/
class Car
{
/**
* @var integer $id
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string $name
* @ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* @var integer $max_speed
* @ORM\Column(name="max_speed", type="integer")
*/
private $max_speed;
}
Note
For more information about creating Entity in Symfony 2, refer to the symfony documentation
Create the directory Tests/Units in your Bundle (for example src/Acme/DemoBundle/Tests/Units). It’s in this directory that will be stored all tests of this Bundle.
Create a Test.php file that will serve as a base for all new tests in this Bundle.
<?php
// src/Acme/DemoBundle/Tests/Units/Test.php
namespace Acme\DemoBundle\Tests\Units;
// It includes the class loader and active it
require_once __DIR__ . '/../../../../../vendor/symfony/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php';
$loader = new \Symfony\Component\ClassLoader\UniversalClassLoader();
$loader->registerNamespaces(
array(
'Symfony' => __DIR__ . '/../../../../../vendor/symfony/src',
'Acme\DemoBundle' => __DIR__ . '/../../../../../src'
)
);
$loader->register();
use mageekguy\atoum;
// For Symfony 2.0 only !
require_once __DIR__ . '/../../../../../vendor/atoum.phar';
abstract class Test extends atoum
{
public function __construct(
adapter $adapter = null,
annotations\extractor $annotationExtractor = null,
asserter\generator $asserterGenerator = null,
test\assertion\manager $assertionManager = null,
\closure $reflectionClassFactory = null
)
{
$this->setTestNamespace('Tests\Units');
parent::__construct(
$adapter,
$annotationExtractor,
$asserterGenerator,
$assertionManager,
$reflectionClassFactory
);
}
}
Note
The inclusion of atoum’s PHAR archive is only necessary for Symfony 2.0. Remove this line if you use Symfony 2.1+.
Note
By default, atoum uses namespace tests/units for testing. However Symfony 2 and its class loader require capitalization at the beginning of the names. For this reason, we change tests namespace through the method: setTestNamespace(‘TestsUnits’).
15.7.2.3. Step 3: write a test¶
In the Tests/Units directory, simply recreate the tree of the classes that you want to test (for example src/Acme/DemoBundle/Tests/Units/Entity/Car.php).
Create our test file:
<?php
// src/Acme/DemoBundle/Tests/Units/Entity/Car.php
namespace Acme\DemoBundle\Tests\Units\Entity;
require_once __DIR__ . '/../Test.php';
use Acme\DemoBundle\Tests\Units\Test;
class Car extends Test
{
public function testGetName()
{
$this
->if($car = new \Acme\DemoBundle\Entity\Car())
->and($car->setName('Batmobile'))
->string($car->getName())
->isEqualTo('Batmobile')
->isNotEqualTo('De Lorean')
;
}
}
15.7.2.4. Step 4: launch tests¶
If you use Symfony 2.0:
# Launch tests of one file
$ php vendor/atoum.phar -f src/Acme/DemoBundle/Tests/Units/Entity/Car.php
# Launch all tests of the Bundle
$ php vendor/atoum.phar -d src/Acme/DemoBundle/Tests/Units
If you use Symfony 2.1+:
# Launch tests of one file
$ ./bin/atoum -f src/Acme/DemoBundle/Tests/Units/Entity/Car.php
# Launch all tests of the Bundle
$ ./bin/atoum -d src/Acme/DemoBundle/Tests/Units
Note
You can get more information on the test launch in the chapter which is dedicated to.
In any case, this is what you should get:
> PHP path: /usr/bin/php
> PHP version:
> PHP 5.3.15 with Suhosin-Patch (cli) (built: Aug 24 2012 17:45:44)
===================================================================
> Copyright (c) 1997-2012 The PHP Group
=======================================
> Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies
===============================================================
> with Xdebug v2.1.3, Copyright (c) 2002-2012, by Derick Rethans
====================================================================
> Acme\DemoBundle\Tests\Units\Entity\Car...
[S___________________________________________________________][1/1]
> Test duration: 0.01 second.
=============================
> Memory usage: 0.50 Mb.
========================
> Total test duration: 0.01 second.
> Total test memory usage: 0.50 Mb.
> Code coverage value: 42.86%
> Class Acme\DemoBundle\Entity\Car: 42.86%
==========================================
> Acme\DemoBundle\Entity\Car::getId(): 0.00%
--------------------------------------------
> Acme\DemoBundle\Entity\Car::setMaxSpeed(): 0.00%
--------------------------------------------------
> Acme\DemoBundle\Entity\Car::getMaxSpeed(): 0.00%
--------------------------------------------------
> Running duration: 0.24 second.
Success (1 test, 1/1 method, 0 skipped method, 4 assertions) !
15.7.3. Use with symfony 1.4¶
If you want to use atoum inside your Symfony 1.4 project, you can install the plugins sfAtoumPlugin. It’s available on this address: https://github.com/atoum/sfAtoumPlugin.
15.7.3.1. Installation¶
There are several ways to install this plugin in your project:
- installation via composer
- installation via git submodules
15.7.3.1.1. Using composer¶
Add this lines inside the composer.json file:
"require" : {
"atoum/sfAtoumPlugin": "*"
},
After a php composer.phar update
the plugin should be in the plugin folder and atoum in the vendor
folder.
Then, in your ProjectConfiguration file, you have to activate the plugin and define the atoum path.
<?php
sfConfig::set('sf_atoum_path', dirname(__FILE__) . '/../vendor/atoum/atoum');
if (sfConfig::get('sf_environment') != 'prod')
{
$this->enablePlugins('sfAtoumPlugin');
}
15.7.3.1.2. Using a git submodule¶
First, install atoum as a submodule:
$ git submodule add git://github.com/atoum/atoum.git lib/vendor/atoum
Then install sfAtoumPlugin as a git submodule:
$ git submodule add git://github.com/atoum/sfAtoumPlugin.git plugins/sfAtoumPlugin
Finally, enable the plugin in in your ProjectConfiguration file:
<?php
if (sfConfig::get('sf_environment') != 'prod')
{
$this->enablePlugins('sfAtoumPlugin');
}
15.7.3.2. Write tests¶
Tests must include the bootstrap file from the plugin:
<?php
require_once __DIR__ . '/../../../../plugins/sfAtoumPlugin/bootstrap/unit.php';
15.7.3.3. Launch tests¶
The symfony command atoum:test
is available. The tests can then be launched in this way:
$ ./symfony atoum:test
All the arguments of atoum are available.
It’s therefore, for example, possible to give a configuration file like this :
php symfony atoum:test -c config/atoum/hudson.php
15.7.4. Symfony 1 plugin¶
To use atoum within a symfony project 1, a plug-in exists and is available at the following address: https://github.com/atoum/sfAtoumPlugin.
The instructions for installation and use are the cookbook Use with symfony 1.4 as well as on the github page.
15.7.5. Symfony 2 bundle¶
To use atoum inside a Symfony 2 project, the bundle AtoumBundle is available.
The instructions for installation and use are the cookbook Use with Symfony 2 as well as on the github page.
15.7.6. Zend Framework 2 component¶
If you want to use atoum within a Zend Framework 2 project, a component exists and is available at the following address.
The instructions for installation and usage are available on this page.