Est-il acceptable d'avoir plusieurs assertions dans un seul test unitaire?

397

Dans le commentaire de cet excellent article , Roy Osherove a mentionné le projet OAPT conçu pour exécuter chaque affirmation dans un seul test.

Ce qui suit est écrit sur la page d'accueil du projet:

Les tests unitaires appropriés doivent échouer pour une seule raison. C'est pourquoi vous devriez utiliser un test d'assertion par unité.

Et aussi, Roy a écrit dans les commentaires:

Mon principe est généralement de tester un CONCEPT logique par test. vous pouvez avoir plusieurs assertions sur le même objet . ils seront généralement le même concept testé.

Je pense que, dans certains cas, plusieurs assertions sont nécessaires (par exemple , une assertion de garde ), mais en général, j'essaie d'éviter cela. Quel est ton opinion? Veuillez fournir un exemple concret dans lequel plusieurs assertions sont réellement nécessaires .

Restuta
la source
2
Comment vous moquez-vous sans avoir plusieurs assertions? Chaque attente sur la maquette est une affirmation en soi, y compris tout ordre d'appels que vous imposez.
Christopher Creutzig
15
J'ai déjà vu la philosophie de l'assertion par méthode abusée par le passé. Un ancien collègue a utilisé un mécanisme de succession génial pour rendre cela possible. Cela a conduit à un grand nombre de sous-classes (une par branche) et à de nombreux tests qui ont effectué le même processus d'installation / de démontage uniquement pour vérifier les différents résultats. C'était lent, difficile à lire et un grave problème de maintenance. Je ne l'ai jamais convaincu de revenir à une approche plus classique. Le livre de Gerard Meszaros parle de ce sujet en détail.
Travis Parks
3
Je pense qu’en règle générale, vous devriez essayer de minimiser le nombre d’affirmations par test. Cependant, tant que le test réduit suffisamment le problème à un endroit spécifique du code, il s'agit d'un test utile.
ConditionRacer
2
J'ai vu des cas où plusieurs RowTestassertions ont été utilisées à la place de (MbUnit) / TestCase(NUnit) pour tester divers comportements de cas marginaux. Utilisez les outils appropriés pour le travail! (Malheureusement, MSTest ne semble pas encore avoir la capacité de tester les lignes.)
GalacticCowboy
@ GalacticCowboy Vous pouvez obtenir des fonctionnalités similaires RowTestet TestCaseutiliser des sources de données de test . J'utilise un simple fichier CSV avec un grand succès.
julealgon

Réponses:

236

Je ne pense pas que ce soit nécessairement une mauvaise chose , mais j'estime que nous devrions nous efforcer de n'avoir que des assertions simples dans nos tests. Cela signifie que vous écrivez beaucoup plus de tests et que nos tests ne testent qu'une chose à la fois.

Cela dit, je dirais que peut-être que la moitié de mes tests n’ont en fait qu’une seule affirmation. Je pense que cela ne devient une odeur de code (test?) Que lorsque vous avez environ cinq assertions ou plus dans votre test.

Comment résolvez-vous plusieurs assertions?

