Development

Documentation/fr_FR/book/trunk/15-Unit-and-Functional-Testing

You must first sign up to be able to contribute.

Version 7 (modified by David.Bonnet, 11 years ago)
--

Cette partie de la documentation est en cours de traduction. Cela signifie qu'elle est traduite de manière soit incomplète, soit inexacte. En attendant que cette traduction soit terminée, vous pouvez consulter la version en anglais pour des informations plus fiables.

Les tests automatisés sont l'une des plus grandes avancés dans la programmation depuis l'orienté objet. Ils favorisent particulièrement les applications basées sur le Web, ils peuvent garantir la qualité d'une application même si les versions sont continuelle. Symfony fournit une variété d'outils pour faciliter les tests automatisés, et ceux-ci sont présentés dans ce chapitre.

Les tests automatisés

N'importe quel développeur expérimenté dans la conception des applications Web se rend bien compte du temps qu'il prend pour bien faire ces tests. L'écriture des cas de tests, les lancer, et analyser les résultats sont un travail pénible. En outre, les conditions des applications du Web tendent à changer constamment, ce qui mène à une mise à jour continue des versions et d'un besoin continu de refactorer le code. Dans ce contexte, de nouvelles erreurs sont susceptibles de survenir régulièrement.

C'est pourquoi les tests automatisés sont suggérés, sinon exigés. Ils font partie d'un environnement de développement réussi. Un ensemble de "test cases" peut garantir qu'une application fait réellement ce qu'elle est censé faire. Même si les structures internes sont souvent retouchées, les tests automatisés empêchent des régressions accidentelles. En plus, ils contraignent les développeurs à écrire des tests dans un format normalisé, rigide pour être compris par un outil de test.

Les tests automatisés peuvent parfois remplacer la documentation du développeur puisqu'ils peuvent clairement illustrer ce que l'application est censée faire. Une bonne suite de tests montre quelle fonctionnalité devrait être prévue pour un ensemble de tests en entrées, et c'est une bonne manière d'expliquer le but d'une méthode.

Le framework symfony applique ce principe à lui-même. Les fonctions internes au framework sont validés par des tests automatisés. Ces tests unitaires et fonctionnels ne sont pas fournis avec la distribution de symfony, mais vous pouvez les vérifier endehors du dépôt de SVN ou les passer en revue en ligne : http://www.symfony-project.com/trac/browser/trunk/test.

Tests Unitaires et Fonctionnels

Les tests unitaires confirment qu'une fonction unitaire du code fournit une sortie correct pour une entrée donnée. Ils valident comment les fonctions et les méthodes fonctionnent dans chaque cas particulier. Les tests unitaires traitent un cas à la fois, ainsi par exemple une méthode simple peut avoir besoin de plusieurs tests unitaire si cela fonctionne différemment dans certaines situations.

Les tests fonctionnels ne valident pas une conversion simple d'entrée-sortie, mais un dispositif complet. Par exemple, un système de cache peut seulement être validé par un test fonctionnel, parce qu'il implique plus d'une étape : La première fois qu'une page est demandée, elle est fabriquée ; la deuxieme fois elle est prise dans le cache. Les tests fonctionnels valident un processus et exigent un scénario de tests. Dans symfony, vous devriez écrire les tests fonctionnels pour toutes vos actions.

Pour les interactions les plus complexes, ces deux types de tests peuvent faire défaut. Les interactions Ajax, par exemple, exigent d'un navigateur Web d'exécuter le Javascript, les tests automatique ont besoin d'un outil tiers spécial. En outre, des effets visuels peuvent seulement être validés par un humain.

Si vous avez une approche étendue aux tests automatisés, vous devrez probablement employer une combinaison de toutes ces méthodes. Comme directive, se rappeler de maintenir des tests simples et lisibles.

