Question

Supposons que vous ayez une méthode:

public void Save(Entity data)
{
    this.repositoryIocInstance.EntitySave(data);
}

Souhaitez-vous écrire un test unitaire?

public void TestSave()
{
    // arrange
    Mock<EntityRepository> repo = new Mock<EntityRepository>();
    repo.Setup(m => m.EntitySave(It.IsAny<Entity>());

    // act
    MyClass c = new MyClass(repo.Object);
    c.Save(new Entity());

    // assert
    repo.Verify(m => EntitySave(It.IsAny<Entity>()), Times.Once());
}

Parce que plus tard, si vous changez l'implémentation de la méthode pour en faire plus "complexe" des trucs comme:

public void Save(Entity data)
{
    if (this.repositoryIocInstance.Exists(data))
    {
        this.repositoryIocInstance.Update(data);
    }
    else
    {
        this.repositoryIocInstance.Create(data);
    }
}

... votre test unitaire échouerait, mais il ne casserait probablement pas votre application ...

Question

Devrais-je même créer des tests unitaires sur des méthodes pour lesquelles ne possède aucun type de retour * ou ** ne modifie rien en dehors de la simulation interne ?

Était-ce utile?

La solution

Il est vrai que votre test dépend de votre implémentation, ce que vous devriez éviter (bien que ce ne soit pas si simple parfois ...) et que ce n’est pas nécessairement mauvais. Toutefois, ces types de tests devraient s’arrêter même si votre modification ne rompt pas le code .

Plusieurs approches peuvent être envisagées:

  • Créez un test qui va réellement dans la base de données et vérifiez si l'état a été modifié comme prévu ( il ne sera plus un unité )
  • Créez un objet de test simulant une base de données, effectuez des opérations en mémoire (une autre implémentation pour votre instance de référentielI) et vérifiez que l'état a bien été modifié comme prévu. Les modifications apportées à l'interface du référentiel entraîneraient également des modifications pour cet objet. Mais vos interfaces ne devraient pas trop changer, non?
  • Vous considérez que tout cela est trop coûteux et utilisez votre approche, ce qui peut entraîner des tests inefficaces plus tard (mais une fois que les chances sont faibles, vous pouvez prendre le risque.)

Autres conseils

N'oubliez pas que les tests unitaires ne consistent pas uniquement à tester le code. Il s’agit de vous permettre de déterminer lorsque le comportement change .

Donc, vous avez peut-être quelque chose de trivial. Cependant, votre implémentation change et vous risquez d'avoir un effet secondaire. Vous voulez que votre suite de tests de régression vous le dise.

par exemple. Souvent, les gens disent que vous ne devriez pas tester les setters / getters car ils sont triviaux. Je ne suis pas d’accord, ce n’est pas parce que ce sont des méthodes compliquées, mais quelqu'un peut les changer par inadvertance par ignorance, par des scénarios difficiles, etc.

Au vu de tout ce que je viens de dire, je mettrais certainement en œuvre des tests pour ce qui précède (via des moqueries et / ou peut-être que cela vaut la peine de concevoir vos classes avec la testabilité à l'esprit et de les avoir avec le statut de rapport, etc.)

Posez-vous deux questions. "Quel est l'équivalent manuel de ce test unitaire?" et "vaut-il la peine d'être automatisé?". Dans votre cas, ce serait quelque chose comme:

Qu'est-ce qu'un équivalent manuel?  - démarrer le débogueur  - entrez dans " Enregistrer " méthode  - passez à l'étape suivante, assurez-vous d'être à l'intérieur de la mise en œuvre d'IRepository.EntitySave

Cela vaut-il la peine d’être automatisé? Ma réponse est "non". C'est 100% évident du code. Parmi des centaines de tests de déchets similaires, je n’en ai pas vu un qui puisse s’avérer utile.

La règle générale est que vous testez tout ce qui pourrait casser. Si vous êtes sûr que la méthode est assez simple (et le reste) pour ne pas être un problème, laissez-la sortir avec des tests.

Deuxièmement, vous devriez tester le contrat de la méthode, et non la mise en œuvre. Si le test échoue après une modification, mais pas l'application, vos tests ne sont pas appropriés. Le test doit couvrir les cas importants pour votre application. Cela devrait garantir que chaque modification de la méthode qui ne casse pas l'application n'échoue pas non plus le test.

La réponse courte à votre question est la suivante: Oui , vous devez absolument tester de telles méthodes.

Je suppose qu'il est important que la méthode Save enregistre en réalité les données. Si vous n’écrivez pas de test unitaire pour cela, comment le savez-vous?

Quelqu'un d'autre peut venir supprimer cette ligne de code qui appelle la méthode EntitySave et aucun des tests unitaires n'échouera. Plus tard, vous vous demandez pourquoi les éléments ne sont jamais conservés ...

Dans votre méthode, vous pourriez dire que toute personne supprimant cette ligne ne le ferait que si elle avait des intentions malveillantes, mais le problème est la suivante: les choses simples ne le restent pas forcément, et il vaut mieux écrire les tests unitaires avant que les choses ne deviennent compliqué.

Ce n'est pas un détail d'implémentation du fait que la méthode Save appelle EntitySave sur le référentiel. Elle fait partie du comportement attendu et constitue une partie très cruciale, si je puis me permettre. Vous voulez vous assurer que les données sont effectivement enregistrées.

Ce n'est pas parce qu'une méthode ne renvoie pas de valeur qu'elle ne vaut pas la peine d'être testée. En général, si vous observez une bonne séparation de commande / requête (CQS), toute méthode void devrait normalement modifier l'état de quelque chose .

Parfois, quelque chose est la classe elle-même, mais d'autres fois, cela peut être l'état de quelque chose d'autre. Dans ce cas, cela change l’état du référentiel, et c’est ce que vous devriez tester.

Cela s'appelle tester Inderect Outputs , au lieu des sorties normales plus normales (valeurs de retour).

Le truc, c’est d’écrire des tests unitaires pour qu’ils ne se cassent pas trop souvent. Lors de l’utilisation de Mocks, il est facile d’écrire Tests sur-spécifiés . C’est pourquoi la plupart des Dynamic Mocks (comme Moq) sont configurés par défaut en mode Stub . plusieurs fois, vous appelez une méthode donnée.

Tout cela, et bien plus encore, est expliqué dans l'excellent Modèles de test xUnit .

Une méthode qui ne renvoie aucun résultat modifie néanmoins l'état de votre application. Dans ce cas, votre test unitaire doit vérifier si le nouvel état est tel que prévu.

"Votre test unitaire échouera mais il ne casserait probablement pas votre application"

.

C’est vraiment important de le savoir. Cela peut sembler ennuyeux et trivial, mais quand une autre personne commence à maintenir votre code, elle a peut-être fait un très mauvais changement à Save et a (invraisemblablement) cassé l'application.

L'astuce consiste à hiérarchiser les priorités.

Testez les éléments importants en premier. Lorsque les choses vont lentement, ajoutez des tests pour des choses triviales.

Lorsqu'il n'y a pas d'assertion dans une méthode, vous affirmez essentiellement que des exceptions ne sont pas levées.

Je me pose également la question de savoir comment tester myMethod (). J'imagine que si vous décidez d'ajouter une valeur de retour à des fins de testabilité, celle-ci doit représenter tous les faits saillants nécessaires pour voir ce qui a changé dans l'état de l'application.

public void myMethod()

devient

 public ComplexObject myMethod() { 
 DoLotsOfSideEffects()
 return new ComplexObject { rows changed, primary key, value of each column, etc };
 }

et non

public bool myMethod()  
  DoLotsOfSideEffects()
  return true;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top