Unit-Test mehrerer Bedingungen in einer IF-Anweisung


25

Ich habe ein Stück Code, das ungefähr so ​​aussieht:

function bool PassesBusinessRules()
{
    bool meetsBusinessRules = false;

    if (PassesBusinessRule1 
         && PassesBusinessRule2
         && PassesBusinessRule3)
    {
         meetsBusinessRules= true;
    }

    return meetsBusinessRules;
}

Ich glaube, es sollte vier Unit-Tests für diese bestimmte Funktion geben. Drei, um jede der Bedingungen in der if-Anweisung zu testen und sicherzustellen, dass sie false zurückgibt. Und ein weiterer Test, der sicherstellt, dass die Funktion true zurückgibt.

Frage: Sollte es stattdessen zehn Unit-Tests geben? Neun, die jeden der möglichen Fehlerpfade überprüft. IE:

  • Falsch Falsch Falsch
  • Falsch Falsch Richtig
  • Falsch Richtig Falsch

Und so weiter für jede mögliche Kombination.

Ich denke, das ist übertrieben, aber einige der anderen Mitglieder in meinem Team tun es nicht. Wenn BusinessRule1 ausfällt, sollte es immer false zurückgeben. Es spielt keine Rolle, ob es zuerst oder zuletzt überprüft wurde.


Verwendet der Compiler eine gierige Auswertung für den Operator &&?
Suszterpatt

12
Wenn Sie 10 Unit-Tests geschrieben haben, würden Sie den Operator && testen, nicht Ihre Methoden.
Mert Akcakaya

2
Würden Sie nicht nur acht Tests durchführen, wenn Sie alle möglichen Kombinationen getestet hätten? Drei boolesche Parameter, die entweder aktiviert oder deaktiviert sind.
Kris Harper

3
@Mert: Nur wenn Sie garantieren können, dass das && immer da ist.
Misko

Hickey: Wenn wir damit verbringen, Tests zu schreiben, dann verbringen wir keine Zeit damit, etwas anderes zu tun. Jeder von uns muss beurteilen, wie er seine Zeit am besten verbringen kann, um seine Ergebnisse sowohl in quantitativer als auch in qualitativer Hinsicht zu maximieren. Wenn die Leute denken, dass fünfzig Prozent ihrer Zeit damit verbracht werden, Tests zu schreiben, maximieren sie ihre Ergebnisse - okay für sie. Ich bin mir sicher, dass das für mich nicht zutrifft. Ich würde diese Zeit lieber damit verbringen, über mein Problem nachzudenken. Ich bin mir sicher, dass dies für mich zu besseren Lösungen mit weniger Fehlern führt als jede andere Nutzung meiner Zeit. Ein schlechtes Design mit einer vollständigen Testsuite ist immer noch ein schlechtes Design.
Job

Antworten:


28

Formal haben diese Arten der Berichterstattung Namen.

Erstens gibt es eine Prädikatabdeckung : Sie möchten einen Testfall, der die if-Anweisung wahr macht, und einen, der sie falsch macht. Die Einhaltung dieser Abdeckung ist wahrscheinlich eine Grundvoraussetzung für eine gute Testsuite.

Dann gibt es Bedingungsabdeckung : Hier möchten Sie testen, dass jede Unterbedingung im if den Wert true und false hat. Dies führt offensichtlich zu mehr Tests, behebt jedoch in der Regel mehr Fehler. Daher ist es oft eine gute Idee, diese in Ihre Testsuite aufzunehmen, wenn Sie Zeit haben.

Das am weitesten fortgeschrittene Erfassungskriterium wird normalerweise als kombinatorische Bedingungserfassung bezeichnet : Hier besteht das Ziel darin, einen Testfall zu erstellen, der alle möglichen Kombinationen von Booleschen Werten in Ihrem Test durchläuft .