Le travail des tests automatisés est de comparer un résultat avec un résultat prévu. En d'autres termes, ils évaluent les affirmations (une expression comme$a == 2). La valeur d'une affirmation est l'une ou l'autre true(vraie) ou false(fausse), et il détermine si un test passe ou échoue. Le mot « assertion (affirmation) » est généralement utilisé en traitant des méthodes de tests automatisées.

La conception piloté par les Tests (Test-Driven Development)

Dans la méthodologie conception piloté par les Tests (TDD), les tests sont écrits avant le code. Lécriture des test en premier vous aides à vous concentrer sur la charge dont la fonction devrait accomplir avant de la développer réellement. C'est une bonne pratique que d'autres méthodologies recommandent, comme l'Extreme Programming (XP). Il tient compte du fait indéniable que si vous n'écrivez pas des tests unitaire en premier, vous ne les écrivez jamais.

Par exemple, imaginer que vous devez développer une fonction de texte dépouiller. La fonction enlève les espaces blancs au début et à la fin du texte, remplace les caractères non-alphabetique par des caractères soulignés, et transforme tous les caractères majuscules en lettre minuscule. Dans le développement piloté par les tests, vous penseriez à tous les cas possibles et fourniriez la première fois en exemple « entrée et sortie >> qui sont prévus pour chaque cas, suivant les indications du tableau 15-1.

Tableau 15-1 - Une liste de cas de test pour une fonction de texte dépouiller

Entrée Sortie prévu
---------------------------------------------
" foo " "foo"
" foo bar" "foo_bar"
"-)foo:..=bar?""foobar_"
"FooBar?""foobar"
"Don't foo-bar me!""don_t_foo_bar_me_"

Vous écririez les tests unitaires, les exécuteriez, et verrifiriez qu'ils échouent. Vous ajouteriez alors le code nécessaire pour manipuler le premier cas de test, exécutez les tests encore, voyez que le premier test passe, et continuez comme celui-ci pour les autres cas de test. Par la suite, quand tous les cas de tests passent, la fonction est correcte.

Une application établie avec une méthodologie pilotée par les tests finit vers le haut avec rigueur autant pour les codes de test que le code réel. Car vous ne voulez pas passer le temps en corrigeant vos cas de tests, les maintenir sera simples.

Refactorer une méthode peut créer de nouveaux bogues. C'est pourquoi, il est également de bonnes habitudes, d'exécuter tous les tests automatisés avant de déployer une nouvelle version de l'application avant sa mise en production : ceci s'appelle test de régression.

Le framework de Test Lime

Il y a beaucoup de frameworks de tests, avec peut-être le plus bien connu PhpUnit et SimpleTest. Symfony a son propre framework appelé Lime. Il est basé surTest::More Une librairie Perl, et il est conforme TAP(Test Anything Protocol : Protocole de test), ce qui signifie que le résultat des tests est montré comme indiqué dans le protocole de test (TAP), conçu pour une meilleure lisibilité des tests de sortie.

Lime fournit un support pour les tests unitaires. Elle est plus légère que d'autres frameworks de tests PHP et a plusieurs avantages :

  • Il lance des fichiers de tests dans une boite de dialogue pour éviter des effets secondaires étranges entre chaque test. Non, tous les frameworks de tests ne garantissent pas un environnement propre pour chaque test.
  • Les tests de Lime sont très lisibles, et ainsi sont le rendement des tests. Sur les systèmes compatibles, Lime emploie les couleurs en sorties dans une manière futée pour distinguer l'information importante.
  • Symfony lui-même utilise les tests lime pour les tests de non régression, ainsi quelques exemples de tests unitaires et fonctionnel peuvent être trouvés dans le code source de symfony.
  • Le noyau de lime est validé par les tests unitaires.
  • Il est écrit en PHP, il est rapide et bien est codé. Il est contenu dans un simple fichier, lime.php, sans aucune dépendance.

Les divers tests ont décrit la prochaine utilisation de la syntaxe de lime. Ils travaillent à l'extérieur de n'importe quelle installation de symfony.

