2009-10-22 7 views
8

J'ai plusieurs unittests de ce modèle:couverture de code vs ExpectedException

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

Il se trouve que la couverture de code MARKES la ligne de lancer en demi-course, donc je reçois 1 bloc de code à découvert à chaque fois. Après avoir réfléchi à ce problème pendant un certain temps, la meilleure solution que je pouvais trouver était d'ajouter un try/catch. Comme il est un motif répété, je vais créer une méthode d'assistance le long des lignes de

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

Cela aurait l'avantage de côté agréable que je pourrais ajouter tous les tests d'exception aux essais non lanceuse.

Est-ce une conception valide, ou ai-je raté quelque chose? Ugs ... semble que la méthode ExpectException ci-dessus me laisse aussi avec un bloc non couvert.

Répondre

10

Ce que vous suggérez est valide. En dehors de votre problème de couverture de code, je dirais que c'est mieux que d'utiliser l'attribut ExpectedException car il montre explicitement quelle ligne du test est censée déclencher l'exception. L'utilisation de ExpectedException signifie que une ligne de code dans le test peut lancer le type d'exception attendu et le test passera toujours. Si l'erreur provient d'un autre appel qui ne devrait pas être lancé, cela peut masquer le fait que le test devrait échouer parce que la ligne qui devrait être lancée ne l'est pas.

Quelle serait une modification utile à ce que vous avez proposé serait de revenir à l'exception pris:

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 encore l'exception si vous le souhaitez (par exemple pour vérifier un. message particulier a été utilisé).

NUnit (bien qu'il ne semble pas que vous utilisez comme vous avez un attribut TestMethod) a un semblable à ce que vous avez proposé de construire intégré:

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

+1. Bon produit. Cela m'a harcelé pendant un certain temps mais je n'ai jamais réussi à le réparer. – magnus

0

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

+0

Actuellement à une couverture horrible de 35%, donc cela n'ajoute pas beaucoup. C'est plus un problème de conception mineur (qui pourrait arriver à sauver quelques centaines de lignes de code de test). – mafu

3

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

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

Quand je l'exercerai TestMethod « my_test » Je viens de recevoir un message indiquant que la méthode d'essai soulevée et System.ArgumentException: bonjour. Dans ce cas, il devrait indiquer "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"); 
} 
2

Je sais que c'est un vieux sujet, mais je suis tombé sur le même problème. Finalement, je me suis interrogé: pourquoi ai-je besoin de connaître la couverture des tests? Je ne le fais pas! - Alors exclons-les, donc la couverture est plus propre.

Dans mon projet de test, j'ai ajouté un fichier 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é ce fichier des paramètres de testmon code couverture est de 100%

De cette façon, il est pas besoin de «hacker» le système de couverture de code de test unité, juste pour atteindre 100% :-)

+0

Merci pour cette idée! Je ne peux pas l'essayer moi-même actuellement, mais cela semble être une très bonne solution, si vous vous assurez simplement que les tests eux-mêmes sont corrects. – mafu

Questions connexes