web-dev-qa-db-fra.com

tests unitaires et méthodes statiques

Lire et reprendre les tests unitaires, essayer de donner un sens à le post suivant qui explique les difficultés des appels de fonction statiques.

Je ne comprends pas clairement ce problème. J'ai toujours supposé que les fonctions statiques étaient une belle façon d'arrondir les fonctions utilitaires dans une classe. Par exemple, j'utilise souvent des appels de fonctions statiques pour initialiser, c'est-à-dire:

Init::loadConfig('settings.php');
Init::setErrorHandler(APP_MODE); 
Init::loggingMode(APP_MODE);

// start loading app related objects ..
$app = new App();

// Après avoir lu le billet, je vise maintenant cela à la place ...

$init = new Init();
$init->loadConfig('settings.php');
$init->loggingMode(APP_MODE);
 // etc ...

Mais, les quelques dizaines de tests que j'avais écrits pour ce cours sont les mêmes. Je n'ai rien changé et ils passent toujours tous. Est-ce que je fais quelque chose de mal?

L'auteur de l'article indique ce qui suit:

Le problème de base avec les méthodes statiques est qu'elles sont du code procédural. Je ne sais pas comment tester le code procédural unitaire. Les tests unitaires supposent que je peux instancier une partie de mon application de manière isolée. Pendant l'instanciation, je câble les dépendances avec des simulacres/amicaux qui remplacent les vraies dépendances. Avec la programmation procédurale, il n'y a rien à "câbler" car il n'y a pas d'objets, le code et les données sont séparés.

Maintenant, je comprends du post que les méthodes statiques créent des dépendances, mais je ne comprends pas intuitivement pourquoi on ne peut pas tester la valeur de retour d'une méthode statique aussi facilement qu'une méthode régulière?

J'éviterai les méthodes statiques, mais j'aurais aimé avoir une idée des QUAND les méthodes statiques sont utiles, voire pas du tout. Il semble que les méthodes statiques soient à peu près aussi mauvaises que les variables globales et devraient être évitées autant que possible.

Toute information ou lien supplémentaire sur le sujet serait grandement apprécié.

43
stefgosselin

Les méthodes statiques elles-mêmes ne sont pas plus difficiles à tester que les méthodes d'instance. Le problème survient lorsqu'une méthode - statique ou autre - appelle autre méthodes statiques car vous ne pouvez pas isoler la méthode testée. Voici un exemple de méthode typique qui peut être difficile à tester:

public function findUser($id) {
    Assert::validIdentifier($id);
    Log::debug("Looking for user $id");  // writes to a file
    Database::connect();                 // needs user, password, database info and a database
    return Database::query(...);         // needs a user table with data
}

Que voudriez-vous tester avec cette méthode?

  • Le fait de passer autre chose qu'un entier positif lance InvalidIdentifierException.
  • Database::query() reçoit l'identifiant correct.
  • Un utilisateur correspondant est renvoyé lorsqu'il est trouvé, null lorsqu'il ne l'est pas.

Ces exigences sont simples, mais vous devez également configurer la journalisation, vous connecter à une base de données, la charger avec des données, etc. La classe Database doit être seule responsable des tests qu'elle peut se connecter et interroger. La classe Log doit faire de même pour la journalisation. findUser() ne devrait pas avoir à gérer tout cela, mais il le doit car cela dépend d'eux.

Si, à la place, la méthode ci-dessus appelle des méthodes d'instance sur les instances Database et Log, le test peut passer dans des objets fictifs avec des valeurs de retour scriptées spécifiques au test en cours.

function testFindUserReturnsNullWhenNotFound() {
    $log = $this->getMock('Log');  // ignore all logging calls
    $database = $this->getMock('Database', array('connect', 'query');
    $database->expects($this->once())->method('connect');
    $database->expects($this->once())->method('query')
             ->with('<query string>', 5)
             ->will($this->returnValue(null));
    $dao = new UserDao($log, $database);
    self::assertNull($dao->findUser(5));
}

Le test ci-dessus échouera si findUser() néglige d'appeler connect(), passe la mauvaise valeur pour $id (5 Ci-dessus), ou renvoie autre chose que null. La beauté est qu'aucune base de données n'est impliquée, ce qui rend le test rapide et robuste, ce qui signifie qu'il n'échouera pas pour des raisons sans rapport avec le test, comme une défaillance du réseau ou de mauvaises données d'échantillon. Il vous permet de vous concentrer sur ce qui compte vraiment: les fonctionnalités contenues dans findUser().

51
David Harkness

Sebastian Bergmann est d'accord avec Misko Hevery et le cite fréquemment:

Le test unitaire a besoin de coutures, les coutures sont l'endroit où nous empêchons l'exécution du chemin de code normal et comment nous réalisons l'isolement de la classe testée. Les coutures fonctionnent par polymorphisme, nous remplaçons/implémentons classe/interface, puis câblons la classe testée différemment afin de prendre le contrôle du flux d'exécution. Avec les méthodes statiques, il n'y a rien à remplacer. Oui, les méthodes statiques sont faciles à appeler, mais si la méthode statique appelle une autre méthode statique, il n'y a aucun moyen de remplacer la dépendance de la méthode appelée.

Le principal problème avec les méthodes statiques est qu'elles introduisent le couplage, généralement en codant en dur la dépendance dans votre code consommateur, ce qui rend difficile leur remplacement par des talons ou des simulations dans vos tests unitaires. Cela viole le principe ouvert/fermé et le principe d'inversion de dépendance , deux des principes SOLIDES .

Vous avez tout à fait raison que la statique est considérée comme nuisible . Évite-les.

Consultez les liens pour plus d'informations s'il vous plaît.

Mise à jour: notez que bien que la statique soit toujours considérée comme nuisible, la capacité de stub et de se moquer des méthodes statiques a été supprimée à partir de PHPUnit 4.

21
Gordon

Je ne vois aucun problème lors du test de méthodes statiques (au moins aucun qui n'existe pas dans les méthodes non statiques).

  • Les objets fictifs sont passés aux classes testées à l'aide de l'injection de dépendance.
  • Mock static methods peut être passé aux classes testées en utilisant un chargeur automatique approprié ou en manipulant le include_path.
  • La liaison statique tardive traite des méthodes appelant des méthodes statiques dans la même classe.
1
Oswald