En effet, jusqu'à il y a peu, le code de ma classe de base ogoUnitTest ressemblait alors un peu à cela :

abstract class ogoUnitTest
{
...
public function run() { ... }
protected function true($var) { ... }
protected function false($var) { ... }
protected function identical($var1, $var2) { ... }
...
}

Il n'y a ici rien de bien extraordinaire, et tout peut sembler parfait.

En effet, si je veux tester une classe manipulant des fichiers, je n'ai qu'à étendre ma classe de base et lui ajouter les méthodes de vérification d'assertion dont j'ai besoin :

class ogoFileUnitTest extends ogoUnitTest
{
...
protected function fileExists($path) { ... }
...
}

Les problèmes ont commencé lorsque j'ai eu besoin de vérifier l'existence de fichiers et l'existence de méthodes précises dans une classe générées dynamiquement, dans la même classe de test unitaire.

En effet, je disposais de deux classes distinctes pour effectuer ces deux tâches, respectivement ogoFileUnitTest et ogoPhpClassUnitTest.

Or, comme l'héritage multiple est impossible en PHP, je ne pouvais pas faire une classe qui permettait de faire cela sans duplication de code.

En effet, si je dérivais ma classe ogoFileUnitTest pour lui ajouter les méthodes d'assertion permettant de vérifier l'existence de méthode dans une classe, je devais prendre le code correspondant dans ogoPhpClassUnitTest et le dupliquer dans ogoFileUnitTest, et il en allait de même si je me servais de ogoPhpClassUnitTest comme point de départ.

J'ai donc complétement changé de stratégie pour effectuer mes tests.

Plutôt que de dériver ma classe de base ogoUnitTest en fonction de mes besoins, j'ai choisi de faire de la composition pour lui ajouter les fonctionnalités qui me manquaient au niveau des assertions, et j'ai supprimé tout le code relatif à ces dernières de ma classe.

Dorénavant, lorsque je rédige mes tests, je n'utilise plus ses méthodes de vérification d'assertion, mais je créé des instances de la classe ogoTestAsserter en fonction de ce que je veux tester.

Tout le code de vérification des assertions a été reporté dans des classes externes dérivant de ogoTestAsserter, que j'utilise ponctuellement en fonction de mes besoins.

Le code d'une classe de tests unitaires ressemble alors à ceci :

class ogoFileCacheAccessorTest extends ogoUnitTest
{
protected function testGetTimestamp()
{
#Instanciation du générateur de mock
$mockGenerator = new ogoMockGenerator();

$mockGenerator->generate('ogoFile', 'ogoMockFile');

#Instanciation du controleur de mock
$fileController = new ogoMockController();
$file = new ogoMockFile(uniqid());
$file->setMockController($fileController);

#Instantiation d'une instance de la classe à tester
$fileCacheAccessor = new ogoFileCacheAccessor($file);

#Définition des valeurs de retour des méthodes de l'objet mocké
#$file->read(*) doit retourner "true"
$fileController->exists = true;

#file->getMoficationTime(*) doit retourner un entier, peut importe sa valeur
$fileController->getModificationTime = rand(1, PHP_INT_MAX);

#Définition de l'asserter qui permettra de vérifier la valeur de retour de la méthode à tester
$this->getTimestampReturn = new ogoIntegerTestAsserter($fileCacheAccessor->getTimestamp());

#Validation de la valeur de retour
$this->getTimestampReturn->isEqualTo($fileController->getModificationTime);

#Avec l'ancienne version, les deux lignes de code ci-dessus auraient été :
#$this->equal($fileController->getModificationTime, $fileCacheAccessor->getTimestamp());

#Et on fait la même chose dans le cas ou le fichier n'existe pas
$fileController->exists = false;
$this->getTimestampReturn = new ogoTypeTestAsserter($fileCacheAccessor->getTimestamp());
$this->getTimestampReturn->isNull();
}
}

J'en ai profité pour essayer de mettre en place une interface d'utilisation des classes d'assertion qui permette d'écrire les tests de manière plus compréhensible, à vous de me dire si j'ai réussi.

L'ancienne version est plus compacte, mais la modularité a un prix, et je trouve la nouvelle façon de faire plus expressive car elle permet de comprendre ce qui est testé en lisant le code de manière bien plus précise.

De mon point de vue, le code est bien plus lisible et modulaire et de plus, il n'y a plus de duplication de code, et l'empreinte mémoire des tests est réduite car seul ce qui est nécessaire est chargé en mémoire.

Seul problème, je vais être obligé à plus ou moins moyen terme de réécrire tout mes tests pour qu'ils utilisent cette nouveauté.

Cependant, rien ne presse, car l'ancienne méthode est toujours fonctionnelle.