2009-11-13 20 views
18

J'essaie d'améliorer le nombre et la qualité des tests dans mes projets Python. L'une des difficultés que j'ai rencontrées avec l'augmentation du nombre de tests est de savoir ce que chaque test fait et comment il est censé aider à détecter les problèmes. Je sais que cette partie du suivi des tests est de meilleurs noms de tests unitaires (qui a été adressée elsewhere), mais je veux aussi comprendre comment la documentation et les tests unitaires vont de pair.Comment les tests unitaires doivent-ils être documentés?

Comment les tests unitaires peuvent-ils être documentés pour améliorer leur utilité lorsque ces tests échouent dans le futur? Concrètement, qu'est-ce qui fait qu'un bon test unitaire est un docstring?

J'apprécierais les deux réponses descriptives et des exemples de tests unitaires avec une excellente documentation. Bien que je travaille exclusivement avec Python, je suis ouvert aux pratiques d'autres langues.

Répondre

13

Je document le plus sur mes tests unitaires avec le nom de la méthode exclusivement:

testInitializeSetsUpChessBoardCorrectly() 
testSuccessfulPromotionAddsCorrectPiece() 

Pour près de 100% de mes cas de test, ce qui explique clairement ce que le test unitaire est la validation et qui est tout ce que je l'utilise. Cependant, dans quelques cas de test plus compliqués, j'ajouterai quelques commentaires tout au long de la méthode pour expliquer ce que font plusieurs lignes. J'ai déjà vu un outil (je crois que c'était pour Ruby) qui génèrerait des fichiers de documentation en analysant les noms de tous les cas de test dans un projet, mais je ne me souviens pas du nom. Si vous avez eu des cas de test pour un jeu d'échecs classe reine:

testCanMoveStraightUpWhenNotBlocked() 
testCanMoveStraightLeftWhenNotBlocked() 

l'outil pourrait générer un document HTML avec le contenu quelque chose comme ceci:

Queen requirements: 
- can move straight up when not blocked. 
- can move straight left when not blocked. 
+0

Qu'est-ce qui se passe avec les noms de vos fonctions?Je suppose que vous nommez vos tests "testFunctionName" et c'est correct, mais vous avez sérieusement une fonction nommée InitializeSetsUpChessBoardCorrectly? Je pense que "setUpChessboard" ferait très bien. –

+10

Non, le nom de la méthode explique exactement ce qu'il teste - ce scénario de test vérifie qu'initalize() configure correctement le tableau d'échecs. Boom, documentation automatique. –

+0

Haha ouais, le "test" au début est juste de l'ancien temps de JUnit, dans lequel mon cerveau est toujours bloqué. Je pourrais juste le nommer initalizeSetsUpChessBoardCorrectly() et utiliser une annotation @Test. –

4

Le nom de la méthode d'essai doit décrire exactement ce que vous êtes essai. La documentation devrait indiquer ce qui fait échouer le test.

1

Vous devez utiliser une combinaison de noms de méthodes descriptifs et de commentaires dans la chaîne doc. Un bon moyen de le faire est d'inclure une procédure de base et des étapes de vérification dans la chaîne doc. Ensuite, si vous exécutez ces tests à partir d'un cadre de test qui automatise l'exécution des tests et la collecte des résultats, vous pouvez demander au framework de consigner le contenu de la chaîne doc pour chaque méthode de test avec sa sortie stdout + stderr.

Voici un exemple de base:

class SimpelTestCase(unittest.TestCase): 
    def testSomething(self): 
     """ Procedure: 
      1. Print something 
      2. Print something else 
      --------- 
      Verification: 
      3. Verify no errors occurred 
     """ 
     print "something" 
     print "something else" 

Avoir la procédure avec le test rend beaucoup plus facile de comprendre ce que le test est en train de faire. Et si vous incluez la docstring avec la sortie de test, il devient plus facile de déterminer ce qui n'a pas fonctionné lors de la lecture des résultats. L'endroit précédent où j'ai travaillé a fait quelque chose comme ça et ça a très bien fonctionné quand des échecs se sont produits. Nous avons exécuté les tests unitaires sur chaque checkin automatiquement, en utilisant CruiseControl.

