Sind Asserts oder Unit-Tests wichtiger?


51

Sowohl Asserts als auch Unit-Tests dienen als Dokumentation für eine Codebasis und als Mittel zum Auffinden von Fehlern. Die Hauptunterschiede bestehen darin, dass Asserts als Plausibilitätsprüfungen fungieren und reale Eingaben erkennen, wohingegen Komponententests für bestimmte simulierte Eingaben ausgeführt werden und Tests mit einer einzigen genau definierten "richtigen Antwort" sind. Was sind die relativen Vorteile der Verwendung von Asserts im Vergleich zu Unit-Tests als Hauptmittel zur Überprüfung der Richtigkeit? Welches sollte Ihrer Meinung nach stärker betont werden?


4
Ich mag diese Idee sehr ... so sehr, dass ich das Thema zu einer Aufgabe für meinen Software-Test- und Qualitätssicherungskurs mache. :-)
Macneil

Eine andere Frage ist: Sollten Sie Ihre Behauptungen einem Komponententest unterziehen? ;)
Mojuba

Antworten:


43

Asserts sind nützlich, um Sie über den internen Status des Programms zu informieren . Zum Beispiel, dass Ihre Datenstrukturen einen gültigen Status haben, zum Beispiel, dass eine TimeDatenstruktur nicht den Wert von enthält 25:61:61. Die von Asserts überprüften Bedingungen sind:

  • Voraussetzungen, die sicherstellen, dass der Anrufer seinen Vertrag einhält,

  • Nachbedingungen, die sicherstellen, dass der Angerufene seinen Vertrag einhält, und

  • Invarianten, die sicherstellen, dass die Datenstruktur nach der Rückkehr der Funktion immer eine Eigenschaft enthält. Eine Invariante ist eine Bedingung, die eine Vorbedingung und eine Nachbedingung ist.

Unit-Tests sind nützlich, um Sie über das externe Verhalten des Moduls zu informieren . Sie Stackkann einen konsistenten Zustand, nachdem die push()Methode aufgerufen wird, aber wenn die Größe des Stapels nicht durch drei nicht erhöht , nachdem es dreimal aufgerufen wird, dann ist das ein Fehler ist. (Zum Beispiel der triviale Fall, dass die falsche push()Implementierung nur die Asserts und Exits überprüft.)

Genau genommen besteht der Hauptunterschied zwischen Zusicherungen und Komponententests darin, dass Komponententests Testdaten enthalten (Werte, mit denen das Programm ausgeführt werden kann), während Zusicherungen dies nicht tun. Das heißt, Sie können Ihre Komponententests automatisch ausführen, während Sie dies für Behauptungen nicht sagen können. Für diese Diskussion habe ich angenommen, dass Sie über das Ausführen des Programms im Kontext von Funktionstests höherer Ordnung sprechen (die das gesamte Programm ausführen und keine Module wie Unit-Tests ansteuern). Wenn Sie nicht über automatisierte Funktionstests als Mittel sprechen, um "echte Eingaben zu sehen", dann liegt der Wert eindeutig in der Automatisierung, und somit würden die Komponententests gewinnen. Wenn Sie im Rahmen von (automatisierten) Funktionstests darüber sprechen, siehe unten.

Es kann zu Überschneidungen bei den getesteten Objekten kommen. Beispielsweise kann Stackdie Nachbedingung von tatsächlich behaupten, dass sich die Stapelgröße um eins erhöht. Was in dieser Behauptung ausgeführt werden kann, ist jedoch begrenzt: Soll auch überprüft werden, ob das oberste Element das ist, was gerade hinzugefügt wurde?

Ziel ist es für beide, die Qualität zu steigern. Bei Unit-Tests geht es darum, Fehler zu finden. Bei Assertions besteht das Ziel darin, das Debuggen zu vereinfachen, indem ungültige Programmzustände sofort nach ihrem Auftreten beobachtet werden.

Beachten Sie, dass keine der Techniken die Richtigkeit überprüft. In der Tat, wenn Sie Unit-Tests durchführen, um sicherzustellen, dass das Programm korrekt ist, werden Sie wahrscheinlich einen uninteressanten Test finden, von dem Sie wissen, dass er funktionieren wird. Es ist ein psychologischer Effekt: Sie werden alles tun, um Ihr Ziel zu erreichen. Wenn es Ihr Ziel ist, Fehler zu finden, werden Ihre Aktivitäten dies widerspiegeln.

Beide sind wichtig und haben ihre eigenen Zwecke.

[Als letzte Anmerkung zu Behauptungen: Um den größtmöglichen Nutzen zu erzielen, müssen Sie sie an allen kritischen Punkten in Ihrem Programm verwenden und nicht an einigen Schlüsselfunktionen. Andernfalls ist die ursprüngliche Ursache des Problems möglicherweise maskiert und ohne stundenlanges Debuggen schwer zu erkennen.]


7

Denken Sie bei der Beschreibung von Behauptungen daran, dass diese mit einem Tastendruck ausgeschaltet werden können.

Beispiel einer sehr schlechten Behauptung:

char *c = malloc(1024);
assert(c != NULL);

Warum ist das so schlimm? Weil keine Fehlerprüfung durchgeführt wird, wenn diese Zusicherung aufgrund der Definition von NDEBUG übersprungen wird.

Ein Unit-Test würde (wahrscheinlich) nur am obigen Code scheitern. Sicher, es hat seine Arbeit getan, indem es Ihnen sagte, dass etwas schief gelaufen ist, oder? Wie wahrscheinlich ist es malloc(), dass der Test fehlschlägt?

