2010-07-17 3 views
4

Supposons que j'ai une fonction comme ceci:pratiques pour l'unité de refactoring testé le code

public void AddEntry(Entry entry) 
{ 
    if (entry.Size < 0) 
     throw new ArgumentException("Entry size must be greater than zero"); 
    DB.Entries.Add(entry); 
} 

Et un test unitaire correspondant:

[TestMethod] 
[ExpectedException(typeof(ArgumentException), "Entry size must be greater than zero")] 
public void AddEntry_TermSizeLessThanZero_ThrowException() 
{ 
    Entry e = new Entry(-5); 

    AddEntry(e); 
} 

Et puis je Refactor le code de validation sur:

public void AddEntry(Entry entry) 
{ 
    Validate(entry); 
    DB.Entries.Add(entry); 
} 

public void Validate(Entry entry) 
{ 
    if (entry.Size < 0) 
     throw new ArgumentException("Entry size must be greater than zero"); 
} 

Le test unitaire ne décrit plus le code de validation.

Quelle est la meilleure chose à faire dans ce cas? Est-ce que je quitte simplement Validate() pour être testé via AddEntry? Edit: pour clarifier, en supposant que j'avais une raison de rendre public le code réfracté (un peu artificiel dans cette situation), est-ce que je voudrais dupliquer le code de test pour qu'il soit complet?

Répondre

2

À ce stade, je ne voudrais pas ajouter un test à Validate(), puisque ce code est déjà atteint par un test existant. J'aime ce test car il correspond étroitement aux exigences de la classe.

Cela change lorsque vous commencez à utiliser Validate() dans d'autres fonctions de la classe ou dans d'autres classes. Validate() peut également être étendu pour valider différents problèmes, vous voudrez y remédier.

Lorsque Valider() a plusieurs appelants et validation() commence tester plusieurs conditions, vous voudrez probablement:

  • 1 Valider() essai par condition de validation
  • 1 test par appel pour valider
    • vous pouvez vérifier que quelqu'un appelle Valider en passant dans l'une des conditions de défaillance
      • envers cette approche est moins fausse abstraction
      • inconvénient est ces tests pourraient tous devenir invalide si la validation() changements de critères
      • envisager de mettre le code générant l'entrée non valide dans une méthode utilitaire de test
    • vous pouvez également vérifier que quelqu'un appelle Valider par en passant dans un objet simulé pour le validateur
      • à l'envers de cette approche est le découplage de ce qui est validé par rapport à qui fait la validation, ce qui rend les tests plus robustes par rapport à certains changements
      • inconvénient est encore une autre couche d'abstraction rend le code plus complexe

Il semble ajouter des tests de cette manière maintient une bonne couverture tout en réduisant de façon linéaire avec le nombre d'exigences testées.

+0

Beaucoup de bonnes réponses, mais je suppose qu'il n'y a pas une seule réponse à ce problème. Je n'ai jamais trop pensé à la seconde approche. Merci! –

6

Puisque vous avez fait de la méthode Validate() un membre public de votre objet (pour quelque raison que ce soit), vous devez certainement ajouter un test séparé pour celui-ci. Le test pour AddEntry doit rester en place. Un test unitaire ne devrait tester que l'interface d'une unité et ne doit pas supposer de détails de mise en œuvre. Donc, dans ce cas, vous devez laisser votre test pour AddEntry en place comme il est, car il décrit le comportement de votre interface. Mais vous ne devez pas supposer ou tester que AddEntry() appelle Validate().

+1

Vous devriez également envisager de rendre 'Validate()' privé et la meilleure pratique consiste à tester uniquement l'interface publique. – Joni

+0

Hmm, c'est en fait ce qui me déroute. C'est un peu un exemple artificiel pour faire un public Validate(), mais si j'avais une raison de réfracter une fonction et la rendre publique, cela aurait-il un sens de dupliquer le code de test? Je veux m'assurer que la fonction d'origine appelle toujours Validate(), donc j'ai encore besoin de tester au moins une partie du comportement de Validate(), et je veux aussi tester Validate() lui-même. Si je devais fournir les tests en tant que documentation pour un autre programmeur, je devrais l'informer d'utiliser Validate(), et éventuellement tester tout son comportement dans l'appelant. –

+0

* Vous ne voulez pas savoir si la fonction d'origine appelle Validate() - vous vous souciez seulement de la valider. L'implémentation de cette validation n'est pas pertinente pour votre test. Si vous avez une raison de le rendre public, alors vous devriez aussi avoir un contrat quelconque (dire ce qui devrait entrer, ce qui devrait sortir, et ce que la fonction garantit ou ne va pas se produire), et vous vous fonderiez tes tests sur ça. – cHao

2

Comme ce code évolue, il y a deux possibilités:

  1. Validate() séjours simples et agit comme une aide privée, dont les appelants externes (y compris les tests) ne doivent pas être au courant. Dans ce cas, Validate() ne devrait pas avoir ses propres tests, mais plutôt avoir son comportement testé par d'autres méthodes qui l'appellent.

  2. Validate() devient suffisamment complexe que vous devez dupliquer beaucoup de code de test pour vérifier qu'il est appelé à chaque endroit qu'il devrait être. Dans ce cas, je considérerais extraire une interface + classe pour effectuer la validation. Validate() peut ensuite être testé à fond dans sa nouvelle maison et un faux IValidator (ou autre) peut être utilisé dans votre test de AddEntry, affirmant que Validate() est appelé comme nécessaire.

Questions connexes