Les tests unitaires et fonctionnels ne sont pas supposés être lancés en mode production. Ils sont des outils de développeur, et comme tels, ils devraient être lancé dans l'ordinateur du développeur, pas sur le serveur principal.

Tests Unitaires

Les tests unitaires de Symfony sont de simple fichier PHP contenu dans Test.php. Il se trouve dans le répertoire test/unit/ de votre application. Ils suivent une syntaxe simple et lisible.

À quoi les tests unitaires ressemblent-ils ?

Le listing 15-1 montre un ensemble typique de test unitaire pour la fonction strtolower(). Il commence par un instantiation de l'objet lime_test (vous n'avez pas besoin de vous inquiéter des paramètres maintenant). Chaque test unitaire est un appel à une méthode d'instantiation de lime_test. Le dernier paramètre de ces méthodes est toujours une chaine de caractère facultative qui sert à l'affichage explicatif du test.

Listing 15-1 - Un exemple de fichier de test unitaire, dans test/unit/strtolowerTest.php

<?php
 
include(dirname(__FILE__).'/../bootstrap/unit.php');
require_once(dirname(__FILE__).'/../../lib/strtolower.php');
 
$t = new lime_test(7, new lime_output_color());
 
// strtolower()
$t->diag('strtolower()');
$t->isa_ok(strtolower('Foo'), 'string',
    'strtolower() returns a string');
$t->is(strtolower('FOO'), 'foo',
    'strtolower() transforms the input to lowercase');
$t->is(strtolower('foo'), 'foo',
    'strtolower() leaves lowercase characters unchanged');
$t->is(strtolower('12#?@~'), '12#?@~',
    'strtolower() leaves non alphabetical characters unchanged');
$t->is(strtolower('FOO BAR'), 'foo bar',
    'strtolower() leaves blanks alone');
$t->is(strtolower('FoO bAr'), 'foo bar',
    'strtolower() deals with mixed case input');
$t->is(strtolower(''), 'foo',
    'strtolower() transforms empty strings into foo');

Lancez le test en ligne de commande avec l'option : test-unit. La ligne de commande renvoie une sortie très explicite, et elle vous aide à localiser les tests qui ont échoué et qui ont passé. Voir l'exemple de la sortie du test : Listing 15-2.

Listing 15-2 - Lancement d'un simple test unitaire en ligne de commande

> symfony test-unit strtolower

1..7
# strtolower()
ok 1 - strtolower() returns a string
ok 2 - strtolower() transforms the input to lowercase
ok 3 - strtolower() leaves lowercase characters unchanged
ok 4 - strtolower() leaves non alphabetical characters unchanged
ok 5 - strtolower() leaves blanks alone
ok 6 - strtolower() deals with mixed case input
not ok 7 - strtolower() transforms empty strings into foo
#     Failed test (.\batch\test.php at line 21)
#            got: ''
#       expected: 'foo'
# Looks like you failed 1 tests of 7.

L'include au début du Listing 15-1 est facultatif, mais il rend le fichier de test indépendant de PHP. Vous pouvez l'exécuter sans ligne de commande symfony, en appelant juste php test/unit/strtolowerTest.php.

Méthodes de test unitaire

L'objet lime_test viens avec un grand nombre de méthodes de tests, qui sont énumérés dans le Tableau 15-2.

Tableau 15-2 - Méthodes de l'objet lime_test pour les tests unitaires

MéthodeDescription
diag($msg) Produit un commentaire mais aucun test ok($test, $msg) Test une condition et passe si elle est vrai is($value1, $value2, $msg) Compare deux valeurs et passe si elles sont égales (==) isnt($value1, $value2, $msg) Compare deux valeurs et passe si elles ne sont pas égales like($string, $regexp, $msg) Test une chaine de caractères avec une expression régulière unlike($string, $regexp, $msg) Vérifie qu'une chaine de caractères avec une expression régulière qui ne passent pas cmp_ok($value1, $operator, $value2, $msg) Compare deux valeurs avec un operateur isa_ok($variable, $type, $msg) Vérifie le type d'un argument isa_ok($object, $class, $msg) Vérifie la classe d'un objet can_ok($object, $method, $msg) Vérifie l'existence d'une méthode pour un objet ou une classe is_deeply($array1, $array2, $msg) Vérifie que deux tableaux ont les mêmes valeurs include_ok($file, $msg) Valide que le fichier existe et qu'il est correctement inclus fail() Échoue toujours--utile pour examiner les exceptions pass() Passe toujours--utile pour examiner des exceptions skip($msg, $nb_tests) Comptes les tests de $nb_tests--utile pour les tests conditionnels todo() Comptes les tests--utile pour les tests qui doivent être écris

La syntaxe est tout à fait franche ; noter que la plupart des méthodes prennent un message en tant que leur dernier paramètre. Ce message est montré sur la sortie quand le test passe. En fait, la meilleure manière d'apprendre ces méthodes est de les examiner, ainsi allez voir le listing 15-3, qui les emploie toutes.

Listing 15-3 - Les méthodes de test de l'objet lime_test dans test/unit/exampleTest.php

<?php
 
include(dirname(__FILE__).'/../bootstrap/unit.php');
 
// Stub objects and functions for test purposes
class myObject
{
  public function myMethod()
  {
  }
}
 
function throw_an_exception()
{
  throw new Exception('exception thrown');
}
 
// Initialize the test object
$t = new lime_test(16, new lime_output_color());
 
$t->diag('hello world');
$t->ok(1 == '1', 'the equal operator ignores type');
$t->is(1, '1', 'a string is converted to a number for comparison');
$t->isnt(0, 1, 'zero and one are not equal');
$t->like('test01', '/test\d+/', 'test01 follows the test numbering pattern');
$t->unlike('tests01', '/test\d+/', 'tests01 does not follow the pattern');
$t->cmp_ok(1, '<', 2, 'one is inferior to two');
$t->cmp_ok(1, '!==', true, 'one and true are not identical');
$t->isa_ok('foobar', 'string', '\'foobar\' is a string');
$t->isa_ok(new myObject(), 'myObject', 'new creates object of the right class');
$t->can_ok(new myObject(), 'myMethod', 'objects of class myObject do have amyMethod method');
$array1 = array(1, 2, array(1 => 'foo', 'a' => '4'));
$t->is_deeply($array1, array(1, 2, array(1 => 'foo', 'a' => '4')),
    'the first and the second array are the same');
$t->include_ok('./fooBar.php', 'the fooBar.php file was properly included');
 
try
{
  throw_an_exception();
  $t->fail('no code should be executed after throwing an exception');
}
catch (Exception $e)
{
  $t->pass('exception catched successfully');
}
 
if (!isset($foobar))
{
  $t->skip('skipping one test to keep the test count exact in the condition', 1);
}
else
{
  $t->ok($foobar, 'foobar');
}
 
$t->todo('one test left to do');

Vous trouverez beaucoup d'autres exemples de l'utilisation de ces méthodes dans les fichiers des tests unitaire de symfony.

Vous pouvez vous demander pourquoi vous emploieriez is() par opposition à ok() ici. Le message d'erreur produit par is() est beaucoup plus explicite; il montre les deux membres du test, tandis que ok() dit juste que la condition a échoué.

Paramètres de tests

L'initialisation de l'objet lime_test prend en tant que son premier paramètre le nombre d'essais qui devraient être exécutés. Si le nombre de tests finalement exécutés diffère de ce nombre, lime vous avertit à ce sujet. Par exemple, l'ensemble des tests de sorties du listing 15-3 comme celui du listing 15-4. L'initialisation a stipulé que 16 tests étaient à couvrir, mais seulement 15 ont eu lieu réellement, ainsi la sortie vous averti comme ceci.

Listing 15-4 - L'aide au comptage des tests

> symfony test-unit example

1..16
# hello world
ok 1 - the equal operator ignores type
ok 2 - a string is converted to a number for comparison
ok 3 - zero and one are not equal
ok 4 - test01 follows the test numbering pattern
ok 5 - tests01 does not follow the pattern
ok 6 - one is inferior to two
ok 7 - one and true are not identical
ok 8 - 'foobar' is a string
ok 9 - new creates object of the right class
ok 10 - objects of class myObject do have a myMethod method
ok 11 - the first and the second array are the same
not ok 12 - the fooBar.php file was properly included
#     Failed test (.\test\unit\testTest.php at line 27)
#       Tried to include './fooBar.php'
ok 13 - exception catched successfully
ok 14 # SKIP skipping one test to keep the test count exact in the condition
ok 15 # TODO one test left to do
# Looks like you planned 16 tests but only ran 15.
# Looks like you failed 1 tests of 16.

La méthode diag() ne compte pas comme un test. Employer la pour montrer des commentaires de sorte que votre sortie de test reste organisée et lisible. D'une part, les méthodes todo() et skip() comptent en tant que tests réels. Une combinaison de pass()/fail() à l'intérieur d'un bloc try/catch compte comme un test simple.

Une stratégie bien planifié des tests doit contenir un nombre prévu de tests. Vous le trouverez très utile pour valider vos propres fichiers de tests--particulièrement dans des cas complexes où des tests sont exécutés à l'intérieur des conditions ou des exceptions. Et si le test échoue à un certain point, vous le verrez rapidement parce que le nombre final de tests exécutés ne correspondra pas avec le nombre donné pendant l'initialisation du test.

Le deuxième paramètre du constructeur est une extension objet de la classe de lime_output.La majeure partie du temps, comme le test est censée être lancé par un CLI, la sortie est un objet de type lime_output_color, tirant profit de la coloration quand celle-ci est disponible.

Les fonctions du testeur unitaire

La fonction du testeur unitaire est de lancer des tests unitaire à partir de la ligne de commande, elle s'attend à une liste de noms de tests ou à un répertoire avec les fichiers de tests. Voir le listing 15-5 pour des détails.

Listing 15-5 - Lancement des tests unitaires

// Test directory structure
test/
  unit/
    myFunctionTest.php
    mySecondFunctionTest.php
    foo/
      barTest.php

> symfony test-unit myFunction                   ## Run myFunctionTest.php
> symfony test-unit myFunction mySecondFunction  ## Run both tests
> symfony test-unit 'foo/*'                      ## Run barTest.php
> symfony test-unit '*'                          ## Run all tests (recursive)

Les fonctions du testeur unitaire

La fonction du testeur unitaire est de lancer des tests unitaire à partir de la ligne de commande, elle s'attend à une liste de noms de tests ou à un répertoire avec les fichiers de tests. Voir le listing 15-5 pour des détails.

Listing 15-5 - Lancement des tests unitaires

// Arborécense d'un répertoire de test
test/
  unit/
    myFunctionTest.php
    mySecondFunctionTest.php
    foo/
      barTest.php

> symfony test-unit myFunction                   ## Run myFunctionTest.php
> symfony test-unit myFunction mySecondFunction  ## Run both tests
> symfony test-unit 'foo/*'                      ## Run barTest.php
> symfony test-unit '*'                          ## Run all tests (recursive)

Stubs, Fixtures, and Autoloading

Dans un test unitaire, le dispositif autoloading n'est pas en activité par défaut. Chaque classe que vous employez dans un test doit être l'une ou l'autre définie dans le dossier de test ou requis comme dépendance externe. C'est pourquoi beaucoup de fichiers de test commencent par un groupe de lignes de include, comme le listing de 15-6 le démontre.