Jaco Pretorius
la source
9
Comme cette réponse - c’est-à-dire qu’elle est OK, mais que, dans l’instance générale, elle n’est pas bonne (-:
Murph le
5
Hein? Pourquoi ferais-tu ça? l'exécution de la méthode est exactement la même?
Jgauffin
198
Un seul test d'assertion par unité est un excellent moyen de tester la capacité du lecteur à faire défiler de haut en bas.
Tom
3
Un raisonnement derrière cela? En l'état, la réponse actuelle indique simplement ce que cela devrait être, mais pas pourquoi.
Steven Jeuris
37
Fortement en désaccord. La réponse ne mentionne aucun avantage à avoir une seule affirmation, et l'inconvénient évident est que vous devez copier-coller des tests uniquement parce qu'une réponse sur Internet le dit. Si vous devez tester plusieurs champs d'un résultat ou plusieurs résultats d'une seule opération, vous devez absolument tous les affirmer dans des assertions indépendantes, car cela fournit des informations bien plus utiles que les tester dans un grand assert blob. Dans un bon test, vous testez une seule opération, pas le résultat d'une opération.
Peter
296

Les tests doivent échouer pour une seule raison, mais cela ne signifie pas toujours qu'il ne devrait y avoir qu'une seule Assertdéclaration. À mon humble avis, il est plus important de s'en tenir au modèle « organiser, agir, affirmer ».

La clé est que vous n'avez qu'une seule action, puis vous inspectez les résultats de cette action en utilisant des assertions. Mais c'est "Organiser, Agir, Assert, Fin du test ". Si vous êtes tenté de continuer à effectuer des tests en effectuant une autre action et que d'autres affirmations sont effectuées par la suite, faites-en un test séparé.

Je suis heureux de voir plusieurs déclarations d'assertion qui font partie du test de la même action. par exemple

[Test]
public void ValueIsInRange()
{
  int value = GetValueToTest();

  Assert.That(value, Is.GreaterThan(10), "value is too small");
  Assert.That(value, Is.LessThan(100), "value is too large");
} 

ou

[Test]
public void ListContainsOneValue()
{
  var list = GetListOf(1);

  Assert.That(list, Is.Not.Null, "List is null");
  Assert.That(list.Count, Is.EqualTo(1), "Should have one item in list");
  Assert.That(list[0], Is.Not.Null, "Item is null");
} 

Vous pouvez combiner ces éléments en une seule affirmation, mais ce n'est pas la même chose d'insister sur le fait que vous devriez ou devez . Il n'y a pas d'amélioration à les combiner.

Par exemple, le premier pourrait être

Assert.IsTrue((10 < value) && (value < 100), "Value out of range"); 

Mais ce n’est pas mieux - le message d’erreur est moins spécifique et n’a aucun autre avantage. Je suis sûr que vous pouvez penser à d’autres exemples dans lesquels la combinaison de deux ou trois affirmations (ou plus) en une seule condition booléenne rend plus difficile la lecture, l’altération et la difficulté de comprendre pourquoi elle a échoué. Pourquoi faire cela juste pour le plaisir d'une règle?

NB : Le code que j'écris ici est C # avec NUnit, mais les principes seront valables pour d'autres langages et frameworks. La syntaxe peut être très similaire aussi.

Anthony
la source
32
La clé est que vous n'avez qu'une seule action, puis vous inspectez les résultats de cette action en utilisant des assertions.
Amitābha
1
Je pense qu’il est également intéressant d’avoir plusieurs assertions, si Arranger coûte cher.
Rekshino
1
@Rekshino, si organiser prend du temps, nous pouvons partager le code de l'organisation, par exemple en l'insérant dans la routine d'initialisation du test.
Shaun Luttin
2
Donc, si je compare à la réponse de Jaco, le "seul affirmation" devient "un seul groupe d’affirmations", ce qui est plus logique pour moi.
Walfrat le
2
C'est une bonne réponse, mais je ne suis pas d'accord pour dire qu'une seule affirmation n'est pas meilleure. Le mauvais exemple d’affirmation n’est pas meilleur, mais cela ne signifie pas qu’une seule affirmation ne serait pas meilleure si elle était bien faite. De nombreuses bibliothèques autorisent des assertions / correspondeurs personnalisés afin de pouvoir créer quelque chose s'il n'est pas déjà présent. Par exemple Assert.IsBetween(10, 100, value), l'impression Expected 8 to be between 10 and 100 vaut mieux que deux affirmations distinctes à mon avis. Vous pouvez certes affirmer que ce n'est pas nécessaire, mais il est généralement utile de se demander s'il est facile de le réduire à une seule affirmation avant d'en faire tout un ensemble.
Thor84no
85

Je n'ai jamais pensé que plus d'une affirmation était une mauvaise chose.

Je le fais tout le temps:

public void ToPredicateTest()
{
    ResultField rf = new ResultField(ResultFieldType.Measurement, "name", 100);
    Predicate<ResultField> p = (new ConditionBuilder()).LessThanConst(400)
                                                       .Or()
                                                       .OpenParenthesis()
                                                       .GreaterThanConst(500)
                                                       .And()
                                                       .LessThanConst(1000)
                                                       .And().Not()
                                                       .EqualsConst(666)
                                                       .CloseParenthesis()
                                                       .ToPredicate();
    Assert.IsTrue(p(ResultField.FillResult(rf, 399)));
    Assert.IsTrue(p(ResultField.FillResult(rf, 567)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 400)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 666)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 1001)));

    Predicate<ResultField> p2 = (new ConditionBuilder()).EqualsConst(true).ToPredicate();

    Assert.IsTrue(p2(new ResultField(ResultFieldType.Confirmation, "Is True", true)));
    Assert.IsFalse(p2(new ResultField(ResultFieldType.Confirmation, "Is False", false)));
}

