Nunit: Das Abfangen von AssertionException schlägt bei Tests seit 3.10 fehl.

Erstellt am 14. März 2018  ·  22Kommentare  ·  Quelle: nunit/nunit

````
Versuchen
{
Assert.True(false, "Fehlermeldung");
}
catch (AssertionException e)
{
Trace.WriteLine("Ignorieren.");
}

        Trace.WriteLine("Test Passed");

````

In Nunit 3.9.0 ist dieser Test erfolgreich und wird im Test-Explorer als bestanden markiert, wenn er vom Visual Studio-Test-Explorer ausgeführt wird. In Nunit 3.10 wird der Testfall in der Test-Explorer-Übersicht als nicht bestanden markiert. Die letzte Trace-Zeile wird in beiden Versionen ausgeführt.

question

Hilfreichster Kommentar

@fluffynuts Vor langer Zeit, als ich Assert.Catch ersten Mal schrieb, hat es nur eine Ausnahme abgefangen und keinen Fehler generiert, wenn keine Ausnahme ausgelöst wurde. Leider hat sich das geändert und wir haben kein "neutrales" Äquivalent von Assert.Throws . Sie könnten jedoch einen schreiben. Hier ist eine ungetestete Version, die auf dem Code in Assert.Throws basiert. Es verarbeitet nur synchronen Code, aber Sie können es leicht für asynchronen erweitern, indem Sie zusätzlichen Code von Assert.Throws .

```C#
public static void Exception SafelyCatchAnNUnitException(TestDelegate-Code)
{
Ausnahme catchException = null;

using (new TestExecutionContext.IsolatedContext())
{
    try
    {
        code();
    }
    catch (Exception ex)
    {
        caughtException = ex;
    }

    return caughtException;
}

}
```

Die Methode gibt jede ausgelöste Ausnahme zurück und bereinigt alle Überreste des Fehlers, die von den Assert-Methoden von NUnit hinterlassen wurden. Wenn keine Ausnahme ausgelöst wird, wird null zurückgegeben.

Es ist auch möglich, IsolatedContext() auf einer höheren Ebene in Ihrem Code zu verwenden, um daraus das eigentliche Testergebnis abzurufen. Dies würde es Ihnen ermöglichen, vollständig auf der Ebene des Testergebnisses zu testen, ohne auf Ausnahmen Bezug zu nehmen. Ich habe mir überlegt, eine Bibliothek für diese Art von Tests zu schreiben, die meiner Meinung nach der Art und Weise, wie NUnit derzeit seine eigenen Tests durchführt, überlegen wäre.

Alle 22 Kommentare

Ihr Test testet zwei Aussagen zur Funktionsweise von NUnit:

  1. Dass alle Assertion-Fehler eine AssertionException auslösen.
  2. Das Abfangen der Ausnahme verhindert, dass der Fehler gemeldet wird.

Zufällig ist die erste Anweisung meistens wahr, gilt aber nicht innerhalb mehrerer Assert-Blöcke oder beim Ausgeben von Warnungen. Das zweite stimmt überhaupt nicht. Wenn Sie die Ausnahme abfangen, wurde der Fehler bereits aufgezeichnet. Dies ist eine Änderung im internen Verhalten von NUnit, die nur beobachtet werden kann, wenn Sie diese Ausnahmen abfangen. Ich für meinen Teil sage den Leuten seit Jahren, dass sie sich nicht auf dieses Verhalten verlassen sollen, da es nur ein Zufall der Umsetzung ist.

Mir ist klar, dass Ihr Beispielcode nur dazu gedacht ist, das Problem zu demonstrieren, das Sie sehen. Können Sie ein Beispiel dafür geben, was Sie tatsächlich versuchen, um die Ausnahme abzufangen? Ich bin mir ziemlich sicher, dass wir Ihnen helfen können, eine bessere Lösung zu finden.

Danke, dass Sie sich so kurzfristig bei mir gemeldet haben. Ich benutze den Mechanismus, wenn die Dinge noch flockig sind. Daher funktioniert die Software manchmal nicht richtig. In diesen Fällen melde ich dies den Entwicklern. In der Zwischenzeit haben wir den Test erweitert, um dieses flockige Verhalten zu umgehen. Der Test wird also bei diesem Problem erfolgreich bestanden, anstatt zu scheitern.

