2009-07-02 10 views
5

j'ai donc une classe avec une méthode comme suit:Comment puis-je tester l'unité d'un détail de mise en œuvre comme la mise en cache

public class SomeClass 
{ 
    ... 

    private SomeDependency m_dependency; 

    public int DoStuff() 
    { 
     int result = 0; 
     ... 
     int someValue = m_dependency.GrabValue(); 
     ... 
     return result; 
    } 
} 

Et j'ai décidé que, plutôt que d'appeler m_dependency.GrabValue() chaque fois, je veux vraiment cache la valeur en mémoire (c'est-à-dire dans cette classe) puisque nous aurons toujours la même valeur à chaque fois (la dépendance se déclenche et récupère des données dans une table qui ne change pratiquement jamais).

J'ai cependant rencontré des problèmes en essayant de décrire ce nouveau comportement dans un test unitaire. Je l'ai essayé ce qui suit (j'utilise NUnit avec RhinoMocks):

[Test] 
public void CacheThatValue() 
{ 
    var depend = MockRepository.GeneraMock<SomeDependency>(); 

    depend.Expect(d => d.GrabValue()).Repeat.Once().Return(1); 

    var sut = new SomeCLass(depend); 
    int result = sut.DoStuff(); 
    result = sut.DoStuff(); 
    depend.VerifyAllExpectations(); 
} 

Cela ne fonctionne pas; ce test passe même sans introduire de modifications à la fonctionnalité. Qu'est-ce que je fais mal?

+2

Désolé de poser la question, mais pourquoi tester quelque chose qui est un détail d'implémentation? – Robert

Répondre

5

Je considère que la mise en cache est orthogonale à Do (ing) Stuff. Je trouverais un moyen de tirer la logique de mise en cache en dehors de la méthode, soit en changeant SomeDependency ou en l'enveloppant en quelque sorte (j'ai maintenant une idée géniale pour une classe de mise en cache basée sur des expressions lambda - yum). De cette façon, vos tests pour DoStuff n'ont pas besoin d'être modifiés, il vous suffit de vous assurer qu'ils fonctionnent avec le nouveau wrapper. Ensuite, vous pouvez tester la fonctionnalité de mise en cache de SomeDependency, ou son wrapper, indépendamment. Avec un code bien architecturé, mettre une couche de cache en place devrait être plutôt facile et ni votre dépendance ni votre implémentation ne devraient faire la différence.

Les tests unitaires ne devraient pas tester l'implémentation, ils devraient tester le comportement. En même temps, le sujet à tester devrait avoir un ensemble de comportements étroitement défini.

Pour répondre à votre question, vous utilisez un simulacre dynamique et le comportement par défaut est d'autoriser tout appel qui n'est pas configuré. Les appels supplémentaires ne font que renvoyer "0". Vous avez besoin de mettre en place une attente qu'il n'y a pas plus d'appels sont faits sur la dépendance:

depend.Expect(d => d.GrabValue()).Repeat.Once().Return(1); 
depend.Expect(d => d.GrabValue()).Repeat.Never(); 

Vous devrez peut-être entrer en mode enregistrement/lecture pour le faire fonctionner correctement.

+0

Bon point: "Les tests unitaires ne devraient pas tester l'implémentation, ils devraient tester le comportement, en même temps que le sujet testé devrait avoir un ensemble de comportements étroitement défini." Cela répond à ma question (tout comme le commentaire de Robert à ma question) – jpoh

4

Cela semble être un cas pour "les tests conduisent la conception". Si la mise en cache est un détail d'implémentation de SubDependency - et ne peut donc pas être directement testé - alors probablement certaines de ses fonctionnalités (en particulier, son comportement de mise en cache) doivent être exposées - et comme il n'est pas naturel de l'exposer dans SubDependency exposé dans une autre classe (appelons-le "Cache"). Dans Cache, bien sûr, le comportement est contractuel - public, et donc testable. Donc, les tests - et les odeurs - nous disent que nous avons besoin d'une nouvelle classe. Conception pilotée par les tests. N'est-ce pas génial?