Ici, j'utilise plusieurs assertions pour m'assurer que des conditions complexes peuvent être transformées en prédicat attendu.

Je ne teste qu'une unité (la ToPredicateméthode), mais je couvre tout ce à quoi je peux penser dans le test.

Matt Ellen
la source
46
Plusieurs assertions sont mauvaises à cause de la détection d'erreur. Si vous avez échoué à votre première Assert.IsTrue, les autres assertions ne seront pas exécutées et vous ne recevrez aucune information de leur part. D'un autre côté, si vous aviez 5 tests au lieu de 1 avec 5, vous pourriez obtenir quelque chose d'utile
Sly
6
Pensez-vous que cela reste mauvais si toutes les assertions testent le même type de fonctionnalité? Comme ci-dessus, l'exemple teste les conditions et si cela échoue, corrigez-le. Cela vous importe-t-il que vous manquiez les 2 derniers assertions si un précédent échouait?
grincer des dents le
106
Je règle mes problèmes un à la fois. Donc, le fait que le test puisse échouer plus d'une fois ne me dérange pas. Si je les séparais, j'aurais les mêmes erreurs, mais toutes en même temps. Je trouve plus facile de régler les choses une étape à la fois. Je reconnais que dans ce cas, les deux dernières affirmations pourraient probablement être reformulées dans leur propre test.
Matt Ellen
19
Votre cas est très représentatif, c'est pourquoi NUnit a un attribut supplémentaire TestCase - nunit.org/?p=testCase&r=2.5
Restuta
10
La structure de test de Google C ++ a ASSERT () et EXPECT (). ASSERT () s'arrête en cas d'échec pendant que EXPECT () continue. C'est très pratique lorsque vous souhaitez valider plus d'une chose dans le test.
ratkok
21

Lorsque j'utilise des tests unitaires pour valider un comportement de haut niveau, je mets absolument plusieurs assertions dans un seul test. Voici un test que j'utilise en réalité pour un code de notification d'urgence. Le code qui s'exécute avant le test met le système dans un état dans lequel, si le processeur principal est exécuté, une alarme est envoyée.

@Test
public void testAlarmSent() {
    assertAllUnitsAvailable();
    assertNewAlarmMessages(0);

    pulseMainProcessor();

    assertAllUnitsAlerting();
    assertAllNotificationsSent();
    assertAllNotificationsUnclosed();
    assertNewAlarmMessages(1);
}

Il représente les conditions qui doivent exister à chaque étape du processus pour que je sois confiant que le code se comporte comme prévu. Si une seule assertion échoue, peu m'importe que les assertions restantes ne soient même pas exécutées; étant donné que l'état du système n'est plus valide, ces affirmations ultérieures ne me diraient rien de précieux. * En cas d' assertAllUnitsAlerting()échec, je ne saurais pas quoi faire du assertAllNotificationSent()succès OU échec jusqu'à ce que je détermine la cause de l'erreur précédente. et corrigé.