Wir haben versucht, dafür die Methode Warn.If() zu verwenden, die das Ergebnis im Test-Explorer jedoch auf "Test übersprungen" setzt. Dies führt zu Meldefehlern, da der Test überhaupt nicht übersprungen wird. Daher implementieren wir stattdessen den Assertion-Catch und verfolgen eine Warnung. Ich weiß, nicht wünschenswert, aber es hat ziemlich gut funktioniert. Zumindest bis zum letzten Update.

Ich bin gespannt, ob diese Änderung beabsichtigt war, dann weiß ich, dass ich Dinge ändern muss.

Warn.If funktioniert einwandfrei ... __außer__ im Test Explorer. 😢 Das Problem besteht darin, dass Test Explorer über Bestehen und Nichtbestehen Bescheid weiß, aber keine Warnungen. Wir mussten unsere Warnergebnisse in etwas übersetzen, das sie verstehen können, bis sie einen Warnergebnisstatus implementieren. FWIW haben wir ein ähnliches Problem mit unserem nicht schlüssigen Ergebniszustand.

Die von uns vorgenommene Änderung war beabsichtigt und seit langem in Planung. Wir möchten uns nicht so oft auf Ausnahmen verlassen und das Ergebnis von Assertions - einschließlich eventuell bestandener - auf eine Weise melden, die den Test nicht stoppt. Wir haben einige Änderungen vorgenommen, um die Auswirkungen für Benutzer wie Sie zu mildern, aber bei dieser schien es nicht möglich zu sein.

Wenn Sie flockige Tests haben, ist es seit vielen Jahren der Standard, sie zu ignorieren. Leider "ignorieren viele Teams die ignorierten Tests", was in diesem Fall keine so gute Wahl ist. Wenn Sie Ihrem Team beibringen können, Warnungen ernst zu nehmen - fast genauso ernst wie Fehler und Ausfälle -, können Sie den Warnstatus verwenden, um Unzulänglichkeiten hervorzuheben. Eine andere Möglichkeit besteht darin, Ignorieren mit der Eigenschaft Bis zu verwenden, sodass es zu einem Fehler wird, wenn es nicht bis zu einem bestimmten Datum behoben wird.

Übrigens, wenn ich Teams coache, habe ich normalerweise eine ständig aktualisierte große sichtbare Anzeige ignorierter Tests. In den meisten Teams weiß jeder, wer für was verantwortlich ist, sodass es nicht erforderlich ist, die für den Schuppentest verantwortliche Person zu rufen. Sie neigen dazu, behoben zu werden, wenn jeder das Problem sieht.

@svanimpelen haben Sie versucht, das Attribut Retry für Ihre flockigen Tests zu verwenden? Sie können sie mehrmals ausführen, um sie zum Bestehen zu bringen.

Was die Warnung betrifft, die zu übersprungenen Tests im Explorer führt, haben wir mit dem Visual Studio-Team darüber gesprochen, dass wir mehr Ergebnisstatus angeben können als die begrenzte Anzahl von MSTest, die derzeit unterstützt wird. Sie mögen die Idee, also werden wir dort hoffentlich in Zukunft Verbesserungen sehen. Bis dahin müssen Sie mit übersprungenen leiden. Übersprungene Ergebnisse führen jedoch zu Warnungen. Ich würde annehmen, Sie wollten, dass Warnungen sehr offensichtlich sind 😄

So viele unserer Methoden dokumentieren, dass die API bei einem Fehler AssertionException auslöst:

https://github.com/nunit/nunit/blob/d03e93c8f25170a8ff48e80863a3fe6fd294613a/src/NUnitFramework/framework/Assert.That.cs#L40 -L43

Stellt diese Dokumentation zusammen mit dem langjährigen Verhalten von NUnit nicht eine bahnbrechende Änderung in 3.10 dar?
Sofern ich nichts übersehe, sollten wir zumindest die jetzt falschen Dokumente entfernen.