+0

http://blog.codinghorror.com/code-tells-you-how-comments-tell-you-why/ –

8

Peut-être que le problème n'est pas de savoir comment écrire des docs de test, mais comment écrire les tests eux-mêmes? Refactoriser les tests de manière à ce qu'ils soient auto-documentés peut aller loin, et votre docstring ne disparaîtra pas lorsque le code changera.

Il y a quelques choses que vous pouvez faire pour rendre les tests plus clairs:

  • noms clairs méthode d'essai descriptives & (déjà mentionné)
  • corps d'épreuve doit être (auto-documenté) claire et concise
  • résumé loin installation compliquée/démontage etc dans les méthodes
  • plus?

Par exemple, si vous avez un test comme celui-ci:

def test_widget_run_returns_0(): 
    widget = Widget(param1, param2, "another param") 
    widget.set_option(true) 
    widget.set_temp_dir("/tmp/widget_tmp") 
    widget.destination_ip = "10.10.10.99" 

    return_value = widget.run() 

    assert return_value == 0 
    assert widget.response == "My expected response" 
    assert widget.errors == None 

Vous pouvez remplacer les instructions de configuration avec un appel de méthode:

def test_widget_run_returns_0(): 
    widget = create_basic_widget() 
    return_value = widget.run() 
    assert return_value == 0 
    assert_basic_widget(widget) 

def create_basic_widget(): 
    widget = Widget(param1, param2, "another param") 
    widget.set_option(true) 
    widget.set_temp_dir("/tmp/widget_tmp") 
    widget.destination_ip = "10.10.10.99" 
    return widget 

def assert_basic_widget(): 
    assert widget.response == "My expected response" 
    assert widget.errors == None 

Notez que votre méthode de test est maintenant composé d'une série d'appels de méthodes avec des noms révélateurs d'intention, une sorte de DSL spécifique à vos tests. Un test comme celui-là a-t-il besoin de documentation? Une autre chose à noter est que votre méthode de test est principalement à un niveau d'abstraction. Quelqu'un lisant la méthode d'essai verra l'algorithme est:

  • créer un widget
  • appelant run sur le widget
  • affirmant le code fait ce que nous attendons

Leur compréhension de la méthode d'essai n'est pas brouillé par les détails de la mise en place du widget, qui est un niveau d'abstraction inférieur à la méthode de test.

La première version de la méthode de test suit le modèle Inline Setup. La deuxième version suit les modèles Creation Method et Delegated Setup.

Généralement, je suis contre les commentaires, sauf s'ils expliquent le «pourquoi» du code. La lecture du Clean Code de l'oncle Bob Martin m'a convaincu de cela. Il y a un chapitre sur les commentaires, et il y a un chapitre sur les tests. Je le recommande.

Pour en savoir plus sur les meilleures pratiques en matière de tests automatisés, consultez le document xUnit Patterns.

+0

Merci pour le ressources supplémentaires et m'aider à comprendre comment simplifier les tests eux-mêmes. Je vais certainement faire d'autres lectures sur le sujet. Merci encore! – ddbeck

0

Lorsque le test échoue (ce qui devrait être avant qu'il ne soit jamais passé), vous devriez voir le message d'erreur et être capable de dire quoi de neuf. Cela n'arrive que si vous le planifiez de cette façon.

Il s'agit entièrement de nommer la classe de test, la méthode de test et le message d'assertion. Quand un test échoue, et que vous ne pouvez pas dire ce qu'il en est de ces trois indices, alors renommez certaines choses ou cassez certaines classes de tests.

Cela ne se produit pas si le nom de l'appareil est ClassXTests et que le nom du test est TestMethodX et que le message d'erreur est "expected true, return false". C'est un signe d'écriture de test bâclée.

La plupart du temps, vous ne devriez pas avoir à lire le test ou des commentaires pour savoir ce qui s'est passé.