(* - D'accord, ils pourraient éventuellement être utiles pour résoudre le problème. Mais l'information la plus importante, à savoir que le test a échoué, a déjà été reçue.)

BlairHippo
la source
Quand vous faites cela, vous devriez mieux utiliser les frameworks de test avec des tests dépendants, c'est mieux (par exemple, le test supporte cette fonctionnalité)
Kemoda
8
J'écris des tests comme celui-ci aussi pour que vous puissiez être sûr de ce que le code fait et des changements d'état, je ne pense pas qu'il s'agisse d'un test unitaire, mais d'un test d'intégration.
mdma
Quelles sont vos opinions sur le refactoring dans un assertAlarmStatus (int numberOfAlarmMessages) ;?
Borjab
1
Vos affirmations donneraient de très bons noms de test.
Bjorn
Il est préférable de laisser le test s'exécuter, même sur une entrée invalide. Cela vous donne plus d'informations de cette façon (et surtout si cela passe quand vous ne vous y attendiez pas).
CurtainDog
8

Une autre raison pour laquelle je pense que plusieurs assertions dans une méthode n'est pas une mauvaise chose est décrite dans le code suivant:

class Service {
    Result process();
}

class Result {
    Inner inner;
}

class Inner {
    int number;
}

Dans mon test, je veux simplement tester que service.process()renvoie le nombre correct dans Innerles instances de classe.

Au lieu de tester ...

@Test
public void test() {
    Result res = service.process();
    if ( res != null && res.getInner() != null ) Assert.assertEquals( ..., res.getInner() );
}

Je fais

@Test
public void test() {
    Result res = service.process();
    Assert.notNull(res);
    Assert.notNull(res.getInner());
    Assert.assertEquals( ..., res.getInner() );
}
Betlista
la source
2
Et c'est une bonne chose, vous ne devriez pas avoir de logique conditionnelle dans vos tests. Cela rend le test plus complexe et moins lisible. Et je suppose que Roy a bien expliqué dans son article que plusieurs affirmations sur un même objet sont acceptables la plupart du temps. Donc, ceux que vous avez sont juste des affirmations de garde et c'est bien de les avoir.
Restuta
2
Vos Assert.notNulls sont redondants, votre test échouera avec un NPE s’ils sont nuls.
Sara
1
De plus, votre premier exemple (avec le if) passera si resisnull
sara le
1
@kai notNull's sont redondants, je suis d'accord, mais je pense qu'il est plus propre d'avoir l'affirmation (et si je ne suis pas paresseux aussi avec un message approprié) au lieu d'exception ...
Betlista
1
Une assertion manquée génère également une exception et, dans les deux cas, vous obtenez un lien direct vers la rangée exacte qui l'a jeté avec une trace de pile correspondante. Personnellement, je préférerais ne pas encombrer le test avec des conditions préalables qui seront vérifiées de toute façon. Je préférerais un onel à la Assert.assertEquals(..., service.process().getInner());, possible avec des variables extraites si la ligne devient "trop ​​longue"
sara
6

Je pense qu'il y a beaucoup de cas où écrire plusieurs assertions est valide dans la règle qu'un test ne doit échouer que pour une seule raison.

Par exemple, imaginez une fonction qui analyse une chaîne de date:

function testParseValidDateYMD() {
    var date = Date.parse("2016-01-02");

    Assert.That(date.Year).Equals(2016);
    Assert.That(date.Month).Equals(1);
    Assert.That(date.Day).Equals(0);
}

Si le test échoue, c'est pour une raison unique, l'analyse est incorrecte. Si vous estimez que ce test peut échouer pour trois raisons différentes, votre définition de "une seule raison" serait trop fine.

Pete
la source
3

