Nunit: La capture d'AssertionException échoue aux tests depuis la version 3.10.

Créé le 14 mars 2018  ·  22Commentaires  ·  Source: nunit/nunit

````
essayer
{
Assert.True(false, "message d'erreur");
}
catch (AssertionException e)
{
Trace.WriteLine("Ignorer.");
}

        Trace.WriteLine("Test Passed");

````

Dans Nunit 3.9.0, ce test est réussi et marqué comme réussi dans l'explorateur de tests lors de l'exécution à partir de l'explorateur de tests Visual Studio. Dans Nunit 3.10, le scénario de test est marqué comme ayant échoué dans la vue d'ensemble de l'explorateur de tests. La dernière ligne de trace est exécutée dans les deux versions.

question

Commentaire le plus utile

@fluffynuts Il y a longtemps, lorsque j'ai écrit pour la première fois Assert.Catch il a simplement détecté une exception et n'a pas généré d'erreur si aucune exception n'a été levée. Malheureusement, cela a changé et nous n'avons plus d'équivalent "neutre" de Assert.Throws . Vous pouvez cependant en écrire un. Voici une version non testée basée sur le code de Assert.Throws . Il ne gère que le code synchrone, mais vous pouvez l'étendre facilement pour l'async en utilisant du code supplémentaire de Assert.Throws .

```C#
Public static void Exception SafelyCatchAnNUnitException (code TestDelegate)
{
Exception catchException = null;

using (new TestExecutionContext.IsolatedContext())
{
    try
    {
        code();
    }
    catch (Exception ex)
    {
        caughtException = ex;
    }

    return caughtException;
}

}
```

La méthode renverra toute exception levée, en nettoyant tout résidu de l'erreur laissée par les méthodes d'assertion de NUnit. Si aucune exception n'est levée, elle renvoie null.

Il est également possible d'utiliser IsolatedContext() à un niveau supérieur dans votre code afin d'en récupérer le résultat réel du test. Cela vous permettrait de tester entièrement au niveau du résultat du test, sans aucune référence aux exceptions. J'ai réfléchi à l'écriture d'une bibliothèque pour ce type de test, ce qui, je pense, serait supérieur à la façon dont NUnit effectue actuellement ses propres tests.

Tous les 22 commentaires

Votre test teste deux propositions sur le fonctionnement de NUnit :

  1. Que tous les échecs d'assertion lèvent une AssertionException.
  2. Cette capture de l'exception empêche que l'erreur soit signalée.

En l'occurrence, la première instruction est généralement vraie mais ne tient pas dans plusieurs blocs d'assertion ou lors de l'émission d'avertissements. La seconde n'est pas vraie du tout. Au moment où vous interceptez l'exception, l'échec a déjà été enregistré. Il s'agit d'un changement dans le comportement interne de NUnit, observable uniquement si vous interceptez ces exceptions. Pour ma part, j'ai dit aux gens de ne pas se fier à ce comportement depuis des années, car il ne s'agit que d'un accident de mise en œuvre.

Je me rends compte que votre exemple de code est uniquement destiné à illustrer le problème que vous voyez. Pouvez-vous fournir un exemple de ce que vous essayez réellement de faire pour intercepter l'exception ? Je suis sûr que nous pouvons vous aider à trouver une meilleure solution.

Merci d'être revenu vers moi dans un délai aussi court. J'utilise le mécanisme quand les choses sont encore floues. Parfois, le logiciel ne fonctionne pas correctement. Dans ces cas, je le signale aux développeurs. En attendant, nous avons étendu le test pour contourner ce comportement floconneux. Ainsi, le test réussit au lieu d'échouer sur ce problème.

Nous avons essayé d'utiliser la méthode Warn.If() pour cela, mais cela définit le résultat sur "Test ignoré" dans l'explorateur de tests. Cela provoque des échecs de rapport car le test n'est pas du tout ignoré. Nous choisissons donc d'implémenter l'assertion catch à la place et de tracer un avertissement. Je sais, pas souhaitable mais ça a plutôt bien marché. Au moins jusqu'à la dernière mise à jour.

Je suis curieux de savoir si ce changement était intentionnel, dans ce cas, je sais que je dois changer les choses.

