web-dev-qa-db-fra.com

Assert.Fail () est-il considéré comme une mauvaise pratique?

J'utilise beaucoup Assert.Fail lorsque je fais du TDD. Je travaille généralement sur un test à la fois, mais lorsque j'ai des idées sur les choses que je veux implémenter plus tard, j'écris rapidement un test vide où le nom de la méthode de test indique ce que je veux implémenter comme une sorte de liste de tâches. Pour être sûr de ne pas oublier, j'ai mis un Assert.Fail () dans le corps.

En essayant xUnit.Net, j'ai découvert qu'ils n'avaient pas implémenté Assert.Fail. Bien sûr, vous pouvez toujours Assert.IsTrue (false) mais cela ne communique pas mon intention aussi. J'ai l'impression que Assert.Fail n'a pas été implémenté exprès. Est-ce considéré comme une mauvaise pratique? Si oui, pourquoi?


@Martin Meredith Ce n'est pas exactement ce que je fais. J'écris d'abord un test, puis j'implémente du code pour le faire fonctionner. Habituellement, je pense à plusieurs tests à la fois. Ou je pense à un test à écrire quand je travaille sur autre chose. C'est alors que j'écris un test d'échec vide à retenir. Au moment où j'arrive à écrire le test, je travaille parfaitement en premier.

@Jimmeh Cela ressemble à une bonne idée. Les tests ignorés n'échouent pas mais ils apparaissent toujours dans une liste séparée. Je dois essayer ça.

@Matt Howells Great Idea. NotImplementedException communique mieux l'intention qu'assert.Fail () dans ce cas

@Mitch Wheat C'est ce que je cherchais. Il semble qu'il ait été laissé de côté pour éviter qu'il ne soit maltraité d'une autre manière.

67
Mendelt

Pour ce scénario, plutôt que d'appeler Assert.Fail, je fais ce qui suit (en C #/NUnit)

[Test]
public void MyClassDoesSomething()
{
    throw new NotImplementedException();
}

Il est plus explicite qu'un Assert.Fail.