Je ne connais aucune situation dans laquelle il serait judicieux d'insérer plusieurs assertions dans la méthode [Test] elle-même. La raison principale pour laquelle les gens aiment avoir plusieurs assertions est qu'ils essaient d'avoir une classe [TestFixture] pour chaque classe testée. Au lieu de cela, vous pouvez diviser vos tests en plusieurs classes [TestFixture]. Cela vous permet de voir de différentes manières que le code n'a peut-être pas réagi comme prévu, au lieu de celui où la première assertion a échoué. Pour ce faire, vous devez tester au moins un répertoire par classe contenant de nombreuses classes [TestFixture]. Chaque classe [TestFixture] serait nommée d'après l'état spécifique d'un objet que vous allez tester. La méthode [SetUp] mettra l'objet dans l'état décrit par le nom de la classe. Ensuite, vous avez plusieurs méthodes [Test], chacune affirmant différentes choses que vous vous attendriez à être vraies, compte tenu de l'état actuel de l'objet. Chaque méthode [Test] porte le nom de la chose qu’elle est en train d’affirmer, sauf peut-être le nom du concept plutôt que simplement une lecture anglaise du code. Ensuite, chaque implémentation de méthode [Test] n'a besoin que d'une seule ligne de code dans laquelle elle affirme quelque chose. Un autre avantage de cette approche est que cela rend les tests très lisibles car il devient très clair ce que vous testez et ce que vous attendez en regardant simplement les noms de classe et de méthode. Cela s’améliorera également au fur et à mesure que vous commencerez à comprendre tous les petits cas que vous voulez tester et à trouver des bogues. sauf peut-être qu'il pourrait être nommé d'après le concept au lieu d'une lecture en anglais du code. Ensuite, chaque implémentation de méthode [Test] n'a besoin que d'une seule ligne de code dans laquelle elle affirme quelque chose. Un autre avantage de cette approche est que cela rend les tests très lisibles car il devient très clair ce que vous testez et ce que vous attendez en regardant simplement les noms de classe et de méthode. Cela s’améliorera également au fur et à mesure que vous commencerez à comprendre tous les petits cas que vous voulez tester et à trouver des bogues. sauf peut-être qu'il pourrait être nommé d'après le concept au lieu d'une lecture en anglais du code. Ensuite, chaque implémentation de méthode [Test] n'a besoin que d'une seule ligne de code dans laquelle elle affirme quelque chose. Un autre avantage de cette approche est que cela rend les tests très lisibles car il devient très clair ce que vous testez et ce que vous attendez en regardant simplement les noms de classe et de méthode. Cela s’améliorera également au fur et à mesure que vous commencerez à comprendre tous les petits cas que vous voulez tester et à trouver des bogues. et ce que vous attendez simplement en regardant les noms de classe et de méthode. Cela s’améliorera également au fur et à mesure que vous commencerez à comprendre tous les petits cas que vous voulez tester et à trouver des bogues. et ce que vous attendez simplement en regardant les noms de classe et de méthode. Cela s’améliorera également au fur et à mesure que vous commencerez à comprendre tous les petits cas que vous voulez tester et à trouver des bogues.

Cela signifie généralement que la dernière ligne de code à l'intérieur de la méthode [SetUp] doit stocker une valeur de propriété ou une valeur de retour dans une variable d'instance privée de [TestFixture]. Vous pouvez ensuite affirmer plusieurs choses différentes à propos de cette variable d'instance à partir de différentes méthodes [Test]. Vous pouvez également faire des assertions sur les propriétés différentes de l'objet à tester, à présent qu'il se trouve dans l'état souhaité.

Parfois, vous devez faire des assertions en cours de route au fur et à mesure que l'objet à tester est dans l'état souhaité, afin de vous assurer de ne pas vous perdre avant de le placer dans l'état souhaité. Dans ce cas, ces assertions supplémentaires doivent apparaître dans la méthode [SetUp]. Si quelque chose ne va pas dans la méthode [SetUp], il sera clair que quelque chose n'allait pas avec le test avant que l'objet n'atteigne l'état souhaité que vous vouliez tester.