Ist dies besser als einfaches Prädikat oder Bedingungsüberdeckung? In Bezug auf die Berichterstattung natürlich. Aber es ist nicht kostenlos. Bei der Testwartung sind die Kosten sehr hoch. Aus diesem Grund kümmern sich die meisten Menschen nicht um eine vollständige kombinatorische Abdeckung. Normalerweise ist das Testen aller Zweige (oder aller Bedingungen) gut genug, um Fehler zu erkennen. Das Hinzufügen der zusätzlichen Tests für kombinatorische Tests führt normalerweise nicht zu mehr Fehlern, erfordert jedoch viel Aufwand beim Erstellen und Verwalten. Aufgrund des zusätzlichen Aufwands lohnt sich diese Auszahlung normalerweise nicht, daher würde ich sie nicht empfehlen.

Ein Teil dieser Entscheidung sollte davon abhängen, wie riskant Sie diesen Code finden. Wenn es viel Raum zum Scheitern gibt, lohnt es sich zu testen. Wenn es etwas stabil ist und sich nicht viel ändert, sollten Sie Ihre Testbemühungen auf andere Bereiche konzentrieren.


2
Wenn die Booleschen Werte aus externen Quellen übergeben werden (was bedeutet, dass sie nicht immer validiert sind), ist häufig eine kombinatorische bedingte Abdeckung erforderlich. Machen Sie zuerst eine Tabelle mit den Kombinationen. Entscheiden Sie dann für jeden Eintrag, ob dieser Eintrag einen sinnvollen Anwendungsfall darstellt. Wenn nicht, sollte irgendwo Code vorhanden sein (entweder Software-Zusicherungen oder Validierungsklausel), um die Ausführung dieser Kombination zu verhindern. Es ist wichtig, nicht alle Parameter in einem einzigen kombinatorischen Test zusammenzufassen: Versuchen Sie, Parameter in Gruppen zu unterteilen, die miteinander interagieren, dh denselben booleschen Ausdruck verwenden.
Rwong

Wie sicher sind Sie sich der kühnen Begriffe? Ihre Antwort scheint das einzige Vorkommen von "Combinatorial Condition Coverage" zu sein, und einige Ressourcen sagen, dass "Predicate Coverage" und "Conditional Coverage" dasselbe sind.
Stijn

8

Letztendlich hängt es von Ihnen (r Team), dem Code und der spezifischen Projektumgebung ab. Es gibt keine universelle Regel. Sie (Ihr Team) sollten so viele Tests schreiben, wie Sie benötigen, um sich sicher zu sein, dass der Code tatsächlich korrekt ist . Wenn Ihre Teamkollegen also nicht von 4 Tests überzeugt sind, brauchen Sie vielleicht mehr.

Die OTOH-Zeit zum Schreiben von Komponententests ist normalerweise eine knappe Ressource. So bemühen uns , die beste Art und Weise zu finden , die begrenzte Zeit zu verbringen Sie haben . Wenn Sie beispielsweise eine andere wichtige Methode mit 0% Abdeckung haben, ist es möglicherweise besser, ein paar Komponententests zu schreiben, um diese abzudecken, als zusätzliche Tests für diese Methode hinzuzufügen. Natürlich kommt es auch darauf an, wie fragil die Umsetzung der einzelnen ist. Wenn Sie in absehbarer Zeit viele Änderungen an dieser speziellen Methode vornehmen, kann dies eine zusätzliche Abdeckung durch Komponententests rechtfertigen. Möglicherweise befinden Sie sich innerhalb des Programms auf einem kritischen Pfad. Dies sind alles Faktoren, die nur Sie (Ihr Team) einschätzen können.

Ich persönlich würde normalerweise mit den 4 Tests, die Sie skizzieren, glücklich sein, das heißt:

  • wahr falsch falsch
  • falsch wahr falsch
  • falsch falsch wahr
  • wahr wahr wahr

plus vielleicht eins:

  • wahr wahr falsch

um sicherzustellen, dass der einzige Weg, einen Rückgabewert von zu erhalten, darin truebesteht, alle drei Geschäftsregeln zu erfüllen. Aber wenn Ihre Teamkollegen darauf bestehen, dass kombinatorische Pfade abgedeckt werden, ist es möglicherweise billiger, diese zusätzlichen Tests hinzuzufügen, als das Argument viel länger fortzusetzen :-)


3

Wenn Sie sicher sein möchten, benötigen Sie acht Einheitentests unter Verwendung der Bedingungen, die durch eine Wahrheitstabelle mit drei Variablen dargestellt werden ( http://teach.valdosta.edu/plmoch/MATH4161/Spring%202004/and_or_if_files/image006.gif ).

Sie können niemals sicher sein, dass die Geschäftslogik immer vorschreibt, dass die Überprüfungen in dieser Reihenfolge durchgeführt werden, und Sie möchten, dass der Test so wenig wie möglich über die tatsächliche Implementierung weiß.


2
Unit-Tests sind White-Box-Tests.
Péter Török

Nun, Ordnung sollte keine Rolle spielen, && ist kommunitiv, oder sollte es zumindest sein
Zachary K

2

Ja, in einer idealen Welt sollte es die vollständige Kombination geben.

Wenn Sie den Komponententest durchführen, sollten Sie wirklich versuchen, zu ignorieren, wie die Methode funktioniert. Geben Sie einfach die 3 Eingänge ein und überprüfen Sie, ob der Ausgang korrekt ist.


1
Unit-Tests sind White-Box-Tests. Und wir leben nicht in einer idealen Welt.
Péter Török,

@ PéterTörök - Wir leben zwar nicht in einer idealen Welt, aber StackExchange ist in der anderen Hinsicht nicht mit Ihnen einverstanden. Speziell für TDD werden die Tests nach den Spezifikationen geschrieben, nicht nach der Implementierung. Ich persönlich nehme "Spezifikation", um alle Eingaben (einschließlich Mitgliedsvariablen) und alle Ausgaben (einschließlich Nebenwirkungen) einzuschließen.
Telastyn

1
Es handelt sich nur um einen bestimmten Thread in StackOverflow, der sich mit einem bestimmten Fall befasst und nicht übermäßig verallgemeinert werden sollte. Zumal es in diesem aktuellen Beitrag offensichtlich um das Testen von Code geht, der bereits geschrieben wurde.
Péter Török

1

Staat ist böse. Die folgende Funktion benötigt keinen Komponententest, da sie keine Nebenwirkungen hat und gut verstanden ist, was sie tut und was nicht. Warum es testen? Traust du deinem eigenen Gehirn nicht ??? Statische Funktionen sind großartig!

static function bool Foo(bool a, bool b, bool c)
{
    return a && b && c;
}

2
Nein, ich vertraue meinem eigenen Gehirn nicht - ich habe die harte Tour gelernt, immer wieder zu überprüfen, was ich tue :-) Also würde ich immer noch Unit-Tests brauchen, um sicherzustellen, dass ich zB nichts falsch geschrieben habe und niemand geht den Code in der Zukunft zu brechen. Und mehr Unit - Tests der Anrufer Methode , um zu überprüfen , die den Zustand von dargestellt berechnet a, bund c. Sie können die Geschäftslogik beliebig verschieben, am Ende müssen Sie sie noch irgendwo testen.
Péter Török

@ Péter Török, du kannst auch in deinen Tests Tippfehler machen und so zu Fehlalarmen führen. Wo hörst du auf? Schreiben Sie Komponententests für Ihre Komponententests? Ich vertraue meinem Gehirn auch nicht zu 100%, aber am Ende des Tages schreibe ich Code, um meinen Lebensunterhalt zu verdienen. Es ist möglich, einen Fehler in dieser Funktion zu haben, aber es ist wichtig, Code so zu schreiben, dass ein Fehler leicht zur Quelle zurückverfolgt werden kann. Sobald Sie das Problem isoliert und eine Fehlerbehebung vorgenommen haben, sind Sie besser dran . Gut geschriebener Code kann sich auf Integrationstests verlassen. Meistens infoq.com/presentations/Simple-Made-Easy
Job

2
In der Tat können die Tests auch fehlerhaft sein. (TDD behebt dies, indem die Tests zuerst fehlschlagen.) Wenn Sie jedoch zweimal dieselbe Art von Fehler machen (und ihn übersehen), ist die Wahrscheinlichkeit wesentlich geringer. Im Allgemeinen können weder Umfang noch Art der Tests beweisen, dass die Software fehlerfrei ist. Reduzieren Sie lediglich die Wahrscheinlichkeit von Fehlern auf ein akzeptables Maß. Und was die Verfolgung von Fehlern zur Quelle angeht, kann IMO Unit-Tests nicht übertreffen - schnelle Rückmeldung :-)
Péter Török

"Die folgende Funktion benötigt keinen Komponententest" Ich denke, Sie sind hier sarkastisch, aber es ist nicht klar. Vertraue ich meinem eigenen Gehirn? NEIN! Traue ich dem Gehirn des nächsten Mannes, der den Code berührt? NOCH MEHR NO! Vertraue ich darauf, dass alle Annahmen, die hinter dem Code stehen, in einem Jahr wahr sein werden? ... Sie bekommen meine Drift. Statische Funktionen töten auch OO ... Wenn Sie FP ausführen möchten, verwenden Sie eine FP-Sprache.
Rob

1

Ich weiß, dass diese Frage ziemlich alt ist. Aber ich möchte dem Problem eine andere Perspektive geben.

Erstens sollten Ihre Komponententests zwei Ziele haben:

  1. Erstellen Sie eine Dokumentation für Sie und Ihre Teamkollegen, damit Sie nach einer bestimmten Zeit den Komponententest lesen und sicherstellen können, dass Sie what's the class' intentionund verstehenhow the class is doing its work
  2. Während der Entwicklung stellt der Komponententest sicher, dass der Code, den wir aufschreiben, seine Arbeit so ausführt, wie wir es uns vorgestellt haben.

Um das Problem zusammenzufassen, wollen wir testen complex if statement, dass es für das gegebene Beispiel 2 ^ 3 Möglichkeiten gibt, das ist eine wichtige Menge von Tests, die wir schreiben können.

  • Sie können sich an diese Tatsache anpassen und 8 Tests aufschreiben oder parametrisierte Tests verwenden
  • Sie können auch den anderen Antworten folgen und sich daran erinnern, dass die Tests mit der Absicht klar sein sollten, auf diese Weise werden wir nicht mit zu vielen Details herumspielen, die in naher Zukunft das Verständnis erschweren könnten what is doing the code

Wenn Sie jedoch der Meinung sind, dass Ihre Tests noch komplexer sind als die Implementierung, sollten Sie die Implementierung (je nach Fall mehr oder weniger) neu gestalten und nicht den Test selbst.

Für die komplexen if-Anweisungen könnten Sie beispielsweise über das Verantwortungsmuster der Kette nachdenken und jeden Handler folgendermaßen implementieren:

If some simple business rule apply, derive to the next handler

Wie einfach wäre es, anstelle einer komplexen Regel verschiedene einfache Regeln zu testen?

Ich hoffe es hilft,


0

Dies ist einer der Fälle, in denen etwas wie Quickcheck ( http://en.wikipedia.org/wiki/QuickCheck ) Ihr Freund sein wird. Anstatt alle N Fälle von Hand auszuschreiben, muss der Computer alle (oder zumindest eine große Anzahl) möglichen Testfälle generieren und validieren, dass alle ein vernünftiges Ergebnis liefern.

Wir programmieren Computer, um Ihren Lebensunterhalt hier zu verdienen. Warum programmieren wir nicht den Computer, um Ihre Testfälle für Sie zu generieren?


0

Sie können die Bedingungen in Schutzbedingungen umgestalten:

if (! PassesBusinessRule1) {
    return false;
}

if (! PassesBusinessRule2) {
    return false;
}

if (! PassesBusinessRule3) {
    return false;
}

Ich denke nicht, dass dies die Anzahl der Fälle verringert, aber meiner Erfahrung nach ist es einfacher, sie auf diese Weise auszubrechen.

(Beachten Sie, dass ich ein großer Fan von "Single Point of Exit" bin, aber ich mache eine Ausnahme für Guard-Bedingungen. Es gibt jedoch auch andere Möglichkeiten, den Code zu strukturieren, sodass Sie keine separaten Rückgaben haben.)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.