Behauptungen dienen zu Debugging-Zwecken, wenn ein Programmierer implizieren muss, dass kein "normales" Ereignis die Behauptung auslösen würde. malloc()ein Scheitern ist in der Tat ein normales Ereignis und sollte daher niemals behauptet werden.

Es gibt viele andere Fälle, in denen Behauptungen verwendet werden, anstatt mit Dingen, die schief gehen könnten, angemessen umzugehen. Dies ist der Grund, warum Behauptungen einen schlechten Ruf erhalten und warum Sprachen wie Go sie nicht enthalten.

Unit-Tests sollen Ihnen mitteilen, wann etwas, das Sie geändert haben, etwas anderes kaputt gemacht hat. Sie wurden entwickelt, um Sie (in den meisten Fällen) davon abzuhalten, jedes Mal, wenn Sie ein Build erstellen, alle Funktionen des Programms zu durchlaufen (Tester sind jedoch für Releases wichtig).

Es gibt wirklich keine eindeutige Korrelation zwischen den beiden, außer dass beide Ihnen mitteilen, dass etwas schief gelaufen ist. Stellen Sie sich eine Zusicherung als Haltepunkt in etwas vor, an dem Sie gerade arbeiten, ohne einen Debugger verwenden zu müssen. Stellen Sie sich einen Komponententest vor, der Ihnen sagt, ob Sie etwas kaputt gemacht haben, an dem Sie nicht arbeiten.


3
Deshalb sind Behauptungen für immer wahre Aussagen, keine Fehlertests.
Dominique McDonnell

@DominicMcDonnell Nun, "sollte wahr sein" Aussagen. Ich behaupte manchmal, um Compiler-Macken herumzukommen, wie zum Beispiel bestimmte Versionen von gcc, in denen ein Buggy abs () eingebaut ist. Wichtig ist, dass Produktions-Builds sowieso ausgeschaltet sind .
Tim Post

Und hier, denke ich, ist Production Code der Ort, an dem die Asserts am dringendsten benötigt werden , da Sie in der Produktion Eingaben erhalten, die Sie nicht für möglich gehalten haben. Die Produktion ist der Ort, an dem die härtesten Bugs verschwinden.
Frank Shearar

@ Frank Shearar, sehr wahr. Sie sollten beim frühesten erkannten Fehlerzustand in der Produktion immer noch hart ausfallen. Sicher, die Benutzer werden sich beschweren, aber nur so können Sie sicherstellen, dass die Fehler behoben werden. Und es ist um einiges besser, ein bla war 0 zu bekommen, als eine Speicherausnahme, um ein paar Funktionsaufrufe später null zu dereferenzieren.
Dominique McDonnell

Warum ist es nicht besser, typische, aber in den Augen des Benutzers häufig ungewöhnliche Probleme zu behandeln, als zu behaupten, dass sie niemals auftreten sollten? Ich kann diese Weisheit nicht erkennen. Stellen Sie sicher, dass ein Primgenerator eine Primzahl zurückgibt, was einige zusätzliche Arbeit erfordert, die in Debugbuilds erwartet wird. Etwas zu behaupten, auf das eine Sprache von Haus aus testen kann, ist einfach dumm. Diese Tests nicht in einen anderen Schalter zu packen, der einige Monate nach einer Veröffentlichung ausgeschaltet werden kann, noch dümmer.
Tim Post

5

Beide Tools tragen zur Verbesserung der Gesamtqualität des von Ihnen erstellten Systems bei. Viel hängt davon ab, welche Sprache Sie verwenden, welche Art von Anwendung Sie erstellen und wo Sie Ihre Zeit am besten verbringen. Ganz zu schweigen davon, dass Sie ein paar Denkanstöße haben.

Wenn Sie eine Sprache ohne assertSchlüsselwort verwenden, können Sie zunächst keine Zusicherungen verwenden (zumindest nicht so, wie wir hier sprechen). Java hatte lange Zeit kein assertSchlüsselwort und einige Sprachen immer noch nicht. Unit Testing wird dann um einiges wichtiger. In einigen Sprachen werden die Assertions nur ausgeführt, wenn ein Flag gesetzt ist (hier wiederum mit Java). Wenn der Schutz nicht immer vorhanden ist, ist dies keine sehr nützliche Funktion.

Es gibt eine Denkschule, die besagt, dass Sie, wenn Sie etwas "behaupten", genauso gut einen if/ throwbedeutungsvollen Ausnahmeblock schreiben könnten . Dieser Denkprozess kommt von vielen Aussagen, die am Anfang einer Methode stehen, um sicherzustellen, dass alle Werte in Grenzen sind. Das Testen Ihrer Vorbedingungen ist ein sehr wichtiger Teil einer erwarteten Nachbedingung.

Unit-Tests sind zusätzlicher Code, der geschrieben und gepflegt werden muss. Für viele ist dies ein Nachteil. Mit der aktuellen Anzahl von Unit-Test-Frameworks können Sie jedoch eine größere Anzahl von Testbedingungen mit vergleichsweise wenig Code generieren. Parametrisierte Tests und "Theorien" führen denselben Test mit einer großen Anzahl von Datenproben durch, die einige schwer auffindbare Fehler aufdecken können.

Ich persönlich habe festgestellt, dass ich mit Unit-Tests mehr Laufleistung erhalte als mit Sprinkling-Tests. Dies liegt jedoch an den Plattformen, auf denen ich die meiste Zeit arbeite (Java / C #). Andere Sprachen bieten eine zuverlässigere Unterstützung für Assertions und sogar "Design by Contract" (siehe unten), um noch mehr Garantien zu bieten. Wenn ich mit einer dieser Sprachen arbeite, verwende ich möglicherweise mehr DBC als Unit-Tests.

http://en.wikipedia.org/wiki/Design_by_contract#Languages_with_native_support

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.