Un autre problème que vous pouvez rencontrer est peut-être que vous testez une exception que vous vous attendiez à recevoir. Cela peut vous inciter à ne pas suivre le modèle ci-dessus. Cependant, vous pouvez toujours y parvenir en attrapant l'exception à l'intérieur de la méthode [SetUp] et en la stockant dans une variable d'instance. Cela vous permettra d'affirmer différentes choses à propos de l'exception, chacune dans sa propre méthode [Test]. Vous pouvez ensuite également affirmer d'autres choses à propos de l'objet testé pour vous assurer qu'il n'y a aucun effet secondaire non souhaité de la part de l'exception levée.

Exemple (cela serait divisé en plusieurs fichiers):

namespace Tests.AcctTests
{
    [TestFixture]
    public class no_events
    {
        private Acct _acct;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
        }

        [Test]
        public void balance_0() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_0
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(0m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_negative
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(-0.01m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }
}
INTPnerd
la source
Comment gérez-vous l'entrée TestCase dans ce cas?
Simon Gillbee
Donc, dans mon organisation actuelle, nous avons plus de 20 000 tests unitaires très similaires à ce que vous montrez. C'est un cauchemar. Une grande partie du code de configuration de test est copié / collé, ce qui entraîne une configuration de test incorrecte et des tests non valides qui réussissent. Pour chaque [Test]méthode, cette classe est ré-instanciée et la [SetUp]méthode est exécutée à nouveau. Cela élimine le .NET Garbage Collector et ralentit considérablement les tests: plus de 5 minutes localement, plus de 20 minutes sur le serveur de génération. Les tests de 20K devraient durer environ 2 à 3 minutes. Je ne recommanderais pas ce style de test, en particulier pour une suite de tests de grande taille.
Fourpastmidnight le
@fourpastmidnight la plupart de ce que vous avez dit semble être une critique valable, mais le problème de copier et coller du code d'installation et donc d'être faux, ce n'est pas un problème de structure mais de programmeurs irresponsables (ce qui pourrait être le résultat de gestionnaires irresponsables ou d'un environnement négatif plus que les mauvais programmeurs). Si les utilisateurs ne font que copier et coller le code et espèrent qu'il est correct et ne se donnent pas la peine de le comprendre, quel que soit le contexte, peu importe la raison, ils doivent soit être formés pour ne pas le faire, soit être abandonnés s'ils ne le peuvent pas. qualifié. Cela va à l’encontre de tout bon principe de programmation.
still_dreaming_1
Mais en général, je suis d’accord, c’est un excès de folie qui entraînera beaucoup de ballonnements, de bagages et de duplications qui engendrera toutes sortes de problèmes. J'avais l'habitude d'être fou et de recommander des choses comme ça. C'est ce que j'espère pouvoir dire tous les jours à mon sujet la veille car cela signifie que je n'arrête jamais de trouver de meilleures façons de faire les choses.
still_dreaming_1
@ still_dreaming_1 wrt "programmeurs irresponsables": Je conviens que ce comportement est un problème majeur. Cependant, ce type de structure de test invite réellement à ce type de comportement, pour le meilleur ou pour le pire. Mis à part les mauvaises pratiques de développement, ma principale objection à cette forme est qu’elle tue vraiment les performances des tests. Il n'y a rien de pire qu'une suite de tests lente. Une suite de tests lente signifie que les utilisateurs n'exécutent pas les tests localement et sont même tentés de les ignorer pour les versions intermédiaires - là encore, un problème de personnel, mais il arrive - ce qui peut être évité en veillant à ce que vos tests soient rapides avec.
Fourpastmidnight le
2

Avoir plusieurs assertions dans le même test n'est un problème que lorsque le test échoue. Ensuite, vous devrez peut-être déboguer le test ou analyser l'exception pour déterminer l'assertion qui échoue. Avec une affirmation dans chaque test, il est généralement plus facile de déterminer ce qui ne va pas.

