Test unitaire des méthodes vides?

170

Quelle est la meilleure façon de tester unitaire une méthode qui ne renvoie rien? Plus précisément en c #.

Ce que j'essaie vraiment de tester, c'est une méthode qui prend un fichier journal et l'analyse pour des chaînes spécifiques. Les chaînes sont ensuite insérées dans une base de données. Rien qui n'ait été fait auparavant mais étant TRÈS nouveau dans TDD Je me demande s'il est possible de tester ceci ou est-ce quelque chose qui n'est pas vraiment testé.

jdiaz
la source
55
Veuillez ne pas utiliser le terme «TDD» si ce n'est pas ce que vous faites. Vous faites des tests unitaires, pas TDD. Si vous faisiez du TDD, vous n'auriez jamais une question comme «comment tester une méthode». Le test existerait en premier, et ensuite la question serait: "Comment réussir ce test?" Mais si vous faisiez du TDD, votre code serait écrit pour le test (et non l'inverse), et vous finiriez essentiellement par répondre à votre propre question. Votre code serait formaté différemment en raison de TDD, et ce problème ne se produirait jamais. Juste clarifier.
Suamere

Réponses:

151

Si une méthode ne renvoie rien, il s'agit de l'un des éléments suivants

  • impératif - soit vous demandez à l'objet de faire quelque chose pour lui-même .. par exemple changer d'état (sans attendre de confirmation .. il suppose que cela sera fait)
  • informationnel - simplement notifier à quelqu'un que quelque chose s'est produit (sans attendre d'action ou de réponse) respectivement.

Méthodes impératives - vous pouvez vérifier si la tâche a été réellement effectuée. Vérifiez si le changement d'état a effectivement eu lieu. par exemple

void DeductFromBalance( dAmount ) 

peut être testé en vérifiant si le solde posté ce message est bien inférieur à la valeur initiale par dAmount

Méthodes informationnelles - sont rares en tant que membre de l'interface publique de l'objet ... donc normalement pas testées à l'unité. Cependant, si vous le devez, vous pouvez vérifier si le traitement à effectuer sur une notification a lieu. par exemple

void OnAccountDebit( dAmount )  // emails account holder with info

peut être testé en vérifiant si l'e-mail est envoyé

Publiez plus de détails sur votre méthode actuelle et les gens pourront mieux répondre.
Mise à jour : votre méthode fait 2 choses. Je l'ai en fait divisé en deux méthodes qui peuvent maintenant être testées indépendamment.

string[] ExamineLogFileForX( string sFileName );
void InsertStringsIntoDatabase( string[] );

String [] peut être facilement vérifié en fournissant à la première méthode un fichier factice et les chaînes attendues. Le second est un peu délicat ... vous pouvez soit utiliser un Mock (google ou search stackoverflow sur des frameworks moqueurs) pour imiter la DB ou frapper la DB réelle et vérifier si les chaînes ont été insérées au bon endroit. Vérifiez ce fil pour quelques bons livres ... Je recommanderais Pragmatic Unit Testing si vous êtes dans une crise.
Dans le code, il serait utilisé comme

InsertStringsIntoDatabase( ExamineLogFileForX( "c:\OMG.log" ) );
Gishu
la source
1
hey gishu, bonne réponse. L'exemple que vous avez donné ... ne sont-ils pas plus des tests d'intégration ...? et si c'est le cas, la question demeure, comment tester vraiment les méthodes de vide ... peut-être que c'est impossible?
andy
2
@andy - dépend de votre définition des «tests d'intégration». Les méthodes impératives changent généralement d'état, vous pouvez donc être vérifié par un test unitaire qui interroge l'état de l'objet. Les méthodes d'information peuvent être vérifiées par un test unitaire qui connecte un auditeur / collaborateur fictif pour s'assurer que le sujet du test émet la bonne notification. Je pense que les deux peuvent être raisonnablement testés via des tests unitaires.
Gishu
@andy la base de données peut être simulée / séparée par une interface d'accès permettant ainsi à l'action d'être testée par les données passées à l'objet fictif.
Peter Geiger
62

Testez ses effets secondaires. Ceci comprend:

  • Y a-t-il des exceptions? (Si c'est le cas, vérifiez que c'est le cas. Si ce n'est pas le cas, essayez des cas de coin qui pourraient si vous ne faites pas attention - les arguments nuls étant la chose la plus évidente.)
  • Joue-t-il bien avec ses paramètres? (S'ils sont mutables, est-ce que cela les fait muter alors que ce n'est pas le cas et vice versa?)
  • At-il le bon effet sur l'état de l'objet / type sur lequel vous l'appelez?

Bien sûr, il y a une limite à bien vous pouvez tester. Vous ne pouvez généralement pas tester avec toutes les entrées possibles, par exemple. Testez de manière pragmatique - suffisamment pour vous donner l'assurance que votre code est conçu de manière appropriée et mis en œuvre correctement, et suffisamment pour servir de documentation supplémentaire pour ce à quoi un appelant peut s'attendre.

Jon Skeet
la source
31

Comme toujours: testez ce que la méthode est censée faire!

Devrait-il changer l'état global (euh, odeur de code!) Quelque part?

Doit-il faire appel à une interface?

Doit-il lever une exception lorsqu'il est appelé avec les mauvais paramètres?

Ne devrait-il lever aucune exception lorsqu'il est appelé avec les bons paramètres?

Devrait-il ...?

David Schmitt
la source
11

Les types de retour / sous-programmes annulés sont de vieilles nouvelles. Je n'ai pas fait de type de retour Void (sauf si j'étais extrêmement paresseux) depuis 8 ans (à partir du moment où cette réponse, donc juste un peu avant que cette question ne soit posée).

Au lieu d'une méthode comme:

public void SendEmailToCustomer()

Créez une méthode qui suit le paradigme int.TryParse () de Microsoft:

public bool TrySendEmailToCustomer()

Peut-être qu'il n'y a aucune information que votre méthode a besoin de renvoyer pour une utilisation à long terme, mais renvoyer l'état de la méthode après qu'elle a effectué son travail est une grande utilité pour l'appelant.

De plus, bool n'est pas le seul type d'état. Il y a un certain nombre de fois où un sous-programme créé précédemment peut en fait retourner trois ou plusieurs états différents (bon, normal, mauvais, etc.). Dans ces cas, vous utiliseriez simplement

public StateEnum TrySendEmailToCustomer()

Cependant, bien que Try-Paradigm réponde quelque peu à cette question sur la façon de tester un retour nul, il y a aussi d'autres considérations. Par exemple, pendant / après un cycle «TDD», vous seriez «Refactoring» et vous remarquerez que vous faites deux choses avec votre méthode… brisant ainsi le «principe de responsabilité unique». Cela devrait donc être réglé en premier. Deuxièmement, vous avez peut-être identifié une dépendance ... vous touchez des données «persistantes».

Si vous effectuez les tâches d'accès aux données dans la méthode en question, vous devez refactoriser dans une architecture à n niveaux ou à n couches. Mais nous pouvons supposer que lorsque vous dites "Les chaînes sont ensuite insérées dans une base de données", vous voulez en fait dire que vous appelez une couche de logique métier ou quelque chose du genre. Ya, nous supposerons que.

Lorsque votre objet est instancié, vous comprenez maintenant que votre objet a des dépendances. C'est à ce moment que vous devez décider si vous allez effectuer une injection de dépendances sur l'objet ou sur la méthode. Cela signifie que votre constructeur ou la méthode en question a besoin d'un nouveau paramètre:

public <Constructor/MethodName> (IBusinessDataEtc otherLayerOrTierObject, string[] stuffToInsert)

Maintenant que vous pouvez accepter une interface de votre objet de niveau métier / données, vous pouvez la simuler pendant les tests unitaires et ne pas avoir de dépendances ni de craindre les tests d'intégration «accidentels».

Donc, dans votre code en direct, vous passez un IBusinessDataEtcobjet REAL . Mais dans vos tests unitaires, vous passez un IBusinessDataEtcobjet MOCK . Dans cette maquette, vous pouvez inclure des propriétés non-interface comme int XMethodWasCalledCountou quelque chose dont le ou les états sont mis à jour lorsque les méthodes d'interface sont appelées.

Ainsi, votre test unitaire passera par votre (vos) méthode (s) en question, exécutera la logique dont ils disposent et appellera une ou deux, ou un ensemble sélectionné de méthodes dans votre IBusinessDataEtcobjet. Lorsque vous faites vos assertions à la fin de votre test unitaire, vous avez maintenant deux ou trois choses à tester.

  1. L'état du «sous-programme» qui est maintenant une méthode Try-Paradigm.
  2. L'état de votre IBusinessDataEtcobjet Mock .

Pour plus d'informations sur les idées d'injection de dépendances au niveau de la construction ... en ce qui concerne les tests unitaires ... consultez les modèles de conception de Builder. Il ajoute une interface et une classe de plus pour chaque interface / classe actuelle que vous avez, mais elles sont très minuscules et fournissent d'énormes augmentations de fonctionnalités pour de meilleurs tests unitaires.

Suamere
la source
La première partie de cette excellente réponse sert de conseil général étonnant à tous les programmeurs novices / intermédiaires.
pimbrouwers
ça ne devrait pas être quelque chose comme ça public void sendEmailToCustomer() throws UndeliveredMailException?
A.Emad le
1
@ A.Emad Bonne question, mais non. Contrôler le flux de votre code en s'appuyant sur la levée d'exceptions est une mauvaise pratique connue depuis longtemps. Cependant, dans les voidméthodes, en particulier dans les langages orientés objet, cela a été la seule vraie option. La plus ancienne alternative de Microsoft est le Try-Paradigm dont je parle et les paradigmes de style fonctionnel comme Monads / Maybes. Ainsi, les commandes (dans CQS) peuvent toujours renvoyer des informations d'état précieuses au lieu de s'appuyer sur le lancement, ce qui est similaire à un GOTO(ce que nous savons est mauvais). lancer (et aller) sont lents, difficiles à déboguer et ne sont pas une bonne pratique.
Suamere
Merci d'avoir clarifier les choses. Est-ce spécifique à C # ou est-ce une mauvaise pratique en général de lever des exceptions même dans des langages comme Java et C ++?
A.Emad
9

Essaye ça:

[TestMethod]
public void TestSomething()
{
    try
    {
        YourMethodCall();
        Assert.IsTrue(true);
    }
    catch {
        Assert.IsTrue(false);
    }
}
Nathan Alard
la source
1
Cela ne devrait pas être nécessaire mais peut être fait comme tel
Nathan Alard
Bienvenue dans StackOverflow! Pensez à ajouter des explications à votre code. Je vous remercie.
Aurasphere
2
Le ExpectedAttributeest conçu pour rendre ce test plus clair.
Martin Liversage
8

Vous pouvez même l'essayer de cette façon:

[TestMethod]
public void ReadFiles()
{
    try
    {
        Read();
        return; // indicates success
    }
    catch (Exception ex)
    {
        Assert.Fail(ex.Message);
    }
}
Reyan Chougle
la source
1
C'est la manière la plus simple que je suppose.
Navin Pandit
5

cela aura un certain effet sur un objet ... demande le résultat de l'effet. S'il n'a pas d'effet visible, il ne vaut pas la peine de tester unité!

Keith Nicholas
la source
4

Vraisemblablement, la méthode fait quelque chose et ne revient pas simplement?

En supposant que c'est le cas, alors:

  1. S'il modifie l'état de son objet propriétaire, vous devez tester que l'état a changé correctement.
  2. S'il prend un objet comme paramètre et modifie cet objet, alors vous devriez tester que l'objet est correctement modifié.
  3. S'il lève des exceptions dans certains cas, vérifiez que ces exceptions sont correctement levées.
  4. Si son comportement varie en fonction de l'état de son propre objet, ou d'un autre objet, prédéfinissez l'état et testez que la méthode a le bon I via l'une des trois méthodes de test ci-dessus).

Si vous nous dites ce que fait la méthode, je pourrais être plus précis.

David Arno
la source
3

Utilisez Rhino Mocks pour définir quels appels, actions et exceptions peuvent être attendus. En supposant que vous puissiez vous moquer ou remplacer certaines parties de votre méthode. Difficile à savoir sans connaître ici quelques détails sur la méthode, ni même le contexte.

Colombe
la source
1
La réponse au test unitaire ne devrait jamais être d'obtenir un outil tiers qui le fasse pour vous. Cependant, lorsqu'une personne sait comment effectuer un test unitaire, l'utilisation d'un outil tiers pour le rendre plus facile est acceptable.
Suamere
2

Cela dépend de ce qu'il fait. S'il a des paramètres, transmettez des simulacres que vous pourrez demander plus tard s'ils ont été appelés avec le bon jeu de paramètres.

André
la source
D'accord - vérifier le comportement des simulacres testant la méthode serait une solution.
Jeff Schumacher
0

Quelle que soit l'instance que vous utilisez pour appeler la méthode void, vous pouvez simplement utiliser,Verfiy

Par exemple:

Dans mon cas, _Logc'est l'instance et LogMessagela méthode à tester:

try
{
    this._log.Verify(x => x.LogMessage(Logger.WillisLogLevel.Info, Logger.WillisLogger.Usage, "Created the Student with name as"), "Failure");
}
Catch 
{
    Assert.IsFalse(ex is Moq.MockException);
}

Les Verifylevées sont-elles une exception en raison de l'échec de la méthode que le test échouerait?

Shreya Kesharkar
la source