P. Tachoire et J. Muetton
ITNetwork, 6 décembre 2012
Sondage: parmi vous, qui écrit des tests ?
Pourquoi ne pas écrire de tests ?
Souvent, les débutants écrivent les tests
Ce genre d'expérience est aussi traumatisante qu'écrire son css à la fin du projet !
Ecrire un test prend effectivement du temps, mais :
echo
, var_dump
)ARGUMENT INVALIDE !
Les tests ne font pas partie du produit fini alors pourquoi prendre du temps à les écrire ?
Question légitime, mais :
echo
et var_dump
sont une façon de tester que vous retirez une fois le
scenario validé.Au final c'est beaucoup de temps gagné sur la vie du projet, on corrige tellement souvent les bugs au cours du développement, et en si peu de temps que le risque est de ne pas se rendre compte à quel point c'est rentable.
Exemple d'effet de bord: ajouter un format/la timezone à un parser de date
Les tests c'est une sorte de filet de sécurité.
Tester c'est anticiper les défauts.
Le contrat de maintenance reste le même, mais le temps passé est réduit.
De mauvais tests peuvent coûter plus cher que pas de tests du tout !
On reste simple et on ne pense à la fonctionnalité, pas au code.
Un code qui marche c'est un code à l'épreuve de la production depuis plusieurs années, pour les autres, gardez un oeil sur le bugtracker...
Les erreurs d'intégration sont repérées immédiatement, donc les nouveaux développements sont plus rapides.
Les tests c'est une sorte de documentation pour les développeurs. Toutes les méthodes présentent des cas d'utilisation avec le résultat attendu.
Il ne faut pas confondre Code complexe et code complexe construit sur des outils plus simples
Mais rappelez vous bien :
c'est Chuck qui maintient votre code.
Il connait votre adresse !
Il est important de garder en tête de rester testable, c'est même le principal objectif quand on code.
Si votre code n'est pas testable, alors:
Ecrire un test commence toujours par un cas d'utilisation, il faut le prendre comme une spécification. Un code complexe doit être spécifié, pourquoi pas via un test ?
Un exemple de test qui a permis de définir une api simple et compréhensible
function testAfterParkingDestinationIsTheNewLocation()
{
$boumbo = new Car($location = 'Clermont ferrand');
$boumbo->startEngine()
->driveTo($destination = 'Paris')
->park();
$this->assertEquals('Paris', $boumbo->getLocation());
}
Il n'est jamais trop tard, alors même si votre projet est commencé, écrivez vos premiers tests dès à présent.3>
Dans notre arsenal il y a :
Tester un module ou une fonctionnalité indépendamment du reste du programme.3>
Nous testons une seule fonctionalité dans un cas de figure prédéterminé.
Test du logiciel d'un point de vue utilisateur du composant ou du logiciel.3>
Les services externes peuvent être simulés pour aller plus vite et assurer la validité du scénario.
Test avec les différents éléments conformément à l'environnement de production.3>
Nous sortons du rôle du développeur et entrons en territoire devops, voir admin sys.
Pour chaque étape, selon les goûts et les besoins :
une visibilité permanente sur la qualité3>
c'est automatiser les outils de qualité autour du code
La méthode facile.
$ wget http://pear.phpunit.de/get/phpunit.phar
$ chmod +x phpunit.phar
Si suhosin
est installé vous devez autoriser les phar:
# /etc/php5/cli/conf.d/suhosin-allow-phar.ini
suhosin.executor.include.whitelist="phar"
Créer un composer.json
avec le contenu
{
"name": "phpunit",
"description": "PHPUnit",
"require": {
"phpunit/phpunit": "3.7.*"
},
"config": {
"bin-dir": "/usr/local/bin/"
}
}
puis
sudo ./composer.phar install
# lancer un scenario
$ phpunit Test/MyFirstTest.php
# lancer un ensemble de scénarii
$ phpunit Test/Model
# lancer la stratégie complète
$ phpunit -c phpunit.xml Test
Ce qui nous donne :
.................................................... 63 / 147 ( 42%)
.................................................... 126 / 147 ( 85%)
...........
Time: 21 seconds, Memory: 43.50Mb
OK (147 tests, 367 assertions)
Une assertion est une expression qui doit être évaluée à vrai.3>
assertTrue($result);
assertNull($result);
assertEquals($expected, $result);
assertFileExists($pathToFile);
assertCount($count, $result);
Toutes les assertions sur la documentation de phpunit3>
Par exemple:
\Text\excerpt("Je mange des carottes et des petits pois", 5);
// nous donne "Je mange des carottes et..."
ou encore :
\Text\excerpt("ITNetwork rules", 5);
// nous donne "ITNetwork rules"
Dans un fichier Test/ExcerptTest
namespace \Test\ExcerptTest;
include __DIR__ . "/../src/text.php";
class ExcerptTest extends \PHPUnit_Framework_TestCase
{
public function testTruncate()
{
$length = 5;
$given = 'Je mange des carottes et des petits pois';
$expect = 'Je mange des carottes et...';
$result = \Text\excerpt($given, $length);
$this->assertEquals($expect, $result);
}
}
Ecrivons une fonction simple pour commencer
// src/text.php
namespace Text;
function excerpt($text, $length = 10)
{
return $text;
}
$ phpunit Test/ExcerptTest.php
Qui nous donne
F
Time: 0 seconds, Memory: 2.75Mb
There was 1 failure:
1) Test\ExcerptTest\ExcerptTest::testTruncate
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'Je mange des carottes et...'
+'Je mange des carottes et des petits pois'
/home/julien/Projects/test/Test/ExcerptTest.php:15
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
Modifions comme suit :
function excerpt($text, $length = 10)
{
$words = explode(' ', $text);
$excerpt = array_slice($words, 0, $length);
$res = join(' ', $excerpt);
return $res . '...';
}
puis lançons les tests3>
PHPUnit 3.6.10 by Sebastian Bergmann.
.
Time: 0 seconds, Memory: 2.75Mb
OK (1 test, 1 assertion)
Créons une nouvelle assertion dans notre classe de tests
public function assertTruncateFiveOk($expect, $given)
{
$result = \Text\excerpt($given, 5);
$this->assertEquals($expect, $result);
}
Testons notre second cas
public function testTruncate()
{
$given = 'Je mange des carottes et des petits pois';
$expect = 'Je mange des carottes et...';
$this->assertTruncateFiveOk($expect, $given);
}
public function testTruncate2()
{
$given = 'ITNetwork rules';
$expect = 'ITNetwork rules';
$this->assertTruncateFiveOk($expect, $given);
}
Autre méthode : Les dataprovider à la rescousse3>
/** @dataProvider truncateProvider */
public function testTruncate($given, $expected)
{
$result = \Text\excerpt($given, 5);
$this->assertEquals($expected, $result);
}
et le provider
public function truncateProvider()
{
return array(
'simple test' => array(
'Je mange des carottes et des petits pois',
'Je mange des carottes et...'),
'shorter test' => array(
'ITNetwork rules', 'ITNetwork rules'),
);
}
class ExcerptTest extends \PHPUnit_Framework_TestCase
{
public function truncateProvider()
{
return array(
'simple test' => array(
'Je mange des carottes et des petits pois',
'Je mange des carottes et...'),
'shorter test' => array(
'ITNetwork rules', 'ITNetwork rules'),
'exact length' => array(
'un deux trois quatre cinq',
'un deux trois quatre cinq'),
'hyphen' => array(
'y a-t\'il un pilote dans l\'avion ?',
'y a-t\'il un pilote...'),
);
}
/* ... */
}
$ phpunit Test/ExcerptTest.php
Retourne :
.FFF
-'ITNetwork rules'
+'ITNetwork rules...'
-'un deux trois quatre cinq'
+'un deux trois quatre cinq...'
-'y a-t'il un pilote...'
+'y a-t'il un pilote dans...'
FAILURES!
Tests: 4, Assertions: 4, Failures: 3.
à vous de le faire passer :)
On veut aussi pouvoir tronquer des chaines avec du html
Ce qui donne :
public function truncateProvider()
{
return array(
/* ... */
'html' => array(
'<pre>Hello Moto & Altra´</pre>',
'Hello Moto & Altra´'),
);
}
function output_something($something) {
print $something;
}
Peut se tester via
class OutputTest extends \PHPUnit_Framework_TestCase
{
public function testOutput(
$input = "foo bar baz", $result = "foo bar baz")
{
$this->expectOutputString($result);
output_something($input);
}
}
function throw_an_exception() {
throw new \LogicException('Please implement me');
}
Peut se tester via
class OutputTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException LogicException
* @expectedExceptionMessage Please implement me
*/
public function testThrowException()
{
throw_an_exception();
}
}
Exécuter du code avant et après chaque methode de test.
class StackTest extends PHPUnit_Framework_TestCase
{
protected $stack;
protected function setUp()
{
$this->stack = array();
}
public function testEmpty()
{
$this->assertTrue(empty($this->stack));
}
}
Pour initialiser les dépendances, définir l'environnement...
Exécuter du code avant et après une classe de test.
class DatabaseTest extends PHPUnit_Framework_TestCase
{
protected static $dbh;
public static function setUpBeforeClass()
{
self::$dbh = new PDO('sqlite::memory:');
}
public static function tearDownAfterClass()
{
self::$dbh = NULL;
}
}
Configuration de la stratégie de test de son projet
<?xml version="1.0" encoding="UTF-8"?>
<phpunit>
<testsuites>
<testsuite name="Object_Freezer">
<directory>Tests</directory>
</testsuite>
</testsuites>
</phpunit>
Pour lancer les tests:
$ phpunit -c phpunit.xml Test/ExcerptTest.php
Toutes les directives dans la doc de phpunit3>
<phpunit
colors = "true"
convertErrorsToExceptions = "true"
convertNoticesToExceptions = "true"
convertWarningsToExceptions = "true"
syntaxCheck = "false"
bootstrap = "bootstrap.php.cache" >
<testsuites>
<testsuite name="Project Test Suite">
<directory>../src/*/*Bundle/Tests</directory>
<directory>../src/*/Bundle/*Bundle/Tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>../src</directory>
<exclude>
<directory>../src/*/*Bundle/Resources</directory>
<directory>../src/*/*Bundle/Tests</directory>
<directory>../src/*/Bundle/*Bundle/Resources</directory>
<directory>../src/*/Bundle/*Bundle/Tests</directory>
</exclude>
</whitelist>
</filter>
<logging>
<log type="junit" target="../build/logs/junit.xml"
logIncompleteSkipped="false"/>
</logging>
</phpunit>
https://github.com/cup-of-giraf/unittesting-sample
Table of contents | t |
---|---|
Exposé | ESC |
Autoscale | e |
Full screen slides | f |
Presenter view | p |
Source files | s |
Slide numbers | n |
Blank screen | b |
Notes | 2 |
Help | h |