Je ne peux pas penser à un scénario dans lequel plusieurs assertions sont réellement nécessaires , car vous pouvez toujours les réécrire en tant que conditions multiples dans la même assertion. Cela peut toutefois être préférable si, par exemple, vous avez plusieurs étapes pour vérifier les données intermédiaires entre les étapes plutôt que de risquer que les étapes ultérieures se bloquent en raison d'une mauvaise entrée.

Guffa
la source
1
Si vous combinez plusieurs conditions en une seule et même assertion, alors en cas d'échec, tout ce que vous savez, c'est que celle-ci a échoué. Avec plusieurs assertions, vous connaissez spécifiquement certaines d’entre elles (celles pouvant aller jusqu’à l’échec). Pensez à vérifier si un tableau retourné contient une seule valeur: cochez-le pour indiquer qu'il n'est pas nul, qu'il contient exactement un élément, puis la valeur de cet élément. (En fonction de la plate-forme), le fait de vérifier immédiatement la valeur peut donner une déréférence NULL (moins utile que l’échec de l’assertion NULL) et ne pas vérifier la longueur du tableau.
Richard
@Richard: Obtenir un résultat puis extraire quelque chose de ce résultat serait un processus en plusieurs étapes, j'ai donc couvert cela dans le deuxième paragraphe de la réponse.
Guffa
2
Règle générale: si vous avez plusieurs assertions dans un test, chacune d'elles doit avoir un message différent. Alors vous n'avez pas ce problème.
Anthony
Et en utilisant un programme de test de qualité tel que NCrunch, vous verrez exactement sur quelle ligne le test a échoué, à la fois dans le code de test et dans le code testé.
Fourpastmidnight le
2

Si votre test échoue, vous ne saurez pas si les affirmations suivantes se briseront également. Cela signifie souvent que vous manquerez d'informations précieuses pour déterminer la source du problème. Ma solution consiste à utiliser une assertion mais avec plusieurs valeurs:

String actual = "val1="+val1+"\nval2="+val2;
assertEquals(
    "val1=5\n" +
    "val2=hello"
    , actual
);

Cela me permet de voir toutes les assertions échouées à la fois. J'utilise plusieurs lignes, car la plupart des IDE affichent côte à côte les différences de chaînes dans une boîte de dialogue de comparaison.

Aaron Digulla
la source
2

Si vous avez plusieurs assertions dans une même fonction de test, je m'attends à ce qu'elles soient directement pertinentes pour le test que vous effectuez. Par exemple,

@Test
test_Is_Date_segments_correct {

   // It is okay if you have multiple asserts checking dd, mm, yyyy, hh, mm, ss, etc. 
   // But you would not have any assert statement checking if it is string or number,
   // that is a different test and may be with multiple or single assert statement.
}