Warn.If fonctionne très bien ... __except__ sous l'explorateur de tests. 😢 Le problème est que Test Explorer connaît les réussites et les échecs, mais pas les avertissements. Nous avons dû traduire nos résultats d'avertissement en quelque chose qu'ils peuvent comprendre jusqu'à ce qu'ils implémentent un statut de résultat d'avertissement. FWIW, nous avons un problème similaire avec notre état de résultat non concluant.

Le changement que nous avons fait était intentionnel et planifié depuis longtemps. Nous ne voulons pas nous fier la plupart du temps aux exceptions et nous voulons rapporter le résultat des assertions - y compris celles qui sont finalement réussies - d'une manière qui n'arrête pas le test. Nous avons apporté quelques modifications pour atténuer l'impact de cela pour les utilisateurs comme vous, mais celui-ci ne semblait pas avoir d'atténuation possible.

Si vous avez des tests floconneux, la norme depuis de nombreuses années a été de les ignorer. Malheureusement, de nombreuses équipes "ignorent les tests ignorés", auquel cas ce n'est pas un si bon choix. Si vous pouvez former votre équipe à prendre les avertissements au sérieux - presque aussi sérieusement, en fait, que les erreurs et les échecs - alors vous pouvez utiliser le statut d'avertissement pour mettre en évidence les défauts. Une autre possibilité consiste à utiliser Ignore avec la propriété Until, de sorte qu'il se transforme en échec s'il n'est pas résolu à une certaine date.

BTW, lorsque j'entraîne des équipes, j'ai généralement un grand écran visible continuellement mis à jour des tests ignorés. Dans la plupart des équipes, tout le monde sait qui est responsable de quoi, il n'est donc pas nécessaire d'appeler la personne responsable du test floconneux. Ils ont tendance à être corrigés lorsque tout le monde voit le problème.

@svanimpelen avez-vous essayé d'utiliser l'attribut Retry pour vos tests floconneux ? Il vous permettra de les exécuter plusieurs fois pour les faire passer.

En ce qui concerne l'avertissement entraînant des tests ignorés dans l'explorateur, nous avons discuté avec l'équipe du studio visuel de la possibilité de spécifier plus d'états de résultat que le nombre limité de MSTest qu'ils prennent actuellement en charge. Ils aiment l'idée, alors j'espère que nous y verrons des améliorations à l'avenir. Jusque-là, vous devrez souffrir de sauté. Cela dit, les résultats ignorés dans les avertissements. Je suppose que vous vouliez que les avertissements soient très évidents

Tant de nos méthodes documentent que l'API lève AssertionException en cas d'échec :

https://github.com/nuni/nunit/blob/d03e93c8f25170a8ff48e80863a3fe6fd294613a/src/NUnitFramework/framework/Assert.That.cs#L40 -L43

Cette documentation, ainsi que le comportement de longue date de NUnit, ne constituent-elles pas un changement radical dans 3.10 ?
À moins que je manque quelque chose, nous devrions au moins supprimer les documents maintenant incorrects.

Je suis d'accord que la documentation peut être trompeuse maintenant que nous avons introduit plusieurs blocs d'assertion. Cela dit, le AssertionException est toujours lancé dans ce cas, la différence est que le test est maintenant signalé comme un échec dans Visual Studio même si l'exception a été interceptée. Je ne vois pas cela comme un changement décisif. Les utilisateurs comptaient sur un comportement non documenté qui fonctionnait. Pour moi, ce n'est pas différent du fait que les utilisateurs se fient au fait que les tests ont été exécutés par ordre alphabétique pour ordonner leurs tests.

Nous devrions probablement mettre à jour la documentation. Nous pourrions supprimer les informations sur la levée d'une exception et simplement déclarer qu'une fausse condition échoue au test. Et/ou, on pourrait dire que l'exception n'est pas levée dans des blocs multi-assertions ? Personnellement, je pense que le type d'exception qui est levée est un détail interne, donc je préfère la première option.

Je pense que c'est vrai. Si l'utilisation d'exceptions doit être documentée n'importe où, cela pourrait être dans la section wiki sur les internes.