Ich stimme zu, dass die Dokumentation jetzt irreführend sein kann, da wir mehrere Assertion-Blöcke eingeführt haben. Das heißt, AssertionException wird in diesem Fall immer noch ausgelöst, der Unterschied besteht darin, dass der Test jetzt in Visual Studio als Fehler gemeldet wird, obwohl die Ausnahme abgefangen wurde. Ich sehe das nicht als Breaking Change. Benutzer verließen sich auf undokumentiertes Verhalten, das zufällig funktionierte. Für mich ist das nicht anders, als dass Benutzer sich darauf verlassen, dass Tests zufällig in alphabetischer Reihenfolge ausgeführt werden, um ihre Tests zu ordnen.

Wir sollten wahrscheinlich die Dokumentation aktualisieren. Wir könnten die Informationen zum Auslösen einer Ausnahme entfernen und stattdessen einfach angeben, dass eine falsche Bedingung den Test nicht besteht. Und/oder könnten wir sagen, dass die Ausnahme nicht in Multi-Assert-Blöcken geworfen wird? Persönlich denke ich, dass die Art der ausgelösten Ausnahme ein internes Detail ist, daher bevorzuge ich die erste Option.

Ich denke das ist richtig . Wenn die Verwendung von Ausnahmen irgendwo dokumentiert werden soll, könnte dies im Wiki-Abschnitt zu Interna sein.

Wenn die Methodendokumentation sagt, dass eine Ausnahme ausgelöst wird, bedeutet dies meiner Meinung nach eindeutig, dass die Ausnahme an der Aufrufstelle abgefangen werden kann. C# hat keine geprüften Ausnahmen und XML-Dokumente sind die Art und Weise, wie wir diesen Teil des Methodenvertrags kommunizieren. Da die Sprache "wirft X" meines Wissens immer bedeutete, dass die Methode es nicht fängt, mache ich mir immer noch ein bisschen Sorgen, wie andere es aufgenommen haben. Vielleicht ein Breaking Change-Hinweis im Wiki zur Sicherheit?

Ich mag die Idee, alle Verweise auf AssertionException aus den XML-Dokumenten zu entfernen und eine Wiki-Seite zu erstellen, die die Leute davor warnt, AssertionException zu verwenden.

💭Wenn das Abfangen von AssertionException immer ein Fehler ist, können wir dann erwägen, diesen Typ intern zu machen?

@jnm2.

Ich stimme Ihnen bezüglich der Bedeutung der XML-Dokumente zu, aber Tatsache ist, dass diese Dokumente __nicht__ sind, wie wir einen Teil des Vertrags bis jetzt kommuniziert haben. Ich denke tatsächlich, dass Sie die erste Person sind, die es vorschlägt, und ich denke, es ist eine gute Idee.

Aber ich denke, Sie müssen erkennen, dass Sie, wenn Sie so darüber nachdenken, viele falsche Kommentare ändern müssen! Ich glaube nicht, dass wir jede dieser Dokumentänderungen als Breaking Change behandeln können. Von Fall zu Fall können wir jede zugrunde liegende Verhaltensänderung vernünftigerweise als brechend behandeln, aber das ist anders. Die Dokumentänderungen sollten nicht kaputt gehen.

Dies wirft die Frage auf, was in der Liste der Breaking Changes enthalten sein sollte. Wenn ich Ihre früheren Kommentare verstanden habe, neigen Sie meiner Meinung nach dazu, alles zu dokumentieren, was einen Benutzer kaputt machen könnte. Ich ziehe es vor, Dinge zu dokumentieren, von denen wir glauben, dass sie viele Benutzer verletzen würden.

So sehe ich den Unterschied - andere mögen anderer Meinung sein... __alles zu dokumentieren, was kaputt gehen könnte__ riecht für mich nach CYA, wie die Art von CYA, die sich das Management manchmal gönnt, wenn es sich darauf konzentriert, wer für das Scheitern verantwortlich gemacht wird, anstatt das Scheitern selbst zu verhindern. WENN wir alles dokumentieren, dann können wir sagen... "Sehen Sie, wir sind abgesichert." Wenn wir nur das dokumentieren, was wir als wichtig erachten, laufen wir Gefahr, falsch zu liegen und uns entschuldigen zu müssen, aber wir geben dem durchschnittlichen Benutzer eine viel leichter verständliche Reihe von Änderungen. Ich bevorzuge letzteres.

Es gibt Zeiten, in denen es sicher ist, eine Assertion-Ausnahme abzufangen. Zum Beispiel habe ich vor einiger Zeit eine Änderung vorgenommen, damit es sicher bleibt, wenn Tests direkt ausgeführt werden, ohne dass ein TestExecutionContext beteiligt ist. Wenn Sie die Ausnahme intern machen, dann brechen Sie das. Eigentlich war ich froh, es kaputt zu machen, aber das Team stimmte zu, dass wir es nicht sollten, also habe ich es repariert. Sie müssen nur entscheiden, was Sie zu brechen bereit sind. Dieser Fix hat __nicht__ das aktuelle Problem behandelt, das sich mit dem Abfangen der Ausnahme im Test selbst und nicht im Code befasst, der die Methode aufruft.

Das Abfangen von AssertionException in einem Test würde ich als klassischen "Geruch" bezeichnen. Es ist wahrscheinlich falsch, aber Sie müssen sich den konkreten Fall ansehen, um dies zu wissen. Ich würde den Benutzern sagen: "Das Abfangen von AssertionException ist höchstwahrscheinlich ein Fehler, es sei denn, Sie haben detaillierte Kenntnisse über die Interna von NUnit. Um ein ordnungsgemäßes Verhalten sicherzustellen, sollten Sie nicht versuchen, das Ergebnis des Tests zu ändern und die Ausnahme erneut auslösen, nachdem Sie getan haben, was Sie tun möchten damit." Ich glaube jedoch immer noch nicht, dass solche Informationen in die XML-Dokumente oder die allgemeinen Benutzerdokumente gehören. Ich würde es als Seite in den Abschnitt Interna setzen.

Übrigens, meine Erfahrung in der Vergangenheit war, dass das Dokumentieren von etwas und dann die Aufforderung an die Leute, es nicht zu verwenden, zu einer erhöhten Nutzung führt. 😄

Gute Punkte, danke! Ich denke, ich denke teilweise in Bezug auf CYA. Es gibt noch ein weiteres Element, nämlich dass ich als Nutzer von Bibliotheken es persönlich begrüßen würde, wenn mir jede wichtige Änderung mitgeteilt würde, damit ich in meinem eigenen Code nach schlechten Annahmen suchen kann. Vor allem bei solchen Änderungen, die der Compiler nicht bemerkt. Da ich das schätzen würde, projiziere ich das in gewissem Maße auf unsere Nutzer.

Sollen wir ein neues Problem starten, um die Fehlerbehebung in den Dokumenten zu verfolgen?

Warum neu?

Der Titel sieht aus wie etwas, das wir als beantwortete Frage schließen würden, anstatt es zu reparieren. Würden wir normalerweise den Titel ändern und dieses Problem verwenden, um die Dokumentänderung zu verfolgen?

Normalerweise überprüfe ich das Problem und klassifiziere es ... in diesem Fall als Dokumentationsproblem. Zugegeben, wir haben lange Zeit damit verbracht, es zuerst als möglichen Fehler zu diskutieren. Es ist wirklich eine Frage der Projektverwaltung und liegt außerhalb meines Zuständigkeitsbereichs.

Was ich sage, ist, etwas Logisches und Konsistentes zu tun. 😄 Dies als kein Fehler zu schließen ist eine Option. Das trennt jedoch die Beschwerde von der Lösung. Wenn Sie einen erläuternden Kommentar schreiben, während Sie ihn als Docs-Fehler reklassifizieren und möglicherweise den Titel bearbeiten, bleibt die Lösung mit der Beschwerde erhalten. Auf jeden Fall landet der Issue-Titel von Done Issues in den Versionshinweisen, also sollte er ausdrücken, was passiert ist.

Können Sie ein Beispiel dafür geben, was Sie tatsächlich versuchen, um die Ausnahme abzufangen? Ich bin mir ziemlich sicher, dass wir Ihnen helfen können, eine bessere Lösung zu finden.

Ich stehe derzeit vor dem gleichen Problem, da meine Tests nach dem Upgrade auf 3.10 fehlgeschlagen sind. Hier nun mein Problem:
Ich habe benutzerdefinierte Testzusicherungsmethoden. Diese ruft intern NUnit.Assert auf. Ich teste diese Assertions-Methoden, um sicherzustellen, dass sie tatsächlich fehlschlagen, wenn ich erwarte, dass sie fehlschlagen.
Derzeit fangen meine Tests AssertionException , um zu überprüfen, ob die benutzerdefinierte Assertion-Methode einen Test fehlschlägt, aber nach dem Upgrade schlagen alle diese Tests aus den oben genannten Gründen fehl.

Was ist die empfohlene Methode, diese mit 3.10 zu testen?

Vielen Dank.

Der einfachste Ansatz besteht darin, die Ausnahme mithilfe von Assert.Throws abzufangen, anstatt dies selbst zu tun. Assert.Throws versteht die Interna von NUnit und stellt sicher, dass der Fehler nicht gemeldet wird.

Ich habe das neulich in meinen Benachrichtigungen gesehen und mir nicht viel dabei gedacht. Aktualisieren Sie jedoch ein Bibliotheksprojekt von mir, das "Assert.That" in seiner Logik ausführt (es ist ein Test-Hilfs-Framework - speziell zum Bereitstellen einer einfachen Möglichkeit zum Testen der EF-Persistenz beim Codieren von EF-Kontexten und -Entitäten von Hand und Codieren der entsprechenden Datenbank Migrationen von Hand (oder mit so etwas wie FluentMigrator) – mit anderen Worten, es soll Assertionen im Namen des Aufrufers durchführen), bekomme ich jetzt Tests, die dort fehlschlagen, wo sie nicht sein sollten – der eine Test ist insbesondere die Behauptung dass eine bestimmte Bedingung Testfehler auslösen sollte - was sie tut, aber da der "Fang" nicht mehr wie immer funktioniert, werden diese Tests, obwohl sie tatsächlich bestanden wurden, als fehlgeschlagen gemeldet.

Ich denke, ich kann keine richtigen NUnit-Assertionen werfen oder Assert.That im Bibliothekscode verwenden, der eine schnellere Testentwicklung unterstützen soll, indem er mühsame Aufgaben für den Benutzer ausführt?

Ich stimme früheren Aussagen zu, dass, wenn die Ausnahme nicht vom Benutzer verwendet werden kann, sie für den Benutzer nicht zum Auslösen verfügbar sein sollte. Das löst jedoch nicht das Problem, bei dem ich Assert.That Code für den Verbraucher und Tests habe, um zu beweisen, dass sie fehlschlagen, weil der Code das tut, was er soll: mit Nunit-Behauptungen fehlschlagen.

Vielleicht sollten wir einige der Ergebnis-APIs produzieren?

@fluffynuts Wenn ich Sie richtig verstehe, befindet sich die catch Klausel in Ihrem Testcode , nicht im Code Ihrer Benutzerbibliothek. (Wenn es sich auch in der Benutzerbibliothek befindet, können wir darüber sprechen, aber die Probleme sind etwas anders.)

Natürlich hat NUnit Selbsttests, also hatten wir genau das gleiche potenzielle Problem, als diese Änderung vorgenommen wurde. Natürlich haben wir unsere eigenen Tests so modifiziert, dass sie weiterhin bestanden haben. Sie können dasselbe tun.

Die erste Verteidigungslinie ist Assert.Throws . In den meisten Fällen beim Testen des Fehlerverhaltens können Sie try / catch(AssertionException) Assert.Throws<AssertionException> direkt durch Assert.Throws ein wegwerfbares isoliertes TestExecutionContext damit Ihre tatsächlichen Testergebnisse nicht durch den von Ihnen getesteten Fehler verfälscht werden.

In einigen Fällen müssen Sie möglicherweise Ihren eigenen isolierten Kontext erstellen. Sie können sehen, wie das geht, indem Sie den Code für Assert.Throws .

Viele der eigenen Fehlertests von NUnit verwenden jedoch einen anderen Ansatz. Wir haben eine separate Assembly mit Tests, die von unseren eigenen Testprogrammen ausgeführt werden. Wir erfassen das Ergebnis und können es dann begutachten. Das ist mehr Arbeit, vermeidet aber, zu viele Implementierungsdetails von NUnit in unsere Tests einfließen zu lassen. Die meisten Methoden dazu finden Sie in unserer Testdienstprogramm-Assembly. Ich denke, darauf bezieht sich

Stellen Sie alle Fragen, die Sie haben, oder kontaktieren Sie mich offline, um weitere Hilfe dabei zu erhalten.

Danke für die Information. Ja, die Assertionen werden im Testcode abgefangen. Assert.Throws würde normalerweise den Zweck erfüllen, aber der fragliche spezifische Test führt den Bibliothekscode mit einem "zu niedrigen" Delta aus, das zum Testen eines Datums-/Uhrzeitwerts zulässig ist, der eingegeben und aus einer Datenbank abgerufen werden sollte. Meistens beträgt die Drift in localdb 1 ms, aber manchmal driftet sie höher - und dieser Test soll nur bestätigen, dass das "manchmal" passiert. Im Moment führe ich den Code also in 4 parallelen Threads 10 Mal aus und erwarte mindestens einen Fehler. Assert.Throws ist für diesen Fall leider zu deterministisch, daher muss ich entweder aufhören, Nunit-Ausnahmen zu verwenden (oder Assert im Bibliothekscode), oder ich muss die Fu lernen, die Sie verwenden. Ich würde mich über jeden Hinweis freuen - kann offline passieren, wenn Sie möchten.

Heh, ich habe gerade festgestellt, dass die Fehler beim Message-Pump-Test für mein AsyncVoidVerificationScope, die ich angestarrt habe, genau das gleiche Problem sind wie @fluffynuts . Eigentlich aufgrund des NUnit 3.10-Upgrades, nicht meiner [assembly: RestoreSynchronizationContext] ITestAction. Dient mir recht, wenn ich sie ignoriere, während ich mehrere Dinge gleichzeitig mache. :D

Wie auch immer, es sieht so aus, als ob ich meinen Delegatenaufruf in using (new TestExecutionContext.IsolatedContext()) einpacken möchte, genau wie Assert.Throws tut.

Assert.Throws und Assert.ThrowsAsync richten jedoch keine isolierten Kontexte ein, bevor asynchrone Delegaten aufgerufen werden. Warum ist das?

@fluffynuts Vor langer Zeit, als ich Assert.Catch ersten Mal schrieb, hat es nur eine Ausnahme abgefangen und keinen Fehler generiert, wenn keine Ausnahme ausgelöst wurde. Leider hat sich das geändert und wir haben kein "neutrales" Äquivalent von Assert.Throws . Sie könnten jedoch einen schreiben. Hier ist eine ungetestete Version, die auf dem Code in Assert.Throws basiert. Es verarbeitet nur synchronen Code, aber Sie können es leicht für asynchronen erweitern, indem Sie zusätzlichen Code von Assert.Throws .

```C#
public static void Exception SafelyCatchAnNUnitException(TestDelegate-Code)
{
Ausnahme catchException = null;

using (new TestExecutionContext.IsolatedContext())
{
    try
    {
        code();
    }
    catch (Exception ex)
    {
        caughtException = ex;
    }

    return caughtException;
}

}
```

Die Methode gibt jede ausgelöste Ausnahme zurück und bereinigt alle Überreste des Fehlers, die von den Assert-Methoden von NUnit hinterlassen wurden. Wenn keine Ausnahme ausgelöst wird, wird null zurückgegeben.

Es ist auch möglich, IsolatedContext() auf einer höheren Ebene in Ihrem Code zu verwenden, um daraus das eigentliche Testergebnis abzurufen. Dies würde es Ihnen ermöglichen, vollständig auf der Ebene des Testergebnisses zu testen, ohne auf Ausnahmen Bezug zu nehmen. Ich habe mir überlegt, eine Bibliothek für diese Art von Tests zu schreiben, die meiner Meinung nach der Art und Weise, wie NUnit derzeit seine eigenen Tests durchführt, überlegen wäre.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen