Tests les plus simples et abstraits

Au lieu de rejoindre


Unittest est probablement le framework d'écriture de test le plus connu en Python. Il est très facile à apprendre et facile à utiliser dans votre projet. Mais rien n'est parfait. Dans cet article, je veux parler d'une fonctionnalité qui me manque personnellement (je pense, pas une).

Un peu sur les tests unitaires


Avant de discuter (et de condamner) le framework de test, je considère qu'il est nécessaire de parler un peu des tests en général. Lorsque j'ai entendu pour la première fois l'expression «tests unitaires», j'ai pensé que c'était la responsabilité d'un service de contrôle de la qualité qui vérifie la conformité des modules logiciels avec les exigences. Imaginez ma surprise quand j'ai découvert que ces mêmes tests devraient être écrits par des programmeurs. Au début, je n'ai pas écrit de tests ... du tout. Rien ne peut remplacer ces sensations lorsque vous vous réveillez le matin et lisez de l'utilisateur «Le programme ne fonctionne pas. Quelque chose doit être fait de toute urgence. " Au début, il m'a semblé que c'était un processus tout à fait normal, jusqu'à ce que j'écrive le premier test unitaire, qui a trouvé une erreur. Les tests unitaires semblent généralement inutiles jusqu'à ce qu'ils détectent des problèmes. Maintenant, je ne peux plus valider une nouvelle fonction sans écrire un test dessus.
Mais les tests ne sont pas seulement utilisés pour vérifier que le code est correctement écrit. Voici une liste de fonctions qui, à mon avis, effectuent des tests:

  • Détection d'erreurs dans le code du programme
  • Donner aux programmeurs l'assurance que leur code fonctionne
  • Corollaire du paragraphe précédent: la possibilité de modifier le programme en toute sécurité
  • Tests - une sorte de documentation qui décrit le plus correctement le comportement du système

Dans un sens, les tests répètent la structure du programme. Les principes de construction de programmes sont-ils applicables aux tests? Je crois que oui - c'est le même programme, mais en tester un autre.

Description du problème


À un moment donné, j'ai eu l'idée d'écrire un test abstrait. Qu'est-ce que je veux dire par là? Il s'agit d'un test qui ne s'exécute pas lui-même, mais déclare des méthodes qui dépendent des paramètres définis dans les héritiers. Et puis j'ai découvert que je ne peux pas faire ça humainement en toute sérénité. Voici un exemple:

class SerializerChecker(TestCase): model = None serializer = None def test_fields_creation(self): props = TestObjectFactory.get_properties_for_model(self.model) obj = TestObjectFactory.create_test_object_for_model(self.model) serialized = self.serializer(obj) self.check_dict(serialized.data, props) 

Je pense que, même sans connaître l'implémentation de TestObjectFactory et la méthode check_dict, il est clair que props est un dictionnaire de propriétés pour un objet, obj est un objet pour lequel nous vérifions le sérialiseur. check_dict vérifie récursivement les dictionnaires pour une correspondance. Je pense que beaucoup de gens familiers avec les plus habiles diront immédiatement que ce test ne correspond pas à ma définition d'abstrait. Pourquoi? Parce que la méthode test_fields_creation s'exécutera à partir de cette classe, dont nous n'avons absolument pas besoin. Après quelques recherches d'informations, je suis arrivé à la conclusion que l'option la plus adéquate n'est pas d'hériter du SerializerChecker de TestCase, mais d'implémenter les héritiers d'une manière ou d'une autre:

 class VehicleSerializerTest(SerializerChecker, RecursiveTestCase): model = Vehicle serializer = VehicleSerialize 

RecursiveTestCase est un descendant de TestCase qui implémente la méthode check_dict.

Cette solution est moche à la fois à partir de plusieurs positions:

  • Dans la classe SerializerChecker, nous devons savoir que l'enfant doit hériter de TestCase. Cette dépendance peut causer des problèmes à ceux qui ne connaissent pas ce code.
  • L'environnement de développement croit obstinément que je me trompe, car SerializerChecker n'a pas de méthode check_dict

image

Erreur lancée par l'environnement de développement

Il peut sembler que vous pouvez simplement ajouter un stub pour check_dict et tous les problèmes ont été résolus:

 class SerializerChecker: model = None serializer = None def check_dict(self, data, props): raise NotImplementedError def test_fields_creation(self): props = TestObjectFactory.get_properties_for_model(self.model) obj = TestObjectFactory.create_test_object_for_model(self.model) serialized = self.serializer(obj) self.check_dict(serialized.data, props) 

Mais ce n'est pas une solution complète au problème:

  • En fait, nous avons créé une interface qui implémente non pas un descendant de cette classe, mais RecursiveTestCase, ce qui crée des questions raisonnables pour l'architecture.
  • TestCase possède de nombreuses méthodes assert *. Avons-nous vraiment besoin d'écrire un talon pour chaque utilisé? Semble toujours être une bonne solution?

Pour résumer


Unittest ne fournit pas la capacité raisonnable de «déconnecter» une classe héritée de TestCase. Je serais très heureux si une telle fonction était ajoutée au cadre. Comment résolvez-vous ce problème?

Source: https://habr.com/ru/post/fr447438/


All Articles