Lorsque la documentation de la méthode indique qu'une exception sera levée, je pense que cela indique sans équivoque que l'exception peut être interceptée sur le site d'appel. C# n'a pas vérifié les exceptions et les documents XML sont la façon dont nous communiquons cette partie du contrat de méthode. Étant donné que pour autant que je sache, le langage "lance X" a toujours signifié que la méthode ne l'attrape pas, je suis toujours un peu inquiet de savoir comment les autres l'ont pris. Peut-être une note de changement de rupture dans le wiki pour être du bon côté ?

J'aime l'idée de supprimer toutes les références à AssertionException des documents XML et de créer une page wiki avertissant les gens de ne pas utiliser AssertionException.

💭Si attraper AssertionException est toujours un bogue, pouvons-nous envisager de rendre ce type interne ?

@jnm2.

Je suis d'accord avec vous sur l'importance des documents XML, mais le fait est que ces documents ne sont __pas__ la façon dont nous avons communiqué une partie du contrat jusqu'à présent. En fait, je pense que vous êtes peut-être la première personne à le suggérer et je pense que c'est une bonne idée.

Mais je pense que vous devez reconnaître que commencer à penser à eux de cette façon impliquera de changer beaucoup de commentaires erronés ! Je ne pense pas que nous puissions traiter chacun de ces changements de documentation comme un changement décisif. Au cas par cas, nous pouvons raisonnablement considérer tout changement de comportement sous-jacent comme une rupture, mais c'est différent. Les modifications de la documentation ne devraient pas être rompues.

Cela soulève la question de savoir ce qui devrait figurer dans la liste des changements de rupture. Si j'ai bien compris vos commentaires passés, je pense que vous avez tendance à vouloir documenter tout ce qui pourrait briser n'importe quel utilisateur. Je préfère documenter des choses qui, selon nous, briseraient beaucoup d'utilisateurs.

Voici comment je vois la différence - d'autres peuvent ne pas être d'accord... Documenter __tout ce qui pourrait casser__ sent le CYA pour moi, comme le genre de CYA auquel la direction se livre parfois lorsqu'elle se concentre sur qui est blâmé pour l'échec plutôt que de prévenir l'échec lui-même. SI nous documentons tout, alors nous pouvons dire... "Tu vois, nous sommes couverts." Si nous ne documentons que ce que nous considérons comme important, nous courons le risque de nous tromper et de devoir nous excuser, mais nous donnons à l'utilisateur moyen un ensemble de changements beaucoup plus facile à comprendre. Je préfère ce dernier.

Il y a des moments où il est sûr d'attraper une exception d'assertion. Par exemple, j'ai apporté une modification il y a quelque temps pour que cela reste sûr lors de l'exécution directe de tests, sans qu'un TestExecutionContext soit impliqué. Si vous rendez l'exception interne, vous la cassez. En fait, j'étais heureux de le casser, mais l'équipe a convenu que nous ne devrions pas le faire, alors je l'ai réparé. Vous n'avez qu'à décider ce que vous êtes prêt à casser. Ce correctif n'a __pas__ géré le problème actuel, qui traite de la capture de l'exception à l'intérieur du test lui-même, plutôt que dans le code qui appelle la méthode.

Attraper AssertionException dans un test est ce que j'appellerais une "odeur" classique. C'est probablement faux, mais il faut regarder le cas précis pour le savoir. Je dirais aux utilisateurs "Catching AssertionException est très probablement une erreur, à moins que vous n'ayez une connaissance détaillée des composants internes de NUnit. Pour garantir un comportement correct, vous ne devez pas essayer de modifier le résultat du test et devez relancer l'exception après avoir fait ce que vous voulez faire avec ça." Cependant, je ne pense toujours pas que ces informations appartiennent aux documents XML ou aux documents généraux des utilisateurs. Je le mettrais comme une page dans la section interne.

BTW, mon expérience passée a été que documenter quelque chose puis dire aux gens de ne pas l'utiliser conduit à une utilisation accrue. ??

Bons points, merci ! Je pense que je pense en partie en termes de CYA. Il y a aussi un autre élément, c'est qu'en tant que consommateur de bibliothèques, j'apprécierais personnellement d'être informé de chaque changement de rupture afin que je puisse rechercher de mauvaises hypothèses dans mon propre code. Particulièrement avec des changements comme celui-ci que le compilateur ne remarque pas. Puisque j'apprécierais cela, je projette cela sur nos utilisateurs dans une certaine mesure.

Devrions-nous commencer un nouveau problème pour suivre le correctif de la documentation ?

Pourquoi neuf ?

Le titre ressemble à quelque chose que nous fermerions comme une question répondue plutôt que comme une correction. Changerions-nous généralement le titre et utiliserions-nous ce problème pour suivre le changement de document ?

Ce que j'ai généralement fait, c'est examiner le problème et le classer... dans ce cas comme un problème de documentation. Certes, nous avons passé beaucoup de temps à en discuter d'abord comme un possible bug. C'est une question d'administration de projet, vraiment, et en dehors de ma portée.

Ce que je dis, c'est de faire quelque chose de logique et de cohérent. Fermer ceci comme n'étant pas un bogue est une option. Cela sépare cependant la plainte de la solution. La rédaction d'un commentaire explicatif lorsque vous le reclassez en tant que bogue de la documentation et modifiez éventuellement le titre conserve la solution avec la plainte. Dans tous les cas, le titre du problème des problèmes terminés se retrouve dans les notes de version, il doit donc exprimer ce qui s'est passé.

Pouvez-vous fournir un exemple de ce que vous essayez réellement de faire pour intercepter l'exception ? Je suis sûr que nous pouvons vous aider à trouver une meilleure solution.

Je suis actuellement confronté au même problème depuis que mes tests ont commencé à échouer après la mise à niveau vers 3.10. Maintenant voici mon problème :
J'ai des méthodes d'assertion de test personnalisées. Ceux-ci appellent en interne NUnit.Assert. Je teste ces méthodes d'assertion pour m'assurer qu'elles échouent réellement lorsque je m'attends à ce qu'elles échouent.
Actuellement, mes tests détectent le AssertionException pour vérifier que la méthode d'assertion personnalisée entraînera l'échec d'un test, mais après la mise à niveau, tous ces tests commencent à échouer pour les raisons ci-dessus.

Quelle est la méthode recommandée pour tester ceux avec 3.10 ?

Merci.

L'approche la plus simple consiste à utiliser Assert.Throws pour intercepter l'exception plutôt que de le faire vous-même. Assert.Throws comprend les éléments internes de NUnit et garantit que l'échec n'est pas signalé.

J'ai vu cela dans mes notifications l'autre jour et je n'y ai pas beaucoup pensé. Cependant, mettre à jour un de mes projets de bibliothèque, qui fait "Assert.That" dans sa logique (c'est un framework d'aide aux tests - en particulier pour fournir un moyen facile de tester la persistance EF lors du codage manuel des contextes et entités EF et du codage de la base de données correspondante migrations à la main (ou avec quelque chose comme FluentMigrator) - donc, en d'autres termes, il est censé effectuer des assertions au nom de l'appelant), je reçois maintenant des tests qui échouent là où ils ne devraient pas être - le seul test en particulier affirme qu'une certaine condition devrait générer des échecs de test - ce qui est le cas, mais comme le "catch" ne fonctionne plus comme il l'a toujours fait, ces tests, bien qu'ils réussissent, sont signalés comme ayant échoué.

Donc, je suppose que je ne peux pas lancer d'assertions NUnit appropriées, ou utiliser Assert.That dans le code de la bibliothèque destiné à aider à un développement de test plus rapide en effectuant des tâches fastidieuses pour l'utilisateur ?

Je suis d'accord avec les déclarations précédentes selon lesquelles si l'exception ne peut pas être consommée par l'utilisateur, elle ne devrait pas être disponible pour l'utilisateur ; Cependant, cela ne résout pas le problème où j'ai du code Assert.That en place pour le consommateur et des tests pour prouver qu'ils échouent parce que le code fait ce qu'il est censé faire : échouer avec des assertions nunit.

Peut-être devrions-nous produire certaines des API de résultat ?

