Tests unitaires

Soyez le premier à donner votre avis sur cette source.

Vue 4 213 fois - Téléchargée 195 fois

Description

Bonjour à tous,

Je présente aujourd'hui une version plus aboutie de ce code en ligne depuis peu. Il se compose de trois classes une interfaces et un index.php

Pourquoi ne pas utiliser PHPUnit ou SimpleTest ? Par goût de la simplicité.

La classe TestManager connait les deux autres classes.
Les classes de tests (que vous devez écrire !) héritant de TestUnitaire sont chargées via la méthode addClasse et lancées ensuite dans la méthode launch.
L'affichage comprend aujourd'hui une interface et une classe HtmlOutput.

Le tout étant soumis à vos appréciations. J'ai développé des choses en vitesse, pour savoir si je ne faisais pas fausse route.

A vous de jouer !

PS : La source ne présente que la classe TestManager. Ca vous donnera l'idée générale.

Source / Exemple :


<?php
final class TestManager {
	const METHOD_PREFIX = 'test_';
	const METHOD_INDENT = "\t";
	private $tests = array();
	/**

  • Pile de logs
  • /
private $logs = array(); /**
  • public function __construct()
  • /
public function __construct(){ assert_options(ASSERT_ACTIVE,1); assert_options(ASSERT_WARNING,0); assert_options(ASSERT_BAIL,0); assert_options(ASSERT_QUIET_EVAL,1); assert_options(ASSERT_CALLBACK,array($this,'hasFail')); } public function hasFail($file,$line,$code){ // Au dernier log (le [0] du tableau), j'ajoute le contexte de l'échec. $this->logs[0]['fail'] = TRUE; //array('file' => $file, 'line' => $line, 'code' => $code); } public function log($m,$args){ $log = array( 'method' => $m, 'args' => $args, 'fail' => FALSE, ); // travail anté-historique (du plus récent au plus ancien) // comme ça, les derniers tests (ceux qui nous préoccupent) occupent le haut du tableau array_unshift($this->logs,$log); } /**
  • vide les logs un par un en les affichant
  • /
public function drainLogs(TestOutput $obj){ while ($log = array_shift($this->logs)) { $obj->show($log['method'],$log['args'],$log['fail']); } } function addClasse($name){ $this->tests[] = $name; } function launch($out_class){ // instancier la sortie $out = new $out_class(); // travail anté-historique while ($current = array_shift($this->tests)) { if (is_subclass_of($current,'TestUnitaire')) { $obj = new $current($this); $l = strlen(self::METHOD_PREFIX); // annoncer la classe $out->classBegin($current); foreach (get_class_methods($obj) as $m) { if (substr($m, 0, $l) == self::METHOD_PREFIX) { // annoncer la methode $out->methodBegin($m); // ... je les appelle dans un environnement clean $obj->setUp(); $obj->$m(); $obj->tearDown(); // Pour ne pas surcharger la mémoire, à chaque fin de méthode, je dépile la pile des logs. $this->drainLogs($out); $out->methodEnd($m); }} $out->classEnd($current); } } } function generateClassTest($name, $output = 'php://output'){ $hdle = fopen($output, 'w'); fwrite($hdle, '<?php'. PHP_EOL . 'class ' . $name . 'Test extends TestUnitaire {' . PHP_EOL); // self::write_method('setUp',$hdle); self::write_method('tearDown',$hdle); foreach(get_class_methods($name) as $m) { self::write_method($m,$hdle); } fwrite($hdle, '}' . PHP_EOL); fclose($hdle); } function write_method($method_name, $handle){ fwrite($handle, self::METHOD_INDENT . 'function ' . $method_name . '(){'. PHP_EOL . self::METHOD_INDENT . '}' . PHP_EOL); } }

Conclusion :


J'attends vos regards externes pour faire avancer ce code.

Codes Sources

A voir également

Ajouter un commentaire

Commentaires

pierreSabatier
Messages postés
27
Date d'inscription
lundi 12 avril 2010
Statut
Membre
Dernière intervention
15 octobre 2011
-
Bonsoir tout le monde,

La source a été vue plus de 180 fois. Quelqu'un voudrait-il formuler un petit commentaire ?
cs_aKheNathOn
Messages postés
575
Date d'inscription
dimanche 23 décembre 2001
Statut
Membre
Dernière intervention
23 octobre 2012
-
Bonjour Pierre,

Un source très intéressant, les asserts en PHP sont assez peu utilisés donc c'est très instructif d'en voire un exemple.

Le code est très simple et j'aime ton approche KISS et justifiant le fait de ne pas passer par un framework de type PHPUnit.

Je commence par un point "obscur" sur le :

$this->_return[$this->iNbError][$config] = $$config; la liste des clés étant limitée par la signature de la fonction je ne vois pas trop l'intérêt de cette boucle.

Un simple code aurait pû suffire :

$this->_return[] = array(
'line' => $line,
'code' => $code
);

--> Pour faire un peu d'optimisations :) <--

Dans la classe Debug :

- Les variables iNbError, et _config n'ont pas besoin d'exister
A la place de iNbError, tu peux utiliser : sizeof($this->_return)

- De la manière dont tu as écrit ta classe Debug n'est pas un singleton, donc soit supprimer oInstance soit en faire un vrai : cf partie PHP 5 : http://fr.wikipedia.org/wiki/Singleton_%28patron_de_conception%29

Dans la classe TestUnitaire :

- J'aurais plutôt mis l'instanciation des asserts dans les DEBUG

- Pour permettre d'instancier les asserts à partir des DEBUG j'aurais éventuellement loggué dans Debug toutes les assertions, ainsi j'aurais également eu le log de tout ce qui est OK.

Sur le principe, il me manque un élément, c'est le contexte d’implémentation des tests unitaires : je présume qu'actuellement on doit le fait en bloc dans le construct. Au passage dans la classe TestUnitaire ton __construct devrait être final public pour éviter les effets de bords de l'héritage.

* Y'à t'il moyen d'avoir un cloisonnement des contextes avec plusieurs fonctions ...
* Si dans une exécution on veut exécuter plusieurs classes de tests à la fois ...

Si tu règle ces petits points je pense que ta classe à vraiment un bon potentiel car c'est pas vraiment intéressant de déployer, configurer et customiser PHPUnit, alors que ton code serait beaucoup plus facilement adaptable.

Bonne continuation,
akh
pierreSabatier
Messages postés
27
Date d'inscription
lundi 12 avril 2010
Statut
Membre
Dernière intervention
15 octobre 2011
-
Akhenaton, j'ai supprimé le côté brouillon de mon source.

En suivant tes indications, je développe un nouveau code. Je pense à 4 classes (ces deux-là, une de logs complet et une d'affichage).

Dans le construct de la classe mère "testUnitaire", je pourrais écrire quelque chose comme :
// Pour chacune de mes méthodes ...
foreach (get_class_methods($this) as $m) {
// ... qu'il faut appeler par CONVENTION
if (substr($m, 0, 5) == 'test_') {
// ... je les appelle dans un environnement clean
$this->setUp();
$this->$m();
$this->tearDown();
}
}

Est-ce que je vois juste ?
cs_aKheNathOn
Messages postés
575
Date d'inscription
dimanche 23 décembre 2001
Statut
Membre
Dernière intervention
23 octobre 2012
-
C'est très bien, pour les setup et teardown - ça permet de se créer un contexte de tests ISO et mieux cloisonnés - important pour éviter les effets de bords de certains tests - petit bémol quand tu sort du scope du $this le cloisonnement n'est plus géré ($_REQUEST, $_SESSION, $GLOBALS ... appels vers des variables statiques) - mais ce type d'effet de bord est très minime.

Parcontre je ne mettrait pas les appels (setUp, test_..., tearDown) dans l'instanciation de la classe de test mais je ferais une classe TestManager à part, qui elle lencerait par exemple les tests sur une classe, ou sur un ensemble de classes. L'objet devrait à mon sens être reconstruit à chaque execution de test.

La classe de log c'est bien la class Débug que tu as actuellement, elle devrait effectivement être en mode singleton et juste stocker les infos.

La classe TestManager s'occuperait de dumper les résultats en fin de test par exemple. Pour la structure de débug, faudrait rajouter un niveau de log sur la classe en cours d'execution vu que c'est multi-classes.

J'utiliserais les classes de Reflection dans le TestManager pour gérer les annotations de documentation, histoire d'associer des commentaires aux fonctions testées et de les affichers dans les résultats.

NB : Un truc utile dans le TestManager est de pouvoir générer un fichier de test unitaire en lui passant en argument un nom de classe.

Si tu peux faire le tout en moins de 1000 lignes de codes, tu tiens un framework de tests unitaires complet et surtout facile d'utilisation.

:) bon courrage
jpaul078
Messages postés
3
Date d'inscription
dimanche 3 avril 2005
Statut
Membre
Dernière intervention
20 octobre 2011
-
Bonjour,

L'idée des tests unitaires m'intéresse énormément.
Ce code fait appel à des notions que je n'ai pas l'habitude d'utiliser en php.
De ce fait, cela m'intéresse encore plus.
Mais j'ai fait un test sans succès et avant de pouvoir le faire fonctionner, j'ai peur qu'il ne me faille investir pas mal de temps pour éclaircir les notions qui m'échappent.
J'ai peu de temps de temps en ce moment et je pense qu'un petit exemple d'utilisation m'aiderais beaucoup.

Dans tout les cas merci de partager ton travail.

Cordialement,
Jean-Paul

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.