En effet, aujourd'hui, dans le cadre des tests unitaires d'ogo, j'ai eu besoin de tester non pas la valeur d'une variable, mais ce que PHP était censé afficher à l'aide de la commande echo.

Mon premier réflexe a été d'utiliser un adapter, mais echo est une construction du langage et non une fonction, et à ce titre, il n'est pas possible d'utiliser cette technique.

Pourtant, il fallait bien que je puisse tester ce que echo recevait comme argument...

Pour résoudre ce problème, ma première approche, très classique, a été d'utiliser les fonctions de bufferisation de PHP, qui permettent de stocker en mémoire l'intégralité de ce qui est envoyé à la commande echo et de ne l'afficher qu'à la demande ou à la fin du script, voir même de récupérer dans une variable la totalité des données.

Le code était alors le suivant :

<?php
...
protected function testMakeReport()
{
$score = new \ogo\test\cases\score($this);
$reporter = new \ogo\test\cases\reporter\cli();
$reporter->addScore($score);

ob_start();
$reporter->makeReport(); // méthode utilisant la commande echo
$echo = ob_get_clean();
$this->assert->string($echo)->isEmpty();
}
...
?>

A première vue, ce n'est pas mal puisque ma problèmatique est résolue, mais en réalité, c'est de la merde !

En effet, à chaque fois que j'aurais besoin de tester le comportement de \ogo\test\cases\reporter\cli::makeReport() ou d'une méthode faisant appel à une commande du type d'echo, je serais obligé de dupliquer la séquence de code suivante :

<?php
...
ob_start();
// des trucs faisant appel à echo
$echo = ob_get_clean();
$this->assert->string($echo)->...

...
?>

Et je sais pas ce qu'il en a été pour vous, mais mes professeurs m'ont appris qu'un développeur qui fait de la duplication de code méritait le bûcher, et personnellement, je suis plutôt allergique aux flammes.

De plus, je suis un fainéant n'aime pas avoir le bout des doigts carrés à force de taper sur mon clavier.

Et c'est à ce moment que j'ai pensé aux fameuses fermetures de PHP 5.3.

En effet, pourquoi ne pas créer une méthode dans ma classe de test qui accepterait une fonction anonyme comme argument, qui démarrerait la bufferisation, éxécuterait la fonction anonyme,  arrêterait la bufferisation et renverrait son contenu ?

C'est donc ce que j'ai fais, de la manière suivante.

J'ai ajouté à ma classe de test \ogo\test\cases\base la méthode getOutput(), dont le code est le suivant :

<?php
...
protected function getOutput($function)
{
ob_start();
$function();
$output = ob_get_clean();

return $output;
}
...
?>

Une fois cela fait, il ne me restait plus qu'à la mettre en oeuvre dans mon test à l'aide d'une fermeture :

<?php
...
protected function testMakeReport()
{
$score = new \ogo\test\cases\score($this);
$reporter = new \ogo\test\cases\reporter\cli();
$reporter->addScore($score);

$this->assert->string($this->getOutput(function() use ($reporter) { $reporter->makeReport(); }))->isEmpty();
}
...
?>

Et en poussant le vice un peu plus loin, il serait même possible de déporter ce traitement dans un asserter spécifique puisque finalement, la méthode getOutput() n'a rien à faire dans la classe de test.

De plus cela permettrait de réduire encore le code :

<?php
...
protected function testMakeReport()
{
$score = new \ogo\test\cases\score($this);
$reporter = new \ogo\test\cases\reporter\cli();
$reporter->addScore($score);

// nous passons maintenant directement par l'asserter
$this->assert->output(function() use ($reporter) { $reporter->makeReport(); })->isEmpty();
}
...
?>

Ainsi, la duplication de code est supprimée, je ne brulerais pas sur un bûcher, mes doigts ne deviendront pas carrés, et j'ai un moyen standard pour tester tout ce qui est envoyé à echo et aux autres commandes PHP du même type.

Pour ceux que cela intéresse, le code final est disponible dans le dépôt de ogo.

Et finalement, les fermetures, assez paradoxalement, m'ouvre tout un tas de perspectives intéressantes dans le cadre de futurs développements...