2009-10-22 3 views
8

Ich habe mehrere Unittests dieses Muster:Testabdeckung vs ExpectedException

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

Es stellt sich heraus, dass die Codeabdeckung markes die Wurflinie als Halb laufen, so dass ich nur 1 Block von ungedeckten Code jedes Mal.

Nachdem ich eine Weile über dieses Problem nachgedacht hatte, war die beste Lösung, die ich mir vorstellen konnte, ein Versuch/Fang. Da dies ein sich wiederholendes Muster ist, werde ich nach dem Vorbild der

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

eine Hilfsmethode erstellen Dies hätte den schönen Nebeneffekt, dass ich alle Ausnahme-Tests auf die nicht werfen Tests hinzufügen könnte.

Ist das ein gültiger Entwurf, oder habe ich etwas vermisst?

Bearbeiten: Ugs ... scheint wie die obige ExpectException-Methode lässt mich mit 1 unbedeckten Block.

Antwort

10

Was Sie vorschlagen, ist gültig. Abgesehen von Ihrem Code-Coverage-Problem würde ich argumentieren, dass es besser ist als das Attribut ExpectedException, da es explizit anzeigt, welche Zeile des Tests die Ausnahme auslösen soll. Die Verwendung von ExpectedException bedeutet, dass die beliebige Zeile des Codes im Test den erwarteten Ausnahmetyp auslösen kann und der Test trotzdem bestanden wird. Wenn der Fehler von einem anderen Aufruf stammt, von dem nicht erwartet wurde, dass er ausgelöst wird, kann dies die Tatsache verschleiern, dass der Test fehlschlagen sollte, weil die Zeile, die geworfen werden soll, nicht vorhanden ist.

Was für eine nützliche Modifikation wäre, was Sie vorgeschlagen haben, wäre die abgefangene Ausnahme zurückzukehren:

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

Dies würde den Testcode ermöglichen, um die Ausnahme geltend machen, wenn es (dh gewünscht ein zu überprüfen. bestimmte Nachricht wurde verwendet).

NUnit (obwohl es sieht nicht, wie Sie es verwenden, wie Sie ein TestMethod Attribut) verfügt über eine integrierte in Konstrukt ähnlich dem, was Sie vorgeschlagen haben:

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

+1. Gutes Zeug. Das hat mich eine Zeitlang geärgert, aber ich habe es nie gelöst. – magnus

0

Ja das ist ziemlich Standard-Tarif - viele unserer Tests machen das gleiche. Gleichzeitig müssen Sie sich fragen, ob Sie nicht einen zu hohen Wert auf die Codeabdeckung legen, wenn diese Halbzweige so viel Gewicht haben, dass es sich lohnt.

+0

Derzeit bei erschreckenden 35% Abdeckung, so fügt dies nicht viel hinzu. Es ist eher ein kleines Design-Problem (das könnte passieren, ein paar hundert Zeilen Testcode zu speichern). – mafu

3

@adrianbanks die ExpectException nicht wie erwartet, wenn der Aktionsparameter eine weitere Ausnahme als die erwartete Ausnahme auslöst:

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

als ich die Testmethod „my_test“ ausführen ich habe eine Meldung, dass die Testmethode angehoben und System.ArgumentException: hallo. In diesem Fall sollte "Expected InvalidOperationException" angezeigt werden. Ich schlage vor, eine neue Version für die ExpectException Methode:

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

Ich weiß, dies ist ein altes Thema, aber ich lief in das gleiche Problem.

Schließlich fragte ich mich: Warum muss ich die Abdeckung der Tests wissen? Ich nicht! - Also lasst uns sie ausschließen, also ist die Abdeckung sauberer.

<?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> 

Nach Auswahl dieser Testeinstellungen mein Code Coverage 100%

dort diese Weise Datei ist:

In meinem Testprojekt eine CodeCoverage.runsettings Datei und das ist der Inhalt Ich habe hinzugefügt keine Notwendigkeit, das Unit-Test-Code-Coverage-System zu "hacken", nur um 100% zu erreichen :-)

+0

Vielen Dank für diese Idee! Ich kann es derzeit nicht selbst ausprobieren, aber es scheint eine sehr gute Lösung zu sein, wenn Sie nur sicherstellen, dass die Tests selbst korrekt sind. – mafu