Il semble être généralement admis qu'il est préférable d'utiliser des assertions plus explicites que Assert.Fail (). La plupart des frameworks doivent cependant l'inclure car ils n'offrent pas de meilleure alternative. Par exemple, NUnit (et d'autres) fournissent un ExpectedExceptionAttribute pour tester qu'un certain code lève une classe particulière d'exception. Cependant, pour tester qu'une propriété de l'exception est définie sur une valeur particulière, on ne peut pas l'utiliser. Au lieu de cela, vous devez recourir à Assert.

[Test]
public void ThrowsExceptionCorrectly()
{
    const string BAD_INPUT = "bad input";
    try
    {
        new MyClass().DoSomething(BAD_INPUT);
        Assert.Fail("No exception was thrown");
    }
    catch (MyCustomException ex)
    {
         Assert.AreEqual(BAD_INPUT, ex.InputString); 
    }
}

La méthode xUnit.Net Assert.Throws rend cela beaucoup plus net sans nécessiter une méthode Assert.Fail. En n'incluant pas de méthode Assert.Fail (), xUnit.Net encourage les développeurs à trouver et à utiliser des alternatives plus explicites et à prendre en charge la création de nouvelles assertions si nécessaire.

51
Matt Howells

Il a été délibérément laissé de côté. Voici la réponse de Brad Wilson quant à la raison pour laquelle il n'y a pas d'Assert.Fail ():

Nous n'avons pas oublié cela, en fait. Je trouve Assert.Fail est une béquille qui implique qu'il manque probablement une assertion. Parfois, c'est simplement la façon dont le test est structuré, et parfois c'est parce que Assert pourrait utiliser une autre assertion.

17
Mitch Wheat

J'ai toujours utilisé Assert.Fail () pour gérer les cas où vous avez détecté qu'un test devait échouer par la logique au-delà de la simple comparaison de valeurs. Par exemple:

try 
{
  // Some code that should throw ExceptionX
  Assert.Fail("ExceptionX should be thrown")
} 
catch ( ExceptionX ex ) 
{
  // test passed
}

Ainsi, l'absence d'Assert.Fail () dans le cadre me semble être une erreur. Je suggérerais de corriger la classe Assert pour inclure une méthode Fail (), puis de soumettre le correctif aux développeurs du framework, avec votre raisonnement pour l'ajouter.

Quant à votre pratique de créer des tests qui échouent intentionnellement dans votre espace de travail, de vous rappeler de les implémenter avant de vous engager, cela me semble une bonne pratique.

9
Craig Trader

J'utilise MbUnit pour mes tests unitaires. Ils ont la possibilité d'ignorer les tests, qui apparaissent comme Orange (plutôt que Vert ou Rouge) dans la suite de tests. Peut-être que xUnit a quelque chose de similaire, et cela signifierait que vous n'avez même pas à asserter la méthode, car elle apparaîtrait dans une couleur agaçante différente, ce qui la rend difficile à manquer?

Modifier:

Dans MbUnit, c'est de la manière suivante:

[Test]
[Ignore]
public void YourTest()
{ } 
5
Jimmeh

Voici le modèle que j'utilise lors de l'écriture d'un test de code que je veux lever une exception par conception:

[TestMethod]
public void TestForException()
{
    Exception _Exception = null;

    try
    {
        //Code that I expect to throw the exception.
        MyClass _MyClass = null;
        _MyClass.SomeMethod();
        //Code that I expect to throw the exception.
    }
    catch(Exception _ThrownException)
    {   
        _Exception = _ThrownException
    }
    finally
    {
        Assert.IsNotNull(_Exception);
        //Replace NullReferenceException with expected exception.
        Assert.IsInstanceOfType(_Exception, typeof(NullReferenceException));
    }
}

À mon humble avis, c'est un meilleur moyen de tester les exceptions par rapport à l'utilisation de Assert.Fail (). La raison en est que non seulement je teste une exception levée, mais je teste également le type d'exception. Je me rends compte que cela est similaire à la réponse de Matt Howells, mais à mon humble avis, l'utilisation du bloc finalement est plus robuste.

De toute évidence, il serait toujours possible d'inclure d'autres méthodes Assert pour tester la chaîne d'entrée d'exceptions, etc. Je vous serais reconnaissant de vos commentaires et opinions sur mon modèle.

4
lexx

Personnellement, je n'ai aucun problème à utiliser une suite de tests comme liste de tâches comme celle-ci tant que vous finissez par écrire le test avant vous implémentez le code à passer.

Cela dit, j'ai utilisé cette approche moi-même, bien que je trouve maintenant que cela me conduit à écrire trop de tests à l'avance, ce qui, d'une manière étrange, ressemble au problème inverse de ne pas écrire de tests du tout: vous finissez par prendre des décisions sur la conception un peu trop tôt à mon humble avis.

Incidemment dans MSTest, le modèle de test standard utilise Assert.Inconclusive à la fin de ses échantillons.

AFAIK, le framework xUnit.NET est destiné à être extrêmement léger et oui, il a délibérément coupé l'échec, pour encourager le développeur à utiliser une condition d'échec explicite.

3
Jim Burger

Avec le bon code que je fais d'habitude:

void goodCode() {
     // TODO void goodCode()
     throw new NotSupportedOperationException("void goodCode()");
}

Avec le code de test, je fais habituellement:

@Test
void testSomething() {
     // TODO void test Something
     Assert.assert("Some descriptive text about what to test")
}

Si vous utilisez JUnit et que vous ne souhaitez pas obtenir l'échec, mais l'erreur, je fais généralement:

@Test
void testSomething() {
     // TODO void test Something
     throw new NotSupportedOperationException("Some descriptive text about what to test")
}
2
Daniel Fanjul

Devinette: retenir Assert.Fail est destiné à vous empêcher de penser qu'un bon moyen d'écrire du code de test est comme un énorme tas de spaghettis conduisant à un Assert.Fail dans les mauvais cas. [Modifier pour ajouter: les réponses des autres confirment largement cela, mais avec des citations]

Puisque ce n'est pas ce que vous faites, il est possible que xUnit.Net soit trop protecteur.

Ou peut-être pensent-ils simplement que c'est si rare et si peu orthogonal qu'il est inutile.

Je préfère implémenter une fonction appelée ThisCodeHasNotBeenWrittenYet (en fait quelque chose de plus court, pour faciliter la saisie). Je ne peux pas communiquer l'intention plus clairement que cela, et vous avez un terme de recherche précis.

Si cela échoue, ou n'est pas implémenté (pour provoquer une erreur de l'éditeur de liens), ou s'il s'agit d'une macro qui ne se compile pas, peut être modifiée selon vos préférences actuelles. Par exemple, lorsque vous voulez exécuter quelque chose qui est terminé, vous voulez un échec. Lorsque vous vous asseyez pour vous débarrasser de tous, vous souhaiterez peut-être une erreur de compilation.

2
Steve Jessop

Il faut se méfier Assert.Fail et son influence corruptrice pour inciter les développeurs à écrire des tests stupides ou cassés. Par exemple:

[TestMethod]
public void TestWork()
{
    try {
        Work();
    }
    catch {
        Assert.Fail();
    }
}

C'est idiot, car le try-catch est redondant. Un test échoue s'il lève une exception.

Également

[TestMethod]
public void TestDivide()
{
    try {
        Divide(5,0);
        Assert.Fail();
    } catch { }
}

Ceci est cassé, le test réussira toujours quel que soit le résultat de la fonction Divide. Encore une fois, un test échoue si et seulement s'il lève une exception.

1
Colonel Panic

Je pense que vous devriez vous demander ce que les tests (initiaux) devraient faire.

Tout d'abord, vous écrivez un (ensemble de) tests sans implémentation. Peut-être aussi les scénarios de jours de pluie.

Tous ces tests doivent échouer, pour être des tests corrects: Vous voulez donc réaliser deux choses: 1) Vérifiez que votre implémentation est correcte; 2) Vérifiez que vos tests unitaires sont corrects.

Maintenant, si vous faites TDD en amont, vous voulez exécuter tous vos tests, aussi, les parties NYI. Le résultat de votre test total réussit si: 1) Tous les éléments implémentés réussissent 2) Tous les éléments NYI échouent

Après tout, ce serait une omission de test unitaire si vos tests unitaires réussissent alors qu'il n'y a pas d'implémentation, n'est-ce pas?

Vous voulez vous retrouver avec une sorte de courrier électronique de votre test d'intégration continue qui vérifie tout le code implémenté et non implémenté, et est envoyé en cas d'échec de tout code implémenté ou de réussite de tout code non implémenté. Les deux sont des résultats indésirables.

Il suffit d'écrire un test [ignorer] qui ne fera pas l'affaire. De même, une assertion qui arrête un échec de la première assertion, n'exécutant pas d'autres lignes de test dans le test.

Maintenant, comment y parvenir alors? Je pense que cela nécessite une organisation plus avancée de vos tests. Et cela nécessite un autre mécanisme qui s'affirme ensuite pour atteindre ces objectifs.

Je pense que vous devez diviser vos tests et créer des tests qui s'exécutent complètement mais doivent échouer, et vice versa.

Les idées sont de diviser vos tests sur plusieurs assemblages, d'utiliser un regroupement de tests (les tests ordonnés dans mstest peuvent faire le travail).

Pourtant, une construction CI qui envoie des courriers sinon tous les tests dans le département NYI n'est pas facile et directe.

0
Roland Roos

Pourquoi utiliseriez-vous Assert.Fail pour avoir dit qu'une exception devait être levée? C'est inutile. Pourquoi ne pas simplement utiliser l'attribut ExpectedException?

0
Better Than Craig

Si vous écrivez un test qui échoue, puis écrivez le code correspondant, puis écrivez le test. Ce n'est pas du développement piloté par les tests.

Techniquement, Assert.fail () ne devrait pas être nécessaire si vous utilisez correctement le développement piloté par les tests.

Avez-vous pensé à utiliser une Todo List ou à appliquer une méthodologie GTD à votre travail?

0
Mez

MS Test a Assert.Fail () mais il a également Assert.Inconclusive (). Je pense que l'utilisation la plus appropriée pour Assert.Fail () est si vous avez une logique en ligne qui serait difficile à mettre dans une assertion, bien que je ne puisse même pas penser à de bons exemples. Pour la plupart, si le framework de test prend en charge autre chose que Assert.Fail (), utilisez-le.

0
Mark Cidade