Testez votre code avec atoum

Le monde des Frameworks de tests unitaires en PHP est un micro-système bien gardé ou seuls deux représentants cohabitaient jusqu’alors : SimpleTest et le standard PHPUnit

Aujourd’hui, il faut aussi Bientôt il faudra aussi compter avec atoum, un framework de tests unitaires qui tire pleinement partie des possibilités de PHP 5.3 tels les namespaces et les closures.

Ainsi, si vous lancez un nouveau projet basé sur PHP 5.3+, il me semble opportun que lors du choix de votre framework de tests unitaires vous ayez une bonne idée de ce qu’est atoum.

Installation

Pour faire simple, téléchargez la dernière version de atoum dans un répertoire de votre choix. Nous considérerons dans la suite de ce tutoriel que atoum est situé dans ./tests/atoum/mageekguy.atoum.phar

NOTE : atoum est distribué au format PHAR (un fichier unique PHPArchive supporté nativement depuis PHP 5.2+)

Le principe général des tests

atoum vous demande de créer une classe de test pour chaque classe à tester. Concrètement, si vous souhaitez tester la classe HelloTheWorld, vous allez créer une classe de test tests\units\HelloTheWorld.

La classe HelloTheWorld à tester, située dans ./classes/HelloTheWorld.class.php

<?php
/**
 * La classe à tester
 */
class HelloTheWorld
{
    public function getHiBob ()
    {
        return "Hi Bob !";
    }
}

La classe de test, située par exemple dans ./tests/HelloTheWorld.php

<?php
//Vos classes de tests se situent dans un namespace dédié
namespace tests\units;

//Inclusion de la classe à tester
require_once __DIR__.'/../classes/HelloTheWorld.class.php';

//Inclusion de atoum dans toutes les classes de tests
require_once __DIR__.'/atoum/mageekguy.atoum.phar';

use \mageekguy\atoum;

/**
 * Test de la classe \HelloTheWorld
 */
class HelloTheWorld extends atoum\test
{
    public function testGetHiBob ()
    {
        //création de l'objet à tester
        $helloToTest = new \HelloTheWorld();

        $this->assert
                    //le retour de la méthode doit être une chaine de caractère
                    ->string($helloToTest->getHiBob())
                    //la chaine doit être égale à Hi Bob !
                    ->isEqualTo('Hi Bob !');
    }
}

Pour lancer les tests vous taperez la commande php -f ./test/HelloTheWorld.php

Résultats du lancement des tests en ligne de commande

Le principe des tests

Dans atoum, le principe générique d’un test est le suivant :

  1. J’indique la cible de mon assertion (un objet, une variable, une chaîne de caractère, un entier, …)
  2. J’indique le ou les états attendus de ma cible (égalité, nullité, existence, …)

Dans l’exemple précédent, nous avons testé que la méthode HelloTheWorld::getHiBob() était de type chaîne de caractère, puis nous avons indiqué qu’elle devait être égale à ‘Hi Bob !’

Les assertions classiques disponibles dans atoum

Tous les types classiques sont testables dans atoum :

  • Booléens (boolean)
  • Nombres flottants (float)
  • Nombres entiers (integer)
  • Objets (object)
  • Tableaux (array ou phpArray)
  • Chaine de caractères (string)
  • Ou simplement une variable (variable)

Exemple de classe à tester

<?php
class BasicTypes
{
    private static $_sharedInstance = false;
    public function getOne (){return 1;}
    public function getTrue(){return true;}
    public function getFalse(){return false;}
    public function getHello(){return 'hello';}
    public function create(){return new BasicTypes();}
    public function getFloat(){return 1.1;}
    public function getNull(){return null;}
    public function getEmptyArray(){return array();}
    public function getArraySizeOf3(){return range(0,2,1);}
}

Exemples de tests sur les types retournés par cette classe (certains tests sont redondants pour les besoins de l’aperçu) :

<?php //...
class BasicTypes extends atoum\test
{
    public function testBoolean ()
    {
        $bt = new \BasicTypes();
        $this->assert
                ->boolean($bt->getFalse())
                    ->isFalse()//getFalse retourne bien false
                ->boolean($bt->getTrue())
                    ->isTrue();//getTrue retourne bien true
    }

    public function testInteger ()
    {
        $bt = new \BasicTypes();
        $this->assert
                ->integer($bt->getOne())
                ->isEqualTo(1)
                ->isGreaterThan(0);
    }

    public function testString()
    {
        $bt = new \BasicTypes();
        $this->assert
                ->string($bt->getHello())
                ->isNotEmpty()
                ->isEqualTo('hello');
    }

