9 votes

CodeCoverage vs ExpectedException

J'ai plusieurs unittests de ce modèle :

[TestMethod ()]
[ExpectedException (typeof (ArgumentNullException))]
public void DoStuffTest_Exception ()
{
    var foo = new Foo ();
    Foo.DoStuff (null);
}

Il s'avère que la couverture de code marque la ligne de lancement comme étant à moitié exécutée, ce qui me permet d'obtenir un bloc de code non couvert à chaque fois.

Après avoir réfléchi à ce problème pendant un certain temps, la meilleure solution que j'ai trouvée a été d'ajouter un try/catch. Comme il s'agit d'un motif répété, je vais créer une méthode d'aide du type

public static void ExpectException<_T> (Action action) where _T: Exception
{
    try { action(); }
    catch (_T) { return; }
    Assert.Fail ("Expected " + _T);
}

Cela aurait l'avantage de me permettre d'ajouter tous les tests d'exception aux tests de non-renvoi.

S'agit-il d'une conception valable, ou ai-je manqué quelque chose ?

Edit : Ugs... il semble que la méthode ExpectException ci-dessus me laisse aussi avec un bloc non couvert.

10voto

adrianbanks Points 36858

Ce que vous suggérez est valable. Mis à part le problème de la couverture du code, je dirais que c'est mieux que d'utiliser la fonction ExpectedException car il indique explicitement la ligne du test qui doit lever l'exception. Utilisation de ExpectedException signifie que cualquier ligne de code dans le test peut lancer le type d'exception attendu et le test passera quand même. Si l'erreur provient d'un autre appel qui n'était pas prévu, elle peut masquer le fait que le test devrait échouer parce que la ligne qui devrait lancer l'exception ne le fait pas.

Ce qui serait une modification utile à ce que vous avez proposé serait de retourner l'exception attrapée :

public static _T ExpectException<_T> (Action action) where _T: Exception
{
    try { action(); }
    catch (_T ex) { return ex; }
    Assert.Fail ("Expected " + typeof(_T));
    return null;
}

Cela permettrait au code de test d'affirmer davantage l'exception s'il le souhaite (par exemple, pour vérifier qu'un message particulier a été utilisé).

NUnit (bien qu'il ne semble pas que vous l'utilisiez puisque vous avez un fichier TestMethod ) a une construction intégrée similaire à ce que vous avez proposé :

Assert.Throws<ArgumentNullException>(() => Foo.DoStuff(null))

3voto

Anben PANGLOSE Points 116

@adrianbanks l'ExpectException ne fonctionne pas comme prévu si le paramètre d'action lance une autre exception que l'exception attendue :

[TestMethod]
public void my_test()
{
    ExpectException<InvalidOperationException>(delegate()
    {
        throw new ArgumentException("hello");
    });
}

Lorsque j'exécute la méthode de test "my_test", je reçois un message indiquant que la méthode de test a généré une exception System.ArgumentException : hello. Dans ce cas, le message devrait dire "Expected InvalidOperationException". Je propose une nouvelle version pour la méthode ExpectException :

public static void VerifierException<T>(Action action) where T : Exception
{
    try
    {
        action();
    }
    catch (Exception ex)
    {
        Assert.IsInstanceOfType(ex, typeof(T));
        return;
    }

    Assert.Fail("Aucune exception n'a été déclenchée alors qu'une exception du type " + typeof(T).FullName + " était attendue");
}

2voto

321X Points 1216

Je sais que c'est un vieux sujet, mais j'ai rencontré le même problème.

J'ai fini par m'interroger : pourquoi ai-je besoin de connaître la couverture des tests ? Je ne le fais pas ! - Alors excluons-les, pour que la couverture soit plus propre.

Dans mon projet de test, j'ai ajouté un CodeCoverage.runsettings et voici le contenu :

<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="Code Coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
        <Configuration>
          <CodeCoverage>
            <ModulePaths>
              <Exclude>
                <ModulePath>.*tests.dll</ModulePath>
                <ModulePath>.*Tests.dll</ModulePath>
                <!-- Add more ModulePath nodes here. -->
              </Exclude>
            </ModulePaths>
          </CodeCoverage>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
</RunSettings>

Après avoir sélectionné cette Paramètres de test mon taux de couverture de code est de 100 %.

De cette façon, il n'est pas nécessaire de "pirater" le système de couverture du code des tests unitaires, juste pour atteindre 100% :-)

0voto

Epaga Points 12717

Oui, c'est assez standard - beaucoup de nos tests font la même chose. En même temps, vous devez vous demander si vous n'accordez pas une trop grande valeur à la couverture du code si ces demi-branches pèsent tellement pour que cela en vaille la peine.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X