Ist es in Ordnung, mehrere Asserts in einem einzigen Unit-Test zu haben?

397

In dem Kommentar zu diesem großartigen Beitrag erwähnte Roy Osherove das OAPT- Projekt, mit dem jede Zusicherung in einem einzigen Test ausgeführt werden soll.

Folgendes steht auf der Homepage des Projekts:

Richtige Komponententests sollten aus genau einem Grund fehlschlagen. Aus diesem Grund sollten Sie eine Zusicherung pro Komponententest verwenden.

Und auch Roy schrieb in Kommentaren:

Meine Richtlinie ist normalerweise, dass Sie ein logisches KONZEPT pro Test testen. Sie können mehrere Zusicherungen für dasselbe Objekt haben . In der Regel handelt es sich dabei um dasselbe Konzept, das getestet wird.

Ich denke, dass es einige Fälle gibt, in denen mehrere Behauptungen erforderlich sind (z. B. Behauptung der Wache ), aber im Allgemeinen versuche ich, dies zu vermeiden. Was ist deine Meinung? Bitte geben Sie ein Beispiel aus der Praxis an, in dem mehrere Asserts wirklich benötigt werden .

Restuta
quelle
2
Wie spottest du, ohne mehrere Behauptungen zu haben? Jede Erwartung an den Schein ist eine Behauptung für sich, einschließlich der Reihenfolge der Anrufe, die Sie auferlegen.
Christopher Creutzig
15
Ich habe gesehen, wie die One-Assert-per-Method-Philosophie in der Vergangenheit missbraucht wurde. Ein alter Mitarbeiter nutzte einen funky Vererbungsmechanismus, um dies zu ermöglichen. Dies führte zu vielen Unterklassen (eine pro Zweig) und zu vielen Tests, bei denen derselbe Auf- und Abbauprozess durchgeführt wurde, um nur die unterschiedlichen Ergebnisse zu überprüfen. Es war langsam, schwer zu lesen und ein ernstes Wartungsproblem. Ich habe ihn nie davon überzeugt, zu einem klassischeren Ansatz zurückzukehren. Gerard Meszaros Buch spricht ausführlich über dieses Thema.
Travis Parks
3
Ich denke, als Faustregel sollten Sie versuchen, die Anzahl der Asserts pro Test zu minimieren. Solange der Test das Problem jedoch auf eine bestimmte Stelle im Code einschränkt, ist er ein nützlicher Test.
ConditionRacer
2
Ich habe Fälle gesehen, in denen mehrere RowTestZusicherungen anstelle von (MbUnit) / TestCase(NUnit) verwendet wurden, um eine Vielzahl von Randfallverhalten zu testen. Verwenden Sie die richtigen Werkzeuge für den Job! (Leider scheint MSTest noch keine Zeilentestfunktion zu haben.)
GalacticCowboy
@GalacticCowboy Sie können ähnliche Funktionen für RowTestund TestCasemit Testdatenquellen erhalten . Ich verwende eine einfache CSV-Datei mit großem Erfolg.
Julealgon

Antworten:

236

Ich denke nicht, dass es notwendigerweise eine schlechte Sache ist , aber ich denke, wir sollten uns bemühen, nur einzelne Aussagen in unseren Tests zu haben. Das bedeutet, dass Sie viel mehr Tests schreiben und unsere Tests immer nur eine Sache gleichzeitig testen.

Davon abgesehen würde ich sagen, dass vielleicht die Hälfte meiner Tests tatsächlich nur eine Aussage enthält. Ich denke, es wird nur dann zu einem Code (Test?) - Geruch, wenn Sie ungefähr fünf oder mehr Asserts in Ihrem Test haben.

Wie lösen Sie mehrere Behauptungen?

Jaco Pretorius
quelle
9
Wie diese Antwort - dh es ist OK, aber es ist im Allgemeinen nicht gut (-:
Murph
5
Eh? Warum würdest du das tun? Die Ausführung der Methode ist genau die gleiche?
jgauffin
197
Eine einzelne Bestätigung pro Testeinheit ist eine hervorragende Möglichkeit, die Fähigkeit des Lesers zu testen, nach oben und unten zu scrollen.
Tom
3
Irgendwelche Gründe dafür? In dieser aktuellen Antwort heißt es nur, was es sein sollte, aber nicht warum.
Steven Jeuris
37
Entschieden widersprechen. In der Antwort wird kein Vorteil einer einzelnen Zusicherung aufgeführt, und der offensichtliche Nachteil besteht darin, dass Sie Tests nur kopieren und einfügen müssen, weil dies in einer Antwort im Internet angegeben ist. Wenn Sie mehrere Felder eines Ergebnisses oder mehrere Ergebnisse einer einzelnen Operation testen müssen, sollten Sie unbedingt alle in unabhängigen Asserts bestätigen, da dies weitaus nützlichere Informationen liefert als das Testen in einem großen Blob-Assert. In einem guten Test testen Sie eine einzelne Operation und nicht ein einzelnes Ergebnis einer Operation.
Peter
296

Tests sollten nur aus einem Grund fehlschlagen, aber das bedeutet nicht immer, dass es nur eine AssertAussage geben sollte. IMHO ist es wichtiger, sich an das Muster " Anordnen, Handeln, Bestätigen " zu halten .

Der Schlüssel ist, dass Sie nur eine Aktion haben und dann die Ergebnisse dieser Aktion mit Asserts überprüfen. Aber es ist „anordnen, Act, Assert, Testende “. Wenn Sie versucht sind, mit dem Testen fortzufahren, indem Sie eine weitere Aktion ausführen, und anschließend weitere Asserts ausführen, führen Sie stattdessen einen separaten Test durch.

Ich freue mich über mehrere Aussagen, die zum Testen derselben Aktion gehören. z.B

[Test]
public void ValueIsInRange()
{
  int value = GetValueToTest();

  Assert.That(value, Is.GreaterThan(10), "value is too small");
  Assert.That(value, Is.LessThan(100), "value is too large");
} 

oder

[Test]
public void ListContainsOneValue()
{
  var list = GetListOf(1);

  Assert.That(list, Is.Not.Null, "List is null");
  Assert.That(list.Count, Is.EqualTo(1), "Should have one item in list");
  Assert.That(list[0], Is.Not.Null, "Item is null");
} 

Sie könnten diese in einer Aussage kombinieren, aber das ist etwas anderes, als darauf zu bestehen, dass Sie sollten oder müssen . Es gibt keine Verbesserung, wenn man sie kombiniert.

zB der erste könnte sein

Assert.IsTrue((10 < value) && (value < 100), "Value out of range"); 

Dies ist jedoch nicht besser - die daraus resultierende Fehlermeldung ist weniger spezifisch und hat keine weiteren Vorteile. Ich bin sicher, Sie können sich andere Beispiele vorstellen, bei denen die Kombination von zwei oder drei (oder mehr) Asserts zu einer großen booleschen Bedingung das Lesen erschwert, das Ändern erschwert und das Ermitteln der Fehlerursache erschwert. Warum tun Sie dies nur aus Gründen der Regel?

NB : Der Code, den ich hier schreibe, ist C # mit NUnit, aber die Prinzipien gelten für andere Sprachen und Frameworks. Die Syntax kann auch sehr ähnlich sein.

Anthony
quelle
32
Der Schlüssel ist, dass Sie nur eine Aktion haben und dann die Ergebnisse dieser Aktion mit Asserts überprüfen.
Amitābha
1
Ich finde es auch interessant, mehr als einen Assert zu haben, wenn Arrangieren zeitaufwendig ist.
Rekshino
1
@Rekshino, wenn arrang zeitaufwendig ist, können wir den arrang-Code gemeinsam nutzen, indem wir ihn beispielsweise in die Testinitialisierungsroutine einfügen.
Shaun Luttin
2
Wenn ich also mit Jacos Antwort vergleiche, wird die "einzige Behauptung" zu einer "einzigen Gruppe von Behauptungen", die für mich sinnvoller ist.
Walfrat
2
Dies ist eine gute Antwort, aber ich bin nicht der Meinung, dass eine einzige Behauptung nicht besser ist. Das Beispiel für schlechte Behauptungen ist nicht besser, aber das bedeutet nicht, dass eine einzige Behauptung nicht besser wäre, wenn sie richtig gemacht würde. Viele Bibliotheken erlauben benutzerdefinierte Asserts / Matcher, sodass etwas erstellt werden kann, wenn es noch nicht vorhanden ist. Zum Beispiel , Assert.IsBetween(10, 100, value)das druckt Expected 8 to be between 10 and 100 ist besser als zwei meiner Meinung nach getrennt behauptet. Sie können sicher argumentieren, dass dies nicht notwendig ist, aber es lohnt sich normalerweise zu überlegen, ob es einfach ist, sich auf eine einzelne Aussage zu beschränken, bevor Sie eine ganze Reihe davon erstellen.
Thor84no
85

Ich hätte nie gedacht, dass mehr als eine Behauptung eine schlechte Sache ist.

Das mache ich die ganze Zeit:

public void ToPredicateTest()
{
    ResultField rf = new ResultField(ResultFieldType.Measurement, "name", 100);
    Predicate<ResultField> p = (new ConditionBuilder()).LessThanConst(400)
                                                       .Or()
                                                       .OpenParenthesis()
                                                       .GreaterThanConst(500)
                                                       .And()
                                                       .LessThanConst(1000)
                                                       .And().Not()
                                                       .EqualsConst(666)
                                                       .CloseParenthesis()
                                                       .ToPredicate();
    Assert.IsTrue(p(ResultField.FillResult(rf, 399)));
    Assert.IsTrue(p(ResultField.FillResult(rf, 567)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 400)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 666)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 1001)));

    Predicate<ResultField> p2 = (new ConditionBuilder()).EqualsConst(true).ToPredicate();

    Assert.IsTrue(p2(new ResultField(ResultFieldType.Confirmation, "Is True", true)));
    Assert.IsFalse(p2(new ResultField(ResultFieldType.Confirmation, "Is False", false)));
}

Hier verwende ich mehrere Asserts, um sicherzustellen, dass komplexe Bedingungen in das erwartete Prädikat umgewandelt werden können.

Ich teste nur eine Einheit (die ToPredicateMethode), aber ich decke alles ab, was mir im Test einfällt.

Matt Ellen
quelle
46
Mehrere Zusicherungen sind aufgrund der Fehlererkennung fehlerhaft. Wenn Sie Ihr erstes Assert.IsTrue nicht bestanden haben, werden andere Asserts nicht ausgeführt und Sie erhalten keine Informationen von ihnen. Auf der anderen Seite, wenn Sie 5 Tests statt 1 mit 5 Behauptungen hatten, könnten Sie etwas
Sly
6
Finden Sie es immer noch schlecht, wenn alle Asserts dieselbe Art von Funktionalität testen? Wie oben testet das Beispiel die Bedingungen, und wenn dies fehlschlägt, sollten Sie es beheben. Ist es für Sie von Bedeutung, dass Sie die letzten beiden Behauptungen möglicherweise verpassen, wenn eine frühere fehlschlägt?
Cringe
106
Ich behebe meine Probleme nacheinander. Die Tatsache, dass der Test mehr als einmal fehlschlagen könnte, stört mich also nicht. Wenn ich sie aufteilen würde, würden die gleichen Fehler auftreten, aber auf einmal. Ich finde es einfacher, Dinge Schritt für Schritt zu reparieren. Ich gebe zu, dass in diesem Fall die letzten beiden Behauptungen wahrscheinlich in ihren eigenen Test überarbeitet werden konnten.
Matt Ellen
19
Ihr Fall ist sehr repräsentativ, deshalb hat NUnit das zusätzliche Attribut TestCase - nunit.org/?p=testCase&r=2.5
Restuta
10
Das Google C ++ - Testframework enthält ASSERT () und EXPECT (). Das ASSERT () stoppt bei einem Fehler, während EXPECT () fortfährt. Das ist sehr praktisch, wenn Sie mehr als eine Sache im Test validieren möchten.
Ratkok
21

Wenn ich Unit-Tests verwende, um das Verhalten auf hoher Ebene zu validieren, füge ich auf jeden Fall mehrere Behauptungen zu einem einzigen Test zusammen. Hier ist ein Test, den ich für einen Notfallbenachrichtigungscode verwende. Der Code, der vor dem Test ausgeführt wird, versetzt das System in einen Zustand, in dem ein Alarm gesendet wird, wenn der Hauptprozessor ausgeführt wird.

@Test
public void testAlarmSent() {
    assertAllUnitsAvailable();
    assertNewAlarmMessages(0);

    pulseMainProcessor();

    assertAllUnitsAlerting();
    assertAllNotificationsSent();
    assertAllNotificationsUnclosed();
    assertNewAlarmMessages(1);
}

Es stellt die Bedingungen dar, die in jedem Schritt des Prozesses vorhanden sein müssen, damit ich sicher sein kann, dass sich der Code wie erwartet verhält. Wenn eine einzelne Behauptung fehlschlägt, ist es mir egal, dass die verbleibenden nicht einmal ausgeführt werden. Da der Status des Systems nicht mehr gültig ist, sagen mir diese nachfolgenden Behauptungen nichts Wertvolles. * Wenn dies assertAllUnitsAlerting()fehlschlägt, weiß ich nicht, was ich von seinem assertAllNotificationSent()Erfolg oder Misserfolg halten soll, bis ich herausgefunden habe, was den vorherigen Fehler verursacht hat und korrigiert es.

(* - Okay, möglicherweise sind sie beim Debuggen des Problems hilfreich. Die wichtigsten Informationen, dass der Test fehlgeschlagen ist, sind jedoch bereits eingegangen.)

BlairHippo
quelle
Wenn Sie das tun, sollten Sie besser Test-Frameworks mit abhängigen Tests verwenden, es ist besser (zB testng unterstützt diese Funktion)
Kemoda
8
Ich schreibe auch solche Tests, damit Sie sicher sein können, was der Code tut und wie sich der Status ändert. Ich halte dies nicht für einen Komponententest, sondern für einen Integrationstest.
mdma
Was halten Sie davon, es in einen assertAlarmStatus (int numberOfAlarmMessages) umzugestalten?
Borjab
1
Ihre Aussagen würden für sehr schöne Testnamen sorgen.
Björn Tipling
Es ist besser, den Test auch bei ungültigen Eingaben laufen zu lassen. Auf diese Weise erhalten Sie mehr Informationen (und insbesondere dann, wenn diese weiterhin bestehen, wenn Sie dies nicht erwartet haben).
CurtainDog
8

Ein weiterer Grund, warum ich denke, dass mehrere Asserts in einer Methode keine schlechte Sache sind, ist im folgenden Code beschrieben:

class Service {
    Result process();
}

class Result {
    Inner inner;
}

class Inner {
    int number;
}

In meinem Test möchte ich einfach testen, dass service.process()in InnerKlasseninstanzen die richtige Anzahl zurückgegeben wird .

Anstatt zu testen ...

@Test
public void test() {
    Result res = service.process();
    if ( res != null && res.getInner() != null ) Assert.assertEquals( ..., res.getInner() );
}

Ich mache

@Test
public void test() {
    Result res = service.process();
    Assert.notNull(res);
    Assert.notNull(res.getInner());
    Assert.assertEquals( ..., res.getInner() );
}
Betlista
quelle
2
Und das ist eine gute Sache, Sie sollten keine bedingte Logik in Ihren Tests haben. Dadurch wird der Test komplexer und weniger lesbar. Und ich denke, Roy hat es in seinem Blog-Post richtig umrissen, dass mehrere Behauptungen zu einem Objekt die meiste Zeit in Ordnung sind. Diejenigen, die Sie haben, sind nur Wächterbehauptungen, und es ist in Ordnung, sie zu haben.
Restuta
2
Ihre Assert.notNulls sind redundant, Ihr Test schlägt mit einem NPE fehl, wenn sie null sind.
Sara
1
Auch Ihr erstes Beispiel (mit dem if) wird passieren , wenn resistnull
sara
1
@kai notNulls sind überflüssig, ich stimme zu, aber ich denke, dass es sauberer ist, die Behauptung zu haben (und wenn ich nicht faul bin auch mit der richtigen Nachricht) statt Ausnahme ...
Betlista
1
Nun, eine fehlgeschlagene Behauptung löst auch eine Ausnahme aus, und in beiden Fällen erhalten Sie einen direkten Link zu der exakten Zeile, die sie mit einem begleitenden Stack-Trace ausgelöst hat. Ich würde einen Oneliner à la bevorzugen Assert.assertEquals(..., service.process().getInner());, der mit extrahierten Variablen möglich ist, wenn die Zeile "zu lang" wird
Sara
6

Ich denke, es gibt viele Fälle, in denen das Schreiben mehrerer Behauptungen innerhalb der Regel gültig ist, dass ein Test nur aus einem Grund fehlschlagen sollte.

Stellen Sie sich zum Beispiel eine Funktion vor, die eine Datumszeichenfolge analysiert:

function testParseValidDateYMD() {
    var date = Date.parse("2016-01-02");

    Assert.That(date.Year).Equals(2016);
    Assert.That(date.Month).Equals(1);
    Assert.That(date.Day).Equals(0);
}

Wenn der Test aus einem Grund fehlschlägt, ist die Analyse falsch. Wenn Sie argumentieren würden, dass dieser Test aus drei verschiedenen Gründen fehlschlagen kann, wären Sie meiner Meinung nach in Ihrer Definition von "einem Grund" zu feinkörnig.

Pete
quelle
3

Ich kenne keine Situation, in der es eine gute Idee wäre, mehrere Behauptungen innerhalb der [Test] -Methode selbst zu haben. Der Hauptgrund, warum Leute gerne mehrere Assertions haben, ist, dass sie versuchen, für jede getestete Klasse eine [TestFixture] -Klasse zu haben. Stattdessen können Sie Ihre Tests in weitere [TestFixture] -Klassen aufteilen. Auf diese Weise können Sie mehrere Methoden anzeigen, bei denen der Code möglicherweise nicht wie erwartet reagiert hat, anstatt nur die, bei der die erste Zusicherung fehlgeschlagen ist. Auf diese Weise haben Sie mindestens ein Verzeichnis pro Klasse, das mit vielen [TestFixture] -Klassen getestet wird. Jede [TestFixture] -Klasse wird nach dem spezifischen Status eines zu testenden Objekts benannt. Die Methode [SetUp] versetzt das Objekt in den durch den Klassennamen beschriebenen Zustand. Dann haben Sie mehrere [Test] -Methoden, von denen jede verschiedene Dinge bestätigt, von denen Sie erwarten, dass sie wahr sind, wenn der aktuelle Status des Objekts gegeben ist. Jede [Test] -Methode ist nach dem, was sie behauptet, benannt, es sei denn, sie ist möglicherweise nach dem Konzept benannt, anstatt nur den Code auf Englisch auszulesen. Dann benötigt jede Implementierung der [Test] -Methode nur eine einzige Codezeile, in der etwas behauptet wird. Ein weiterer Vorteil dieses Ansatzes besteht darin, dass die Tests sehr gut lesbar sind, da deutlich wird, was Sie testen und was Sie erwarten, wenn Sie sich nur die Klassen- und Methodennamen ansehen. Dies wird auch besser skaliert, wenn Sie anfangen, all die kleinen Randfälle zu erkennen, die Sie testen möchten, und wenn Sie Fehler finden. außer vielleicht könnte es nach dem Konzept benannt werden, anstatt nur einen englischen Code auszulesen. Dann benötigt jede Implementierung der [Test] -Methode nur eine einzige Codezeile, in der etwas behauptet wird. Ein weiterer Vorteil dieses Ansatzes besteht darin, dass die Tests sehr gut lesbar sind, da deutlich wird, was Sie testen und was Sie erwarten, wenn Sie sich nur die Klassen- und Methodennamen ansehen. Dies wird auch besser skaliert, wenn Sie anfangen, all die kleinen Randfälle zu erkennen, die Sie testen möchten, und wenn Sie Fehler finden. außer vielleicht könnte es nach dem Konzept benannt werden, anstatt nur einen englischen Code auszulesen. Dann benötigt jede Implementierung der [Test] -Methode nur eine einzige Codezeile, in der etwas behauptet wird. Ein weiterer Vorteil dieses Ansatzes besteht darin, dass die Tests sehr gut lesbar sind, da deutlich wird, was Sie testen und was Sie erwarten, wenn Sie sich nur die Klassen- und Methodennamen ansehen. Dies wird auch besser skaliert, wenn Sie anfangen, all die kleinen Randfälle zu erkennen, die Sie testen möchten, und wenn Sie Fehler finden. und was Sie erwarten, wenn Sie sich die Klassen- und Methodennamen ansehen. Dies wird auch besser skaliert, wenn Sie anfangen, all die kleinen Randfälle zu erkennen, die Sie testen möchten, und wenn Sie Fehler finden. und was Sie erwarten, wenn Sie sich die Klassen- und Methodennamen ansehen. Dies wird auch besser skaliert, wenn Sie anfangen, all die kleinen Randfälle zu erkennen, die Sie testen möchten, und wenn Sie Fehler finden.

Normalerweise bedeutet dies, dass die letzte Codezeile in der [SetUp] -Methode einen Eigenschaftswert oder einen Rückgabewert in einer privaten Instanzvariablen der [TestFixture] speichern sollte. Dann können Sie verschiedene Dinge über diese Instanzvariable aus verschiedenen [Test] -Methoden bestätigen. Sie können auch Aussagen darüber treffen, welche unterschiedlichen Eigenschaften des zu testenden Objekts auf den gewünschten Zustand eingestellt sind.

Manchmal müssen Sie während des Versuchs, das zu testende Objekt in den gewünschten Zustand zu versetzen, Behauptungen aufstellen, um sicherzustellen, dass Sie nichts falsch gemacht haben, bevor Sie das Objekt in den gewünschten Zustand versetzt haben. In diesem Fall sollten diese zusätzlichen Zusicherungen in der [SetUp] -Methode erscheinen. Wenn in der [SetUp] -Methode etwas schief geht, ist klar, dass mit dem Test etwas nicht stimmte, bevor das Objekt in den gewünschten Zustand kam, den Sie testen wollten.

Ein weiteres Problem ist, dass Sie möglicherweise eine Ausnahmebedingung testen, deren Auslösung Sie erwartet haben. Dies kann Sie dazu verleiten, dem obigen Modell nicht zu folgen. Dies kann jedoch trotzdem erreicht werden, indem die Ausnahme in der [SetUp] -Methode abgefangen und in einer Instanzvariablen gespeichert wird. Auf diese Weise können Sie verschiedene Dinge über die Ausnahme behaupten, und zwar jeweils mit einer eigenen [Test] -Methode. Sie können dann auch andere Dinge über das zu prüfende Objekt behaupten, um sicherzustellen, dass die Ausnahmebedingung keine unbeabsichtigten Nebenwirkungen hat.

Beispiel (dies würde in mehrere Dateien aufgeteilt):

namespace Tests.AcctTests
{
    [TestFixture]
    public class no_events
    {
        private Acct _acct;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
        }

        [Test]
        public void balance_0() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_0
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(0m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_negative
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(-0.01m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }
}
INTPnerd
quelle
Wie gehen Sie in diesem Fall mit TestCase-Eingaben um?
Simon Gillbee
In meiner derzeitigen Organisation gibt es also über 20.000 Unit-Tests, die Ihren Vorstellungen sehr ähnlich sind. Das ist ein Albtraum. Ein Großteil des Test-Setup-Codes wird kopiert / eingefügt, was zu einem falschen Test-Setup und ungültigen Tests führt. Für jede [Test]Methode wird diese Klasse erneut instanziiert und die [SetUp]Methode erneut ausgeführt. Dadurch wird der .NET-Garbage-Collector beendet und die Tests werden extrem langsam ausgeführt: mehr als 5 Minuten lokal, mehr als 20 Minuten auf dem Buildserver. 20K-Tests sollten in ca. 2-3 Minuten durchgeführt werden. Ich würde diesen Teststil überhaupt nicht empfehlen, insbesondere für eine große Testsuite.
Mitternacht,
@fourpastmidnight Das meiste von dem, was du gesagt hast, scheint berechtigte Kritik zu sein, aber der Punkt über das Kopieren und Einfügen von Setup-Code und damit falsch zu sein, ist kein Strukturproblem, sondern ein Problem verantwortungsloser Programmierer (was das Ergebnis verantwortungsloser Manager oder einer negativen Umgebung sein könnte mehr als schlechte Programmierer). Wenn die Leute einfach Code kopieren und einfügen und hoffen, dass er korrekt ist und sich nicht die Mühe machen, den Code zu verstehen, müssen sie entweder geschult werden, dies nicht zu tun, oder sie müssen loslassen, wenn dies nicht möglich ist trainiert. Das widerspricht jeder guten Programmierpraxis.
still_dreaming_1
Aber im Allgemeinen stimme ich zu, dass dies ein verrückter Overkill ist, der zu viel Aufblähen / Gepäck / Verdoppelung führen wird, was zu allen möglichen Problemen führen wird. Früher war ich verrückt und empfehle solche Sachen. Das hoffe ich jeden Tag über mich selbst am Vortag sagen zu können, denn das bedeutet, dass ich immer bessere Wege finde, Dinge zu tun.
still_dreaming_1
@ still_dreaming_1 wrt "unverantwortliche Programmierer": Ich bin damit einverstanden, dass dieses Verhalten ein großes Problem darstellt. Diese Art von Teststruktur lädt jedoch wirklich zu einem solchen Verhalten ein, zum Guten oder Schlechten. Abgesehen von schlechten Entwicklungspraktiken ist mein Hauptargument gegen dieses Formular, dass es die Testleistung wirklich beeinträchtigt. Es gibt nichts Schlimmeres als eine langsam laufende Testsuite. Eine langsam laufende Testsuite bedeutet, dass Benutzer keine Tests lokal ausführen und sogar versucht sind, sie bei Zwischenerstellungen zu überspringen - wieder ein Problem für Benutzer, aber es kommt vor -, was alles vermieden werden kann, indem sichergestellt wird, dass Tests schnell ausgeführt werden mit.
4 Uhr nachmittags,
2

Es ist nur dann ein Problem, wenn der Test fehlschlägt, wenn mehrere Behauptungen im selben Test enthalten sind. Dann müssen Sie möglicherweise den Test debuggen oder die Ausnahme analysieren, um herauszufinden, welche Behauptung fehlschlägt. Mit einer Aussage in jedem Test ist es normalerweise einfacher festzustellen, was falsch ist.

Ich kann mir kein Szenario vorstellen, in dem mehrere Zusicherungen wirklich benötigt werden , da Sie sie immer als mehrere Bedingungen in derselben Zusicherung umschreiben können. Es kann jedoch vorzuziehen sein, wenn Sie beispielsweise mehrere Schritte ausführen, um die Zwischendaten zwischen den Schritten zu überprüfen, anstatt zu riskieren, dass die späteren Schritte aufgrund einer schlechten Eingabe abstürzen.

Guffa
quelle
1
Wenn Sie mehrere Bedingungen zu einer einzigen Zusicherung zusammenfassen, ist bei einem Fehlschlagen nur diejenige bekannt, die fehlgeschlagen ist. Bei mehreren Zusicherungen kennen Sie einige davon (die bis einschließlich des Ausfalls) genau. Prüfen Sie, ob ein zurückgegebenes Array einen einzelnen Wert enthält: Überprüfen Sie, ob es nicht null ist, dass es genau ein Element und dann den Wert dieses Elements enthält. (Abhängig von der Plattform) Wenn Sie den Wert sofort überprüfen, kann dies zu einer Null-Dereferenzierung führen (weniger hilfreich als das Fehlschlagen der Null-Assertion), und die Array-Länge wird nicht überprüft.
Richard
@Richard: Ein Ergebnis zu erhalten und dann etwas aus diesem Ergebnis zu extrahieren, wäre ein Prozess in mehreren Schritten. Ich habe das im zweiten Absatz der Antwort behandelt.
Guffa
2
Faustregel: Wenn ein Test mehrere Aussagen enthält, sollte für jede eine andere Meldung angezeigt werden. Dann hast du dieses Problem nicht.
Anthony
Und mit einem Qualitätstestläufer wie NCrunch können Sie genau feststellen, in welcher Zeile der Test fehlgeschlagen ist, sowohl im Testcode als auch im zu testenden Code.
Mitternacht,
2

Wenn Ihr Test fehlschlägt, wissen Sie nicht, ob die folgenden Aussagen ebenfalls stimmen. Oft bedeutet dies, dass Sie wertvolle Informationen verpassen, um die Ursache des Problems herauszufinden. Meine Lösung besteht darin, eine Zusicherung mit mehreren Werten zu verwenden:

String actual = "val1="+val1+"\nval2="+val2;
assertEquals(
    "val1=5\n" +
    "val2=hello"
    , actual
);

Dadurch kann ich alle fehlgeschlagenen Behauptungen auf einmal sehen. Ich verwende mehrere Zeilen, da die meisten IDEs in einem Vergleichsdialog nebeneinander Zeichenfolgenunterschiede anzeigen.

Aaron Digulla
quelle
2

Wenn Sie mehrere Asserts in einer einzelnen Testfunktion haben, sind diese meiner Meinung nach direkt für den Test relevant, den Sie durchführen. Zum Beispiel,

@Test
test_Is_Date_segments_correct {

   // It is okay if you have multiple asserts checking dd, mm, yyyy, hh, mm, ss, etc. 
   // But you would not have any assert statement checking if it is string or number,
   // that is a different test and may be with multiple or single assert statement.
}

Es ist keine schlechte Sache, viele Tests zu machen (auch wenn Sie das Gefühl haben, dass es wahrscheinlich ein Overkill ist). Sie können argumentieren, dass es wichtiger ist, über die wichtigsten und wichtigsten Tests zu verfügen. Wenn Sie also eine Behauptung erstellen, stellen Sie sicher, dass Ihre Behauptungsanweisungen korrekt platziert sind, anstatt sich über mehrere Behauptungen Gedanken zu machen. Wenn Sie mehr als einen benötigen, verwenden Sie mehr als einen.

Hagubear
quelle
1

Das Ziel des Komponententests ist es, Ihnen so viele Informationen wie möglich über die Fehler zu geben, aber auch zu helfen, die grundlegendsten Probleme zuerst genau zu lokalisieren. Wenn Sie logisch wissen, dass eine Zusicherung fehlschlägt, wenn eine andere Zusicherung fehlschlägt oder mit anderen Worten eine Abhängigkeitsbeziehung zwischen dem Test besteht, ist es sinnvoll, diese als mehrere Zusicherungen in einem einzigen Test zu rollen. Dies hat den Vorteil, dass die Testergebnisse nicht mit offensichtlichen Fehlern verunreinigt werden, die hätten beseitigt werden können, wenn wir die erste Behauptung innerhalb eines einzelnen Tests aufgehoben hätten. In dem Fall, in dem diese Beziehung nicht existiert, wäre es natürlich vorzuziehen, diese Behauptungen in einzelne Tests zu unterteilen, da das Auffinden dieser Fehler andernfalls mehrere Iterationen von Testläufen erfordern würde, um alle Probleme zu lösen.

Wenn Sie dann auch die Einheiten / Klassen so entwerfen, dass übermäßig komplexe Tests geschrieben werden müssten, wird dies beim Testen weniger belastet und möglicherweise ein besseres Design gefördert.

U / min jpierson
quelle
1

Ja, es ist in Ordnung, mehrere Behauptungen zu haben, solange ein fehlgeschlagener Test genügend Informationen liefert, um den Fehler diagnostizieren zu können. Dies hängt davon ab, was Sie testen und welche Fehlermodi vorliegen.

Richtige Komponententests sollten aus genau einem Grund fehlschlagen. Aus diesem Grund sollten Sie eine Zusicherung pro Komponententest verwenden.

Ich habe solche Formulierungen nie als hilfreich empfunden (dass eine Klasse einen Grund haben sollte, sich zu ändern, ist ein Beispiel für solch ein nicht hilfreiches Sprichwort). Angenommen, zwei Zeichenfolgen sind gleich, entspricht dies semantisch der Behauptung, dass die Länge der beiden Zeichenfolgen gleich ist und jedes Zeichen am entsprechenden Index gleich ist.

Wir könnten verallgemeinern und sagen, dass jedes System aus mehreren Behauptungen als eine einzige Behauptung umgeschrieben und jede einzelne Behauptung in eine Menge kleinerer Behauptungen zerlegt werden könnte.

Konzentrieren Sie sich also nur auf die Klarheit des Codes und die Klarheit der Testergebnisse und lassen Sie sich von der Anzahl der von Ihnen verwendeten Behauptungen leiten, anstatt umgekehrt.

CurtainDog
quelle
0

Die Antwort ist sehr einfach: Wenn Sie eine Funktion testen, die mehr als ein Attribut desselben Objekts oder sogar zwei verschiedene Objekte ändert, und die Richtigkeit der Funktion von den Ergebnissen all dieser Änderungen abhängt, möchten Sie dies bestätigen dass jede dieser Änderungen ordnungsgemäß durchgeführt wurde!

Ich habe die Idee eines logischen Konzepts, aber die umgekehrte Schlussfolgerung besagt, dass keine Funktion mehr als ein Objekt verändern darf. Aber das ist meiner Erfahrung nach unmöglich umzusetzen.

Nehmen Sie das logische Konzept einer Banküberweisung - das Abheben eines Betrags von einem Bankkonto MUSS in den meisten Fällen das Hinzufügen dieses Betrags zu einem anderen Konto umfassen. Sie wollen diese beiden Dinge NIEMALS trennen, sie bilden eine atomare Einheit. Vielleicht möchten Sie zwei Funktionen ausführen (Geld abheben / hinzufügen) und damit zwei verschiedene Unit-Tests schreiben - zusätzlich. Diese beiden Aktionen müssen jedoch innerhalb einer Transaktion stattfinden, und Sie möchten auch sicherstellen, dass die Transaktion funktioniert. In diesem Fall reicht es einfach nicht aus, den Erfolg der einzelnen Schritte zu überprüfen. Sie müssen beide Bankkonten in Ihrem Test überprüfen.

Möglicherweise gibt es komplexere Beispiele, die Sie nicht in einem Komponententest, sondern in einem Integrations- oder Abnahmetest testen würden. Aber diese Grenzen sind fließend, IMHO! Es ist nicht so einfach zu entscheiden, es ist eine Frage der Umstände und vielleicht der persönlichen Vorlieben. Das Abheben von Geld von einem Konto und das Aufladen auf ein anderes Konto ist immer noch eine sehr einfache Funktion und definitiv ein Kandidat für Unit-Tests.

Cslotty
quelle
-1

Diese Frage steht im Zusammenhang mit dem klassischen Problem des Ausgleichs zwischen Problemen mit Spaghetti und Lasagne-Code.

Wenn Sie mehrere Asserts haben, können Sie leicht das Spaghetti-Problem lösen, bei dem Sie keine Ahnung haben, worum es beim Test geht. Wenn Sie jedoch nur einen Assert pro Test haben, können Sie Ihre Tests gleichermaßen unlesbar machen, wenn Sie mehrere Tests in einer großen Lasagne durchführen, um herauszufinden, welcher Test was unmöglich macht .

Es gibt einige Ausnahmen, aber in diesem Fall ist es die Antwort, das Pendel in der Mitte zu halten.

gsf
quelle
-3

Ich bin überhaupt nicht mit dem "nur aus einem Grund scheitern" einverstanden. Was wichtiger ist, ist, dass die Tests kurz sind und klar imo lesen.

Dies ist jedoch nicht immer möglich, und wenn ein Test kompliziert ist, ist es sinnvoller, einen (langen) beschreibenden Namen und weniger Dinge zu testen.

Johan Larsson
quelle