Avoir beaucoup de tests (même si vous estimez que c'est probablement un excès) n'est pas une mauvaise chose. Vous pouvez soutenir que les tests essentiels et essentiels sont plus importants. Ainsi, lorsque vous effectuez une assertion, assurez-vous que vos instructions d'assertion sont correctement placées plutôt que de vous inquiéter de trop d'assertions multiples. Si vous en avez besoin de plusieurs, utilisez-en plusieurs.

hagubear
la source
1

L'objectif du test unitaire est de vous fournir le plus d'informations possible sur ce qui ne fonctionne pas, mais également de vous aider à identifier avec précision les problèmes les plus fondamentaux en premier. Lorsque vous savez logiquement qu'une assertion échouera en raison de l'échec d'une autre assertion ou, en d'autres termes, qu'il existe une relation de dépendance entre les tests, il est alors logique de les convertir en assertions multiples au sein d'un même test. Cela présente l'avantage de ne pas ajouter aux résultats des tests des erreurs évidentes qui auraient pu être éliminées si nous avions renoncé lors de la première assertion dans un seul test. Dans le cas où cette relation n'existe pas, la préférence serait naturellement de séparer ces assertions en tests individuels car sinon, la découverte de ces échecs nécessiterait plusieurs itérations de tests pour résoudre tous les problèmes.

Si vous concevez également les unités / classes de telle sorte que des tests trop complexes doivent être écrits, cela allégera le fardeau lors des tests et favorisera probablement une meilleure conception.

jpierson
la source
1

Oui, il est correct d’avoir plusieurs assertions tant qu’un test en échec vous fournit suffisamment d’informations pour pouvoir diagnostiquer l’échec. Cela dépendra de ce que vous testez et des modes de défaillance.

Les tests unitaires appropriés doivent échouer pour une seule raison. C'est pourquoi vous devriez utiliser un test d'assertion par unité.

Je n'ai jamais trouvé que de telles formulations étaient utiles (une classe devrait avoir une raison de changer, c'est un exemple d'adage aussi inutile). Considérons une assertion que deux chaînes sont égales, ceci équivaut sémantiquement à affirmer que la longueur des deux chaînes est la même et que chaque caractère à l’index correspondant est égal.

Nous pourrions généraliser et dire que tout système de plusieurs assertions pourrait être réécrit en une seule assertion, et que toute assertion unique pourrait être décomposée en un ensemble d'assertions plus petites.

Alors, concentrez-vous uniquement sur la clarté du code et des résultats du test, et laissez-le guider le nombre d'assertions que vous utilisez plutôt que l'inverse.

CurtainDog
la source
0

La réponse est très simple: si vous testez une fonction qui modifie plus d'un attribut, du même objet ou même deux objets différents, et que la correction de la fonction dépend des résultats de toutes ces modifications, vous souhaitez affirmer que chacun de ces changements a été correctement effectué!

Je conçois l'idée d'un concept logique, mais la conclusion inverse dirait qu'aucune fonction ne doit jamais changer plus d'un objet. Mais c'est impossible à mettre en œuvre dans tous les cas, selon mon expérience.

Prenons le concept logique d’une transaction bancaire: retirer un montant d’un compte bancaire DOIT dans la plupart des cas inclure l’ajout de ce montant à un autre compte. Vous ne voulez JAMAIS séparer ces deux choses, elles forment une unité atomique. Vous voudrez peut-être créer deux fonctions (retire / addMoney) et écrire ainsi deux tests unitaires différents - en plus. Mais ces deux actions doivent avoir lieu dans une transaction et vous voulez également vous assurer que la transaction fonctionne. Dans ce cas, il ne suffit tout simplement pas de garantir le succès des étapes individuelles. Vous devez vérifier les deux comptes bancaires, dans votre test.

Il peut y avoir des exemples plus complexes que vous ne testeriez pas dans un test unitaire, mais dans un test d'intégration ou d'acceptation. Mais ces limites sont fluides, à mon humble avis! Ce n'est pas si facile de décider, c'est une question de circonstances et peut-être de préférence personnelle. Retirer de l’argent de l’un d’eux et l’ajouter à un autre compte reste une fonction très simple et certainement un candidat pour les tests unitaires.

Cslotty
la source
-1

Cette question est liée au problème classique de l’équilibre entre les problèmes de code de spaghetti et de lasagne.

Si vous avez plusieurs assertions, vous pouvez facilement résoudre le problème des spaghettis sans avoir la moindre idée de la nature du test. Cependant, si vous ne soumettez qu'une seule affirmation par test, vos tests sont tout aussi illisibles que si vous avez plusieurs tests dans une grosse lasagne. .

Il y a quelques exceptions, mais dans ce cas, garder le pendule au milieu est la solution.

gsf
la source
-3

Je ne suis même pas d'accord avec le "échec pour une seule raison" en général. Ce qui est plus important, c’est que les tests sont courts et se lisent clairement en imo.

Cela n’est cependant pas toujours réalisable et quand un test est compliqué, un nom descriptif (long) et tester moins de choses ont plus de sens.

Johan Larsson
la source