Comment dois-je écrire un test pour une méthode pure qui ne renvoie rien?

13

J'ai un tas de classes qui traitent de la validation des valeurs. Par exemple, une RangeValidatorclasse vérifie si une valeur se situe dans la plage spécifiée.

Chaque classe de validateur contient deux méthodes is_valid(value):, qui renvoie Trueou en Falsefonction de la valeur, et ensure_valid(value)qui vérifie une valeur spécifiée et soit ne fait rien si la valeur est valide, ou lève une exception spécifique si la valeur ne correspond pas aux règles prédéfinies.

Il existe actuellement deux tests unitaires associés à cette méthode:

  • Celui qui transmet une valeur non valide et garantit que l'exception a été levée.

    def test_outside_range(self):
        with self.assertRaises(demo.ValidationException):
            demo.RangeValidator(0, 100).ensure_valid(-5)
    
  • Celui qui passe une valeur valide.

    def test_in_range(self):
        demo.RangeValidator(0, 100).ensure_valid(25)
    

Bien que le deuxième test fasse son travail - échoue si l'exception est levée et réussit s'il ensure_validne lance rien - le fait qu'il n'y ait pas de asserts à l'intérieur semble étrange. Quelqu'un qui lit un tel code se demanderait immédiatement pourquoi il existe un test qui semble ne rien faire.

Est-ce une pratique courante lors du test de méthodes qui ne renvoient pas de valeur et n'ont pas d'effets secondaires? Ou dois-je réécrire le test d'une manière différente? Ou simplement mettre un commentaire expliquant ce que je fais?

Arseni Mourzenko
la source
11
Point pédant, mais si vous avez une fonction qui ne prend aucun argument (sauf pour une selfréférence) et ne renvoie aucun résultat, ce n'est pas une fonction pure.
David Arno
10
@DavidArno: Ce n'est pas un point pédant, ça va droit au cœur de la question: la méthode est difficile à tester précisément parce qu'elle est impure.
Jörg W Mittag
@DavidArno Point plus pédant, vous pourriez avoir la méthode "ne rien faire" (en supposant que "ne renvoie aucun résultat" est interprété comme "retourne un type d'unité" qui est appelé voiddans de nombreuses langues et a des règles stupides.) Alternativement, vous pouvez avoir une infinité boucle (qui fonctionne même lorsque "ne renvoie aucun résultat" signifie vraiment ne renvoie aucun résultat.
Derek Elkins a quitté le SE
Il y a une fonction pure de type unit -> unitdans n'importe quelle langue qui considère l'unité comme un type de données standard au lieu de quelque chose de magique. Malheureusement, il retourne juste l'unité et ne fait rien d'autre.
Phoshi

Réponses:

21

La plupart des frameworks de test ont une assertion explicite pour "ne lance pas", par exemple Jasmine a expect(() => {}).not.toThrow();et nUnit et ses amis en ont également un.

DeadMG
la source
1
Et si le framework de test n'a pas une telle assertion, il est toujours possible de créer une méthode locale qui fait exactement la même chose, ce qui rend le code explicite. Bonne idée.
Arseni Mourzenko
7

Cela dépend fortement du langage et du framework utilisés. En termes de NUnit, il existe des Assert.Throws(...)méthodes. Vous pouvez leur passer une méthode lambda:

Assert.Throws(() => rangeValidator.EnsureValid(-5))

qui est exécuté dans le Assert.Throws. L'appel au lambda sera très probablement encapsulé par un try { } catch { }bloc et l'assertion échouera, si une exception est interceptée.

Si votre framework ne fournit pas ces moyens, vous pouvez le contourner en encapsulant l'appel par vous-même (j'écris en C #):

// assert that the method does not fail
try
{
    rangeValidator.EnsureValid(50);
}
catch(Exception e)
{
    Assert.IsTrue(false, $"Exception: {e.Message}");
}

// assert that the method does fail
try
{
    rangeValidator.EnsureValid(50);
    Assert.IsTrue(false, $"Method is expected to throw an exception");
}
catch(Exception e)
{
}

Cela rend l'intention plus claire, mais encombre le code dans une certaine mesure. (Bien sûr, vous pouvez envelopper toutes ces choses dans une méthode.) À la fin, ce sera à vous de décider.

Éditer

Comme Doc Brown l'a souligné dans les commentaires, le problème n'est pas d'indiquer que la méthode lance, mais qu'elle ne lance pas. Dans NUnit, il y a aussi une affirmation pour que

Assert.DoesNotThrow(() => rangeValidator.EnsureValid(-5))
Paul Kertscher
la source
1

Ajoutez simplement un commentaire pour expliquer pourquoi aucune assertion n'est nécessaire et pourquoi vous ne l'avez pas oublié.

Comme vous pouvez le voir dans les autres réponses, toute autre chose rend le code plus compliqué et encombré. Avec le commentaire là-bas, d'autres programmeurs connaîtront l'intention du test.

Cela étant dit, ce type de test devrait être une exception (sans jeu de mots). Si vous vous retrouvez à écrire quelque chose comme ça régulièrement, alors les tests vous disent probablement que la conception n'est pas optimale.

jhyot
la source
1

Vous pouvez également affirmer que certaines méthodes sont appelées (ou pas) correctement.

Par exemple:

public void SendEmail(User user)
     ... construct email ...
     _emailSender.Send(email);

Dans votre test:

emailSenderMock.VerifyIgnoreArgs(service =>
    service.Send(It.IsAny<Email>()),
    Times.Once
);
Gabriel Robert
la source