@fluffynuts Si je vous comprends bien, la clause catch est dans votre code de test, pas le code de votre bibliothèque utilisateur. (Si c'est aussi dans la bibliothèque utilisateur, nous pouvons en parler, mais les problèmes sont légèrement différents.)

Bien sûr, NUnit a des tests de lui-même, nous avons donc eu exactement le même problème potentiel lorsque ce changement a été effectué. Naturellement, nous avons modifié nos propres tests pour qu'ils continuent à réussir. Vous pouvez faire la même chose.

La première ligne de défense est Assert.Throws . Dans la plupart des cas de test de comportement d'échec, vous pouvez remplacer directement try / catch(AssertionException) par Assert.Throws<AssertionException> . En interne, Assert.Throws crée un TestExecutionContext isolé jetable afin que vos résultats de test réels ne soient pas pollués par l'erreur que vous testez.

Dans quelques cas, vous devrez peut-être créer votre propre contexte isolé. Vous pouvez voir comment faire cela en examinant le code pour Assert.Throws .

Cependant, une grande partie des propres tests de défaillance de NUnit utilisent en réalité une approche différente. Nous avons un assembly séparé avec des tests qui sont exécutés par nos propres programmes de test. Nous capturons le résultat et pouvons ensuite l'examiner. C'est plus de travail mais évite de mettre trop de détails d'implémentation sur NUnit dans nos tests. La plupart des méthodes qui font cela se trouvent dans notre assemblage d'utilitaires de test. Je pense que c'est ce à quoi

Postez toutes vos questions ou contactez-moi hors ligne pour obtenir de l'aide à ce sujet.

Merci pour l'info. Oui, les assertions sont capturées dans le code de test. Assert.Throws ferait normalement l'affaire, mais le test spécifique en question exécute le code de la bibliothèque avec un delta "trop ​​faible" autorisé pour tester une valeur datetime qui doit être insérée et récupérée à partir d'une base de données. La plupart du temps, la dérive expérimentée pour cela dans localdb est de 1 ms, mais parfois elle dérive plus haut - et ce test vise simplement à affirmer que le "parfois" se produit. Donc, pour le moment, j'exécute le code dans 4 threads parallèles, 10 fois et je m'attends à au moins un échec. Assert.Throws est, malheureusement, trop déterministe pour ce cas, donc je dois soit arrêter d'utiliser les exceptions nunit (ou Assert, dans le code de la bibliothèque), soit apprendre le fu que vous utilisez. J'apprécierais tous les pointeurs - peut se produire hors ligne, si vous le souhaitez.

Hé, je viens de réaliser que les échecs du test de la pompe de message pour mon AsyncVoidVerificationScope que j'ai observé sont précisément le même problème que @fluffynuts . En fait, en raison de la mise à niveau de NUnit 3.10, ce n'est pas mon [assembly : RestoreSynchronizationContext] ITestAction. Me sert bien pour les ignorer tout en faisant plusieurs choses à la fois. :RÉ

Quoi qu'il en soit, il semble que ce que je veux faire, c'est envelopper mon invocation de délégué dans using (new TestExecutionContext.IsolatedContext()) comme le fait Assert.Throws .

Cependant, Assert.Throws et Assert.ThrowsAsync n'établissent pas de contextes isolés avant d'appeler des délégués asynchrones. Pourquoi est-ce?

@fluffynuts Il y a longtemps, lorsque j'ai écrit pour la première fois Assert.Catch il a simplement détecté une exception et n'a pas généré d'erreur si aucune exception n'a été levée. Malheureusement, cela a changé et nous n'avons plus d'équivalent "neutre" de Assert.Throws . Vous pouvez cependant en écrire un. Voici une version non testée basée sur le code de Assert.Throws . Il ne gère que le code synchrone, mais vous pouvez l'étendre facilement pour l'async en utilisant du code supplémentaire de Assert.Throws .

```C#
Public static void Exception SafelyCatchAnNUnitException (code TestDelegate)
{
Exception catchException = null;

using (new TestExecutionContext.IsolatedContext())
{
    try
    {
        code();
    }
    catch (Exception ex)
    {
        caughtException = ex;
    }

    return caughtException;
}

}
```

La méthode renverra toute exception levée, en nettoyant tout résidu de l'erreur laissée par les méthodes d'assertion de NUnit. Si aucune exception n'est levée, elle renvoie null.

Il est également possible d'utiliser IsolatedContext() à un niveau supérieur dans votre code afin d'en récupérer le résultat réel du test. Cela vous permettrait de tester entièrement au niveau du résultat du test, sans aucune référence aux exceptions. J'ai réfléchi à l'écriture d'une bibliothèque pour ce type de test, ce qui, je pense, serait supérieur à la façon dont NUnit effectue actuellement ses propres tests.

Cette page vous a été utile?
0 / 5 - 0 notes