    public function testObject ()
    {
        $bt = new \BasicTypes();
        $this->assert
                ->object($bt->create())
                ->isInstanceOf('BasicTypes')
                ->isNotIdenticalTo($bt);//Une nouvelle instance
    }

    public function testFloat()
    {
        $bt = new \BasicTypes();
        $this->assert
                ->float($bt->getFloat())
                ->isEqualTo(1.1);
    }

    public function testArray()
    {
        $bt = new \BasicTypes();
        $this->assert
                ->array($bt->getArraySizeOf3())
                    ->hasSize(3)
                    ->isNotEmpty()
                ->array($bt->getEmptyArray())
                    ->isEmpty();
    }

    public function testNull ()
    {
        $bt = new \BasicTypes();
        $this->assert
                ->variable($bt->getNull())
                ->isNull();
    }
}

Tester un Singleton

Pour tester qu’un Singleton retourne bien toujours la même instance, on demande à atoum de vérifier que les instances des objets sont identiques :

<?php //...
class Singleton extends atoum\test
{
    public function testGetInstance()
    {
        $this->assert
                ->object(\Singleton::getInstance())
                    ->isInstanceOf('Singleton')
                    ->isIdenticalTo(\Singleton::getInstance());
    }
}

Tests sur les exceptions

Pour tester les exceptions, contrairement à PHPUnit, atoum tire pleinement partie des fonctions anonymes disponibles en PHP 5.3 :

class ExceptionLauncher extends atoum\test
{
    public function testLaunchException ()
    {
        $exception = new \ExceptionLauncher();
        $this->assert
                 ->exception(function()use($exception){
                                $exception->launchException();
                            })
                 ->isInstanceOf('LaunchedException')
                 ->hasMessage('Message in the exception');

    }
}

Tests sur les erreurs

Alors que dans PHPUnit je passais par des Mocks, atoum propose des assertions dédiées aux tests d’erreurs :

class RaiseError extends atoum\test
{
    public function testRaiseError ()
    {
        $error = new \RaiseError();

        $this->assert->object($error);
        $this->assert
                 ->when(function()use($error){
                        $error->raise();
                 })
                 ->error('This is an error', E_USER_WARNING)
                    ->exists();
                 //Sachant qu'il est possible de ne spécifier 
                 // ni message ni type attendu.
    }
}

Tests à l’aide de Mock

Les Mocks sont bien sûrs supportés par atoum !

Avec la possibilité de générer un Mock à partir d’une interface…

class UsingWriter extends atoum\test
{
    public function testWithMockedInterface ()
    {
        $this->mockGenerator->generate('\IWriter');
        $mockIWriter = new \mock\IWriter;

        $usingWriter = new \UsingWriter();
        //La méthode setIWriter attends un objet 
        //qui implemente l'interface IWriter
        //  (setIWriter (IWriter $writer))
        $usingWriter->setIWriter($mockIWriter);

        $this->assert
                ->when(function () use($usingWriter) {
                                $usingWriter->write('hello');
                })
                ->mock($mockIWriter)
                    ->call('write')
                    ->once();
    }

… de générer un mock à partir d’un objet…

    public function testWithMockedObject ()
    {
        $this->mockGenerator->generate('\Writer');
        $mockWriter = new \mock\Writer;

        $usingWriter = new \UsingWriter();
        //La méthode setWriter attends un objet 
        //de type Writer (setWriter (Writer $writer))
        $usingWriter->setWriter($mockWriter);

        $this->assert
                ->when(function () use($usingWriter) {
                                $usingWriter->write('hello');
                })
                ->mock($mockWriter)
                    ->call('write')
                    ->once();
    }

… ou de générer un mock « libre »…

        $this->mockGenerator->generate('WriterFree');
        $mockWriter = new \mock\WriterFree;
        $mockWriter->getMockController()->write = function($text){};

        $usingWriter = new \UsingWriter();
        $usingWriter->setFreeWriter($mockWriter);

        $this->assert
                ->when(function () use($usingWriter) {
                                $usingWriter->write('hello');
                })
                ->mock($mockWriter)
                    ->call('write')
                    ->once();

Ce qui ne gâche rien

Si vous décidez de tester atoum, sachez que l’auteur est particulièrement réactif.

Le code de atoum est clair et extensible.

atoum est testé par…. atoum lui même !

Liens

4 réflexions au sujet de « Testez votre code avec atoum »

  1. Ping : Pourquoi utilisons-nous atoum pour les tests unitaires » Team Fusion

  2. Ping : PHP: Projet Silex – TDD pour le code métier « rand(0)

  3. Je up un vieu sujet, mais j’avoue que je trouve cet outil 1000 fois plus pratique que PHPUnit, que ce soit en terme de temps d’installation ou en terme d’utilisation…

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *