2009-07-15 9 views
35

Wir haben einige NUnit-Tests, die auf die Datenbank zugreifen. Wenn einer von ihnen ausfällt, kann die Datenbank in einem inkonsistenten Zustand bleiben - was kein Problem darstellt, da wir die Datenbank für jeden Testlauf neu aufbauen - aber es kann dazu führen, dass andere Tests im selben Lauf fehlschlagen.NUnit - Bereinigung nach Testfehler

Kann festgestellt werden, dass einer der Tests fehlgeschlagen ist, und eine Bereinigung durchführen?

Wir wollen in jedem Test keinen Bereinigungscode schreiben, das machen wir jetzt schon. Ich würde gerne in Teardown aufräumen, aber nur wenn der Test fehlgeschlagen ist, da die Reinigung teuer sein könnte.

Update: Um zu verdeutlichen - ich möchte Tests einfach sein und keine Bereinigung oder Fehlerbehandlung Logik enthalten. Ich möchte auch keinen Datenbank-Reset bei jedem Test ausführen - nur wenn der Test fehlschlägt. Und dieser Code sollte wahrscheinlich in der Teardown-Methode ausgeführt werden, aber mir ist keine Möglichkeit bekannt, Informationen zu erhalten, wenn der Test, den wir gerade beenden, fehlgeschlagen oder erfolgreich war.

Update2:

 [Test] 
     public void MyFailTest() 
     { 
      throw new InvalidOperationException(); 
     } 

     [Test] 
     public void MySuccessTest() 
     { 
      Assert.That(true, Is.True); 
     } 

     [TearDown] 
     public void CleanUpOnError() 
     { 
      if (HasLastTestFailed()) CleanUpDatabase(); 
     } 

Ich bin auf der Suche für die Umsetzung von HasLastTestFailed()

+0

Wenn Sie nicht in jedem Test oder nach jedem Test aufräumen möchten, wird keine Bereinigung durchgeführt. Es tut uns leid. – rein

Antwort

20

Diese Idee hat mich interessiert, also habe ich ein wenig gegraben. NUnit verfügt nicht über diese Fähigkeit, aber es gibt ein vollständiges Erweiterungsrahmenwerk, das mit NUnit bereitgestellt wird. Ich fand this great article about extending NUnit - es war ein guter Ausgangspunkt. Nachdem ich damit herumgespielt habe, kam ich zu der folgenden Lösung: Eine Methode, die mit einem benutzerdefinierten Attribut CleanupOnError versehen ist, wird aufgerufen, wenn einer der Tests im Fixture fehlgeschlagen ist.

Hier ist, wie der Test wie folgt aussieht:

[TestFixture] 
    public class NUnitAddinTest 
    { 
    [CleanupOnError] 
    public static void CleanupOnError() 
    { 
     Console.WriteLine("There was an error, cleaning up..."); 
     // perform cleanup logic 
    } 

    [Test] 
    public void Test1_this_test_passes() 
    { 
     Console.WriteLine("Hello from Test1"); 
    } 

    [Test] 
    public void Test2_this_test_fails() 
    { 
     throw new Exception("Test2 failed"); 
    } 

    [Test] 
    public void Test3_this_test_passes() 
    { 
     Console.WriteLine("Hello from Test3"); 
    } 
    } 

wo das Attribut einfach ist:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 
    public sealed class CleanupOnErrorAttribute : Attribute 
    { 
    } 

Und hier ist, wie es aus dem Addin ausgeführt ist:

public void RunFinished(TestResult result) 
{ 
    if (result.IsFailure) 
    { 
    if (_CurrentFixture != null) 
    { 
     MethodInfo[] methods = Reflect.GetMethodsWithAttribute(_CurrentFixture.FixtureType, 
                  CleanupAttributeFullName, false); 
     if (methods == null || methods.Length == 0) 
     { 
     return; 
     } 

     Reflect.InvokeMethod(methods[0], _CurrentFixture); 
    } 
    } 
} 

Aber hier ist Der knifflige Teil: Das AddIn muss im Verzeichnis addins neben dem NUnit-Runner stehen. Meine wurde in TestDriven neben dem NUnit Runner platziert.NET-Verzeichnis:

C:\Program Files\TestDriven.NET 2.0\NUnit\addins

(Ich habe das addins Verzeichnis, es war nicht da)

EDIT Eine andere Sache ist, dass das Bereinigungsverfahren static sein muss!

ich gehackt eine einfache Addin zusammen, können Sie die Quelle von my SkyDrive herunterladen. Sie müssen an den entsprechenden Stellen Verweise auf , nunit.core.dll und nunit.core.interfaces.dll hinzufügen.

Ein paar Anmerkungen: Das Attribut Klasse überall in Ihrem Code platziert werden kann. Ich wollte es nicht in derselben Baugruppe wie das Add-In selbst platzieren, da es sich auf zwei NUnit-Baugruppen bezieht, also habe ich es in einer anderen Baugruppe platziert. Denken Sie daran, die Zeile in der CleanAddin.cs zu ändern, wenn Sie sich entscheiden, es irgendwo anders zu setzen.

Hoffe, dass hilft.

+0

Ihre Implementierung enthält nur Integrationstests. Als ich eine Variation daraus machte, stieß ich auf verschiedene Probleme. Beantworten Sie meine Frage, wie man einen Komponententest für ein Nunit Addin schreibt? http://stackoverflow.com/questions/3927349/how-to-write-a-nunit-test-for-an-nunit-add-in – Precipitous

1

Was ist ein Try-Catch-Block verwenden, Erneutes Auslösen der Ausnahme gefangen?

try 
{ 
//Some assertion 
} 
catch 
{ 
    CleanUpMethod(); 
    throw; 
} 
+0

Wäre das nicht zu jedem Test explizieren? „Wir wollen keinen Bereinigungscode in jedem Test schreiben“ –

+0

Ja, ich habe eine zweite Lösung geschrieben, um besser die aktualisierte Kommentare Adresse –

0

Wie schlägt es fehl? Ist es möglich, es in einen Versuch (do test)/catch (fix gebrochene db)/endlich blockieren?

Oder Sie könnten eine private Methode aufrufen, um es zu beheben, wenn Sie Ihren Fehlerzustand überprüft haben.

2

Ja, gibt es. Sie können das Teardown Attribut verwenden, das nach jedem Test abgebaut wird. Sie möchten das Skript "Zurücksetzen" der Datenbank anwenden und vor und nach jedem Test entfernen und neu aufbauen.

Dieses Attribut wird in einem TestFixture verwendet, um einen gemeinsamen Satz von Funktionen bereitzustellen, die nach jedes Testverfahren ausgeführt wird, durchgeführt werden.

aktualisieren: Basierend auf den Kommentaren und Updates auf die Frage, ich würde sagen, dass Sie das Teardown-Attribut verwenden können und private Variablen verwenden, um anzuzeigen, ob die Methode Inhalt ausgelöst werden soll.

Obwohl ich auch sah, dass Sie keine komplexe Logik oder Fehlerbehandlungscode wollen.

Vor diesem Hintergrund würde ich denken, dass ein Standard-Setup/Teardown am besten für Sie arbeiten würde. Es spielt keine Rolle, ob ein Fehler vorliegt und Sie keinen Fehlerbehandlungscode haben müssen.

Wenn Sie eine spezielle Säuberung benötigen, weil die nächsten Tests vom erfolgreichen Abschluss des aktuellen Tests abhängig sind, würde ich vorschlagen, Ihre Tests erneut zu besuchen - sie sollten wahrscheinlich nicht voneinander abhängig sein.

+0

Ich glaube, er über Teardown weiß, was er will, ist zu tun in Teardown darüber im Klaren sein, ob irgendwelche Tests in der Fixtur fehlgeschlagen, wenn nicht, kann er die Wiederherstellung der Datenbank überspringen. –

+0

Nun, es ist möglich, Testdaten zu erzeugen, die von Test zu Test unabhängig sind, aber es würde viel länger dauern, die Datenbank für jeden Test einzurichten, als in den Tests Cleanup-Code zu schreiben. Mein Ziel ist es, Bauzeit zu reduzieren und Setup/Teardown Code zu bereinigen. Und nein, Spott ist keine Option für diese Tests, wir verwenden sie bereits in unseren Unit Tests ... – bh213

+0

Ich verstehe dein Dilemma, aber ich habe keine weiteren Ideen .... –

2

Während es möglich sein könnte, nUnit dazu zu zwingen, dies zu tun, ist es nicht das vernünftigste Design, Sie könnten immer eine temporäre Datei irgendwo einstellen und wenn diese Datei existiert, führen Sie Ihre Bereinigung durch.

Ich würde empfehlen, Ihren Code zu ändern, so dass Sie Datenbanktransaktionen aktiviert haben und am Ende des Tests einfach die Datenbank in den ursprünglichen Zustand zurückversetzen (z. B. die Transaktion, die Ihre Komponententests darstellt).

+0

Es kann mehrere Transaktionen geben und wir haben auch Tests, die REST/WCF verwenden, wo keine Transaktionen vorhanden sind. Dies sind automatisierte Tests, über die wir sprechen. keine Einheitentests – bh213

+1

Es nUnit die richtige Sache dann zu verwenden? Sie wollen nicht das Standardverhalten eines Unit Testing Framework und Sie sagen auch, dass sie nicht Unit-Tests sind .... klingt wie nUnit ist das falsche Werkzeug! Warum haben Sie nicht einfach einige Konsolen-Anwendungen und rufen eine Batch-Datei oder eine Msbuild-Datei nacheinander auf? –

+0

@ Ray- meine Gedanken auch so +1. Ich habe Ihre Antwort zunächst nicht gesehen. Meine Antwort erweitert, wie Sie dies tun TransactionScope – RichardOD

1

Ich würde tun wie phsr suggeriert für jetzt und wenn Sie es sich leisten können, refactor die Tests, so dass sie nie auf die gleichen Daten verlassen müssen, die ein anderer Test benötigt oder noch besser abstrahieren die Datenzugriffsschicht und verspotten die Ergebnisse kommen aus dieser Datenbank. Es klingt wie Ihre Tests sind ziemlich teuer und Sie sollten Ihre gesamte Abfragelogik auf der Datenbank und Geschäftslogik in Ihrer Versammlung tun, die Sie nicht wirklich interessieren, was die Ergebnisse sind, die zurückgegeben werden.

Sie können auch Ihre ExceptionHandling viel besser testen.

1

Eine weitere Option ist eine spezielle Funktion, die Ihre Exceptions auslöst, die einen Schalter in der Testvorrichtung setzt, der eine Ausnahme meldet.

public abstract class CleanOnErrorFixture 
{ 
    protected bool threwException = false; 

    protected void ThrowException(Exception someException) 
    { 
     threwException = true; 
     throw someException; 
    } 

    protected bool HasTestFailed() 
    { 
      if(threwException) 
      { 
       threwException = false; //So that this is reset after each teardown 
       return true; 
      } 
      return false; 
    } 
} 

mit Dann wird Ihr Beispiel:

[TestFixture] 
public class SomeFixture : CleanOnErrorFixture 
{ 
    [Test] 
    public void MyFailTest() 
    { 
     ThrowException(new InvalidOperationException()); 
    } 

    [Test] 
    public void MySuccessTest() 
    { 
     Assert.That(true, Is.True); 
    } 

    [TearDown] 
    public void CleanUpOnError() 
    { 
     if (HasLastTestFailed()) CleanUpDatabase(); 
    } 
} 

Das einzige Problem ist hier, dass der Stack-Trace auf den CleanOnErrorFixture

+0

+1 aber dies erfordert immer noch eine Fehlerbehandlung, von der bh213 sich fern zu halten versucht.Es ist eine gute Lösung, passt aber nicht genau zu dem, was verlangt wird .... was wahrscheinlich bei den meisten der Fall ist die bisher gegebenen Antworten ... –

+0

In seinem Beispiel hatte er den "schlechten Test" eine Ausnahme aussprechen lassen. Die abstrakte Klasse bietet die gleiche Funktionalität wie das Beispiel und implementiert die "HasLastTestFailed" -Methode, die er wünscht. Vererbung von dieser Klasse fügt die Fehlerbehandlung nicht wie ein try catch-Block hinzu. In diesem Beispiel wird nur die Änderung der Ausnahmebedingung geändert (mithilfe eines Metonds anstelle von throw). –

+0

-1 erfordert System im Test, um die Methode innerhalb des Testcodes aufzurufen (CleanOnErrorFixture.ThrowException) –

1

Eine Option führt bisher nicht erwähnt wird, um den Test einpacken in ein TransactionScope-Objekt, es spielt also keine Rolle, was passiert, da der Test niemals etwas an die Datenbank schreibt.

Hier ist some details on the technique. Sie können wahrscheinlich mehr finden, wenn Sie eine Suche nach Komponententests und Transaktionskopien durchführen (obwohl Sie wirklich Integrationstests durchführen, wenn Sie eine DB treffen). Ich habe es in der Vergangenheit erfolgreich benutzt.

Dieser Ansatz ist einfach, erfordert keine Bereinigung und stellt sicher, dass die Tests isoliert sind.

Bearbeiten- Ich habe gerade bemerkt Ray Hayes Antwort ist auch ähnlich zu mir.

0

Ich sage nicht, das ist eine gute Idee, aber es sollte funktionieren. Denken Sie daran, dass Assertionsfehler nur Ausnahmen sind. Vergessen Sie auch nicht, dass es auch ein [TestFixtureTearDown] -Attribut gibt, das nur einmal ausgeführt wird, nachdem alle Tests im Fixture ausgeführt wurden.

Unter Verwendung dieser zwei Fakten, die Sie so etwas wie Setzen einen Flags schreiben können, wenn ein Test den Wert der Flagge in der Testvorrichtung abzureißen versagt und zu überprüfen.

Ich empfehle dies nicht, aber es funktionieren würde. Sie verwenden NUnit nicht wie beabsichtigt, aber Sie können es tun.

60

Seit Version 2.5.7 erkennt NUnit Teardown, ob der letzte Test fehlgeschlagen ist. Eine neue Testcontext-Klasse ermöglicht Tests über sich die TestStauts einschließlich zuzugreifen.

Weitere Einzelheiten finden Sie in http://nunit.org/?p=releaseNotes&r=2.5.7

[TearDown] 
public void TearDown() 
{ 
    if (TestContext.CurrentContext.Result.Status == TestStatus.Failed) 
    { 
     PerformCleanUpFromTest(); 
    } 
} 
+0

Ausgezeichnet, wusste nicht über diese TestContext-Klasse. Die neuesten Versionen von NUnit verschoben die Statusaufzählung direkt auf die TestContext-Klasse, also wäre es: TestContext.Status == TestStatus.Failed –

+0

Gibt es eine Möglichkeit, auch die Ursache des Fehlers mit dieser Methode zu erhalten? Beispiel: die Ausnahme, die den Fehler verursacht –

0

Sie können eine [TearDown] Methode mit
if (TestContext.CurrentContext.Result.Status != TestStatus.Passed)
einige Code hinzufügen, ausgeführt werden, wenn der Test fehlgeschlagen.

+0

Bitte beachten Sie das Lesen: http://stackoverflow.com/editing-help – GottZ