Listing 15-6 - Inclusion des Classes dans le fichier de test unitaire

<?php
 
include(dirname(__FILE__).'/../bootstrap/unit.php');

include(dirname(__FILE__).'/../../config/config.php');
require_once($sf_symfony_lib_dir.'/util/sfToolkit.class.php');

 
$t = new lime_test(7, new lime_output_color());

 
// isPathAbsolute()
$t->diag('isPathAbsolute()');
$t->is(sfToolkit::isPathAbsolute('/test'), true,
    'isPathAbsolute() returns true if path is absolute');

$t->is(sfToolkit::isPathAbsolute('\\test'), true,
    'isPathAbsolute() returns true if path is absolute');

$t->is(sfToolkit::isPathAbsolute('C:\\test'), true,
    'isPathAbsolute() returns true if path is absolute');

$t->is(sfToolkit::isPathAbsolute('d:/test'), true,
    'isPathAbsolute() returns true if path is absolute');
$t->is(sfToolkit::isPathAbsolute('test'), false,
    'isPathAbsolute() returns false if path is relative');

$t->is(sfToolkit::isPathAbsolute('../test'), false,
    'isPathAbsolute() returns false if path is relative');
$t->is(sfToolkit::isPathAbsolute('..\\test'), false,
    'isPathAbsolute() returns false if path is relative');

Dans les fichiers de tests unitaires, vous avez besoin d'instantier non seulement l'objet que vous examinez, mais également les objets dont ils dépendent au moment du test. Puisque les tests unitaires doivent demeurer indépendent des autres tests, cette indépendance peut faire échouer un test si une classe est cassée. En outre, l'installation de vrais objets peut être chère, en termes de lignes de code et en temps d'exécution. Maintenir dans l'esprit que la vitesse est cruciale dans le testeur unitaire parce qu'un développeur ce lassera rapidement d'un processus lent.

Toutes les fois que vous commencez à inclure beaucoup de scrits pour un test d'unitaire, vous pouvez avoir besoin d'un simple système d'auto-chargement. À cette fin, la classe de sfCore (qui doit être manuellement incluse) fournit une méthode d'initSimpleAutoload(), qui attend un chemin absolu comme paramètre. Toutes les classes situées sous ce chemin seront automatiquement chargées. Par exemple, si vous voulez avoir toutes les classes situées sous $sf_symfony_lib_dir/util/, commencez votre scrit de test comme ceci :

require_once($sf_symfony_lib_dir.'/util/sfCore.class.php');
sfCore::initSimpleAutoload($sf_symfony_lib_dir.'/util');

Le générateur objet Propel compte sur une longue cascade de classes, quand vous voudriez examiner l'objet Propel, l'auto-chargement sera nécessaire.Notez, pour que Propel travaille correctement, vous dervrez necessairement inclure les fichiers du répertoire vendor/propel/ (ainsi l'appel à sfCore devient sfCore::initSimpleAutoload(array(SF_ROOT_ DIR.'/lib/model', $sf_symfony_lib_dir.'/vendor/propel'));) et pour ajouter le noyau de Propel au chemin d'inclusion (on fait set_include_path($sf_symfony_lib_dir.'/vendor'.PATH_SEPARATOR.SF_ROOT_DIR.PATH_SEPARATOR.get_include_path().

Un autre domaine d'application pour les questions d'auto-chargement est l'utilisation des stubs (raccourcis). Un stub est une exécution alternative d'une classe où les vraies méthodes sont remplacées avec des données simples. Il imite le comportement de la vraie classe, mais sans son coût. Un bon exemple des stubs est un raccordement à une base de données ou une interface à un service Web. Dans le listing 15-7, les tests unitaires se fondent sur une API de la classe de WebService. Au lieu d'appeler les vrais fetch() de la méthode de classe du Webservice, les tests utilisent les stubs pour renvoyer les données aux tests.

Listing 15-7 - Utilistation des stubs dans les tests unitaires