Heute habe ich ein " JUnit- Grundlagen" -Video angesehen und der Autor hat gesagt, dass Sie beim Testen einer bestimmten Methode in Ihrem Programm keine anderen Ihrer eigenen Methoden verwenden sollten.
Um genauer zu sein, er sprach über das Testen einer Methode zur Datensatzerstellung, die einen Namen und einen Nachnamen für Argumente verwendete und diese verwendete, um Datensätze in einer bestimmten Tabelle zu erstellen. Er behauptete jedoch, dass er beim Testen dieser Methode nicht seine anderen DAO- Methoden verwenden sollte, um die Datenbank abzufragen und das Endergebnis zu überprüfen (um zu überprüfen, ob der Datensatz tatsächlich mit den richtigen Daten erstellt wurde). Er forderte dazu auf, zusätzlichen JDBC- Code zu schreiben , um die Datenbank abzufragen und das Ergebnis zu überprüfen.
Ich glaube, ich verstehe den Geist seiner Behauptung: Sie möchten nicht, dass der Testfall einer Methode von der Richtigkeit der anderen Methoden (in diesem Fall der DAO-Methode) abhängt, und dies wird erreicht, indem Sie (erneut) Ihre eigene Validierung schreiben / unterstützender Code (der spezifischer und fokussierter sein sollte, daher einfacherer Code).
Nichtsdestotrotz protestierten Stimmen in meinem Kopf mit Argumenten wie Code-Vervielfältigung, unnötigem zusätzlichen Aufwand usw. Ich meine, wenn wir die gesamte Testbatterie betreiben und alle unsere öffentlichen Methoden gründlich testen (in diesem Fall auch die DAO-Methode), sollte das nicht sein Ist es nicht in Ordnung, nur einige dieser Methoden zu verwenden, während Sie andere Methoden testen? Wenn einer von ihnen nicht tut, was er tun soll, schlägt sein eigener Testfall fehl, und wir können ihn beheben und die Testbatterie erneut ausführen. Keine Notwendigkeit für Code-Duplizierung (auch wenn der Code-Duplizierung etwas einfacher ist) oder unnötiger Aufwand.
Ich habe ein starkes Gefühl bei der Sache wegen einiger neuer Excel - VBA - Anwendungen I (richtig stückgeprüft dank geschrieben habe Rubberduck für VBA ), wobei diese Empfehlung Anwendung eine Menge zusätzlicher zusätzliche Arbeit bedeuten würde, ohne wahrgenommen Nutzen.
Können Sie uns bitte Ihre Einsichten dazu mitteilen?
quelle
Antworten:
Der Geist seiner Behauptung ist in der Tat richtig. Der Zweck von Komponententests besteht darin, Code zu isolieren und unabhängig von Abhängigkeiten zu testen, damit fehlerhafte Verhaltensweisen schnell erkannt werden können, wo sie auftreten.
Vor diesem Hintergrund ist Unit Testing ein Werkzeug, das Ihren Zwecken dienen soll. Es ist kein Altar, zu dem man beten muss. Manchmal bedeutet dies, Abhängigkeiten zu belassen, weil sie zuverlässig genug funktionieren und Sie sich nicht die Mühe machen wollen, sie zu verspotten. Manchmal bedeutet dies, dass einige Ihrer Komponententests tatsächlich ziemlich eng sind, wenn nicht sogar Integrationstests.
Letztendlich werden Sie nicht darauf eingestuft, wichtig ist das Endprodukt der getesteten Software, aber Sie müssen nur darauf achten, wann Sie die Regeln biegen und entscheiden, wann sich die Kompromisse lohnen.
quelle
Ich denke, das hängt von der Terminologie ab. Für viele ist ein "Komponententest" eine sehr spezifische Sache, und per Definition kann kein Bestehen / Nichtbestehen-Zustand vorliegen, der von einem Code außerhalb der zu testenden Einheit (Methode, Funktion usw.) abhängt . Dies würde die Interaktion mit einer Datenbank einschließen.
Für andere ist der Begriff "Komponententest" viel lockerer und umfasst jede Art von automatisiertem Testen, einschließlich Testcode, der integrierte Teile der Anwendung testet.
Ein Testpurist (wenn ich diesen Begriff verwenden darf) könnte dies einen Integrationstest nennen , der sich von einem Komponententest dadurch unterscheidet, dass der Test von mehr als einer reinen Codeeinheit abhängt .
Ich vermute, dass Sie die lockerere Version des Begriffs "Komponententest" verwenden und sich tatsächlich auf einen "Integrationstest" beziehen.
Wenn Sie diese Definitionen verwenden, sollten Sie sich bei einem "Komponententest" nicht auf Code außerhalb des Prüflings verlassen. Bei einem "Integrationstest" ist es jedoch durchaus sinnvoll, einen Test zu schreiben, der einen bestimmten Code ausübt und dann die Datenbank auf die Bestehenskriterien überprüft.
Ob Sie sich auf Integrationstests oder Unit-Tests oder beides verlassen sollten, ist ein viel größeres Thema.
quelle
Die Antwort ist ja und nein ...
Einzigartige Tests, die isoliert durchgeführt werden, sind ein absolutes Muss, da Sie damit die Grundlagen dafür schaffen können, dass Ihr Low-Level-Code ordnungsgemäß funktioniert. Beim Codieren größerer Bibliotheken finden Sie jedoch auch Codebereiche, die Tests erfordern, die sich über mehrere Einheiten erstrecken.
Diese einheitenübergreifenden Tests sind gut für die Codeabdeckung und beim Testen der End-to-End-Funktionalität geeignet. Sie weisen jedoch einige Nachteile auf, die Sie berücksichtigen sollten:
Am Ende des Tages möchten Sie beides haben. Viele Tests, die Funktionen auf niedriger Ebene erfassen, und einige, die die End-to-End-Funktionalität testen. Konzentrieren Sie sich zunächst auf den Grund, warum Sie Tests schreiben. Sie sollen Ihnen die Gewissheit geben, dass Ihr Code die erwartete Leistung erbringt. Was auch immer Sie tun müssen, um dies zu erreichen, ist in Ordnung.
Die traurige, aber wahre Tatsache ist, dass Sie, wenn Sie automatisierte Tests verwenden, bereits eine Menge Entwickler hinter sich haben. Gute Testpraktiken sind das erste, was ein Entwicklerteam mit schwierigen Fristen konfrontiert. Solange Sie sich also an Ihre Waffen halten und Unit-Tests schreiben, die einen aussagekräftigen Eindruck davon vermitteln, wie Ihr Code ablaufen soll, wäre ich nicht besessen davon, wie "rein" Ihre Tests sind.
quelle
Ein Problem bei der Verwendung anderer Objektmethoden zum Testen einer Bedingung besteht darin, dass Sie Fehler verpassen, die sich gegenseitig aufheben. Noch wichtiger ist, dass Sie den Schmerz einer schwierigen Testimplementierung verpassen und dass Ihnen dieser Schmerz etwas über Ihren zugrunde liegenden Code beibringt. Schmerzhafte Tests bedeuten jetzt später schmerzhafte Wartung.
Verkleinern Sie Ihre Einheiten, teilen Sie Ihre Klasse, refaktorieren Sie sie und gestalten Sie sie neu, bis es einfacher ist, zu testen, ohne den Rest Ihres Objekts erneut zu implementieren. Holen Sie sich Hilfe, wenn Sie der Meinung sind, dass Ihr Code oder Ihre Tests nicht weiter vereinfacht werden können. Denken Sie an einen Kollegen, der anscheinend immer Glück hat und Aufträge erhält, die sich einfach und sauber testen lassen. Bitten Sie ihn oder sie um Hilfe, weil es kein Glück ist.
quelle
Wenn die zu testende Funktionseinheit lautet: "Werden die Daten dauerhaft gespeichert und sind sie abrufbar?", Müsste ich den Komponententest durchführen: "In einer realen Datenbank speichern, alle Objekte zerstören, die Verweise auf die Datenbank enthalten, und dann den Code aufrufen, um die abzurufen." Objekte zurück.
Das Testen, ob ein Datensatz in der Datenbank erstellt wird, scheint eher mit den Implementierungsdetails des Speichermechanismus zu tun zu haben, als die Funktionseinheit zu testen, die für den Rest des Systems verfügbar ist.
Vielleicht möchten Sie den Test verkürzen, um die Leistung mithilfe einer nachgebildeten Datenbank zu verbessern, aber ich habe Auftragnehmer gehabt, die genau das getan haben und am Ende ihres Vertrags mit einem System abgereist sind, das solche Tests bestanden hat, aber tatsächlich nichts in der Datenbank gespeichert hat zwischen Systemneustarts.
Sie können darüber streiten, ob "Einheit" im Komponententest eine "Codeeinheit" oder eine "Funktionseinheit" ist. Diese Funktion wird möglicherweise von vielen Codeeinheiten zusammen erstellt. Ich halte das nicht für eine sinnvolle Unterscheidung - die Fragen, an die ich denken möchte, lauten: "Sagt der Test etwas darüber aus, ob das System geschäftlichen Nutzen bringt?" Und "Ist der Test spröde, wenn sich die Implementierung ändert?" Tests wie der beschriebene sind nützlich, wenn Sie das System testen möchten - Sie haben noch nicht das 'Objekt aus Datenbankdatensatz abrufen' geschrieben, können also nicht die gesamte Funktionalität testen -, aber sie sind spröde gegenüber Implementierungsänderungen, also würde ich Entfernen Sie sie, sobald der vollständige Betrieb überprüft werden kann.
quelle
Der Geist ist richtig.
Idealerweise testen Sie in einem Unit-Test eine Unit (eine einzelne Methode oder eine kleine Klasse).
Im Idealfall würden Sie das gesamte Datenbanksystem auslagern. Dh, Sie würden Ihre Methode in einer gefälschten Umgebung ausführen und nur sicherstellen, dass die richtigen DB-APIs in der richtigen Reihenfolge aufgerufen werden. Sie möchten die Datenbank ausdrücklich nicht testen, wenn Sie eine Ihrer eigenen Methoden testen.
Vorteile gibt es viele. Vor allem werden Tests schnell blind, da Sie sich nicht darum kümmern müssen, eine korrekte DB-Umgebung einzurichten und diese anschließend zurückzusetzen.
Natürlich ist dies ein hohes Ziel in jedem Softwareprojekt, das dies nicht von Anfang an richtig macht. Aber es ist der Geist der Einheitentests .
Beachten Sie, dass es andere Tests wie Funktionstests, Verhaltenstests usw. gibt, die von diesem Ansatz abweichen. Verwechseln Sie "Testen" nicht mit "Komponententests".
quelle
Es gibt mindestens einen sehr wichtigen Grund , den direkten Datenbankzugriff in Ihren Komponententests für Geschäftscode, der mit der Datenbank interagiert, nicht zu verwenden: Wenn Sie Ihre Datenbankimplementierung ändern, müssen Sie alle diese Komponententests neu schreiben. Benennen Sie eine Spalte um. Für Ihren Code müssen Sie nur eine einzelne Zeile in Ihrer Data-Mapper-Definition ändern. Wenn Sie Ihren Data Mapper beim Testen jedoch nicht verwenden, müssen Sie auch jeden einzelnen Komponententest ändern , der auf diese Spalte verweist . Dies könnte zu einem außerordentlichen Arbeitsaufwand führen, insbesondere bei komplexeren Änderungen, die sich weniger gut suchen und ersetzen lassen.
Wenn Sie Ihren Data Mapper als abstrakten Mechanismus verwenden, anstatt direkt mit der Datenbank zu kommunizieren, können Sie die Abhängigkeit von der Datenbank leichter vollständig entfernen , was derzeit möglicherweise nicht relevant ist, aber wenn Sie bis zu Tausenden von Komponententests und allen davon erhalten Wenn Sie auf eine Datenbank zugreifen, sind Sie dankbar, dass Sie diese leicht überarbeiten können, um diese Abhängigkeit zu beseitigen, da Ihre Testsuite von Minuten auf Sekunden sinkt, was einen enormen Vorteil für Ihre Produktivität haben kann.
Ihre Frage zeigt an, dass Sie in die richtige Richtung denken. Wie Sie vermuten, sind Unit-Tests auch Code. Es ist genauso wichtig, dass sie wartbar sind und sich leicht an zukünftige Änderungen anpassen lassen, wie für den Rest Ihrer Codebasis. Bemühen Sie sich, sie lesbar zu halten und Duplikate zu vermeiden, und Sie werden eine viel bessere Testsuite dafür haben. Und eine gute Testsuite wird verwendet. Und eine Test-Suite, die verwendet wird, hilft, Fehler zu finden, während eine, die nicht verwendet wird, nur wertlos ist.
quelle
Die beste Lektion, die ich beim Erlernen von Unit-Tests und Integrationstests gelernt habe, ist, keine Methoden zu testen, sondern Verhaltensweisen zu testen. Mit anderen Worten, was macht dieses Objekt?
Wenn ich das so betrachte, werden eine Methode, die Daten beibehält, und eine andere Methode, die sie zurückliest, testbar. Wenn Sie die Methoden speziell testen, erhalten Sie am Ende einen Test für jede einzelne Methode, z. B .:
Dieses Problem tritt aufgrund der Perspektive der Testmethoden auf. Teste keine Methoden. Verhalten testen. Wie verhält sich die WidgetDao-Klasse? Widgets bleiben erhalten. Ok, wie verifizierst du, dass Widgets bestehen bleiben? Nun, was ist die Definition von Persistenz? Es bedeutet, wenn Sie es schreiben, können Sie es wieder zurücklesen. Lesen + Schreiben zusammen wird also zu einem Test und meiner Meinung nach zu einem aussagekräftigeren Test.
Das ist ein logischer, zusammenhängender, zuverlässiger und meiner Meinung nach aussagekräftiger Test.
Die anderen Antworten konzentrieren sich darauf, wie wichtig Isolation ist, wie pragmatisch und realistisch eine Debatte ist und ob ein Komponententest eine Datenbank abfragen kann oder nicht. Diese beantworten die Frage allerdings nicht wirklich.
Um zu testen, ob etwas gespeichert werden kann, müssen Sie es speichern und dann zurücklesen. Sie können nicht testen, ob etwas gespeichert ist, wenn Sie es nicht lesen dürfen. Testen Sie das Speichern von Daten nicht getrennt vom Abrufen von Daten. Sie werden mit Tests enden, die Ihnen nichts sagen.
quelle
Ein Beispiel für einen Fehlerfall, den Sie gerne abfangen würden, besteht darin, dass das zu testende Objekt eine Caching-Ebene verwendet, die Daten jedoch nicht wie erforderlich beibehalten. Wenn Sie das Objekt dann abfragen, wird angezeigt: "Ja, ich habe den neuen Namen und die neue Adresse". Sie möchten jedoch, dass der Test fehlschlägt, da er nicht genau das getan hat, was er eigentlich sollte.
Alternativ (und mit Blick auf die Verletzung der Einzelverantwortung) wird angenommen, dass eine UTF-8-codierte Version der Zeichenfolge in einem byteorientierten Feld beibehalten werden muss, Shift JIS jedoch beibehalten wird. Eine andere Komponente wird die Datenbank lesen und erwartet, dass UTF-8 angezeigt wird, daher die Anforderung. Bei der Rundreise durch dieses Objekt werden dann der richtige Name und die richtige Adresse gemeldet, da sie von Shift JIS zurückkonvertiert werden. Der Fehler wird jedoch von Ihrem Test nicht erkannt. Es wird hoffentlich durch einen späteren Integrationstest erkannt, aber der springende Punkt bei Unit-Tests ist, Probleme frühzeitig zu erkennen und genau zu wissen, welche Komponente dafür verantwortlich ist.
Das können Sie nicht annehmen, denn wenn Sie nicht aufpassen, schreiben Sie eine Reihe von Tests, die sich gegenseitig bedingen. Das "spart es?" test ruft die Speichermethode auf, die getestet wird, und anschließend die Lademethode, um das Speichern zu bestätigen. Das "lädt es?" test ruft die Speichermethode auf, um das Testgerät einzurichten, und dann die Lademethode, die es testet, um das Ergebnis zu überprüfen. Beide Tests stützen sich auf die Richtigkeit der Methode, die sie nicht testen, dh, keiner von beiden testet tatsächlich die Richtigkeit der Methode, die er testet.
Der Hinweis, dass es hier ein Problem gibt, ist, dass zwei Tests, die angeblich verschiedene Einheiten testen, tatsächlich dasselbe tun . Beide rufen einen Setter gefolgt von einem Getter auf und prüfen dann, ob das Ergebnis der ursprüngliche Wert ist. Sie wollten jedoch testen, ob der Setter die Daten beibehält, und nicht, ob das Setter / Getter-Paar zusammenarbeitet. Damit Sie wissen, dass etwas nicht stimmt, müssen Sie nur herausfinden, was passiert, und die Tests korrigieren.
Wenn Ihr Code für Komponententests gut konzipiert ist, können Sie auf mindestens zwei Arten testen, ob die Daten von der getesteten Methode wirklich korrekt beibehalten wurden:
Verspotten Sie die Datenbankschnittstelle, und lassen Sie Ihre Verspottung die Tatsache aufzeichnen, dass die richtigen Funktionen mit den erwarteten Werten aufgerufen wurden. Dies testet die Methode, was es soll, und ist der klassische Unit-Test.
Übergeben Sie ihm eine aktuelle Datenbank mit genau der gleichen Absicht , um aufzuzeichnen, ob die Daten korrekt gespeichert wurden oder nicht. Aber anstatt eine verspottete Funktion zu haben, die nur sagt "Ja, ich habe die richtigen Daten", liest Ihr Test direkt aus der Datenbank zurück und bestätigt, dass er korrekt ist. Dies ist möglicherweise nicht der reinste Test, da eine gesamte Datenbank-Engine eine ziemlich große Sache ist, um einen verherrlichten Schein zu schreiben, wobei ich mit größerer Wahrscheinlichkeit eine Feinheit übersehen kann, die einen Test bestanden hat, obwohl etwas nicht stimmt (also zum Beispiel ich) sollte nicht dieselbe Datenbankverbindung zum Lesen verwenden, die zum Schreiben verwendet wurde, da möglicherweise eine nicht festgeschriebene Transaktion angezeigt wird. Aber es testet das Richtige, und zumindest weißt du, dass es genau ist implementiert die gesamte Datenbankschnittstelle, ohne dass ein Mock-Code geschrieben werden muss!
Es ist also nur ein Detail der Testimplementierung, ob ich die Daten von JDBC aus der Testdatenbank lese oder ob ich die Datenbank verspotte. In beiden Fällen kann ich das Gerät besser testen, indem ich es isoliere, als wenn ich zulasse, dass es mit anderen falschen Methoden in derselben Klasse konspiriert, um auch dann richtig auszusehen, wenn etwas nicht stimmt. Aus diesem Grund möchte ich auf bequeme Weise überprüfen, ob die korrekten Daten erhalten geblieben sind, und nicht der Komponente vertrauen, deren Methode ich teste.
Wenn Ihr Code nicht für Komponententests geeignet ist, haben Sie möglicherweise keine andere Wahl, da das Objekt, dessen Methode Sie testen möchten, die Datenbank möglicherweise nicht als injizierte Abhängigkeit akzeptiert. In diesem Fall ändert sich die Diskussion über den besten Weg, das zu testende Gerät zu isolieren, in eine Diskussion darüber, wie nahe es möglich ist, das zu testende Gerät zu isolieren. Die Schlussfolgerung ist jedoch die gleiche. Wenn Sie Verschwörungen zwischen fehlerhaften Einheiten vermeiden können, können Sie, vorbehaltlich der verfügbaren Zeit und aller anderen Faktoren, die Sie für effektiver halten, Fehler im Code finden.
quelle
So mache ich das gerne. Dies ist nur eine persönliche Entscheidung, da sich dies nicht auf das Ergebnis des Produkts auswirkt, sondern nur auf die Art und Weise, wie es hergestellt wird.
Dies hängt von den Möglichkeiten ab, in der Lage zu sein, Scheinwerte in Ihre Datenbank aufzunehmen, ohne dass Sie die Anwendung testen.
Angenommen, Sie haben zwei Tests: A - Lesen der Datenbank B - Einfügen in die Datenbank (abhängig von A)
Wenn A besteht, sind Sie sicher, dass das Ergebnis von B von dem in B getesteten Teil und nicht von den Abhängigkeiten abhängt. Wenn A fehlschlägt, können Sie ein falsches Negativ für B haben. Der Fehler kann in B oder in A liegen. Sie können nicht sicher sein, bis A einen Erfolg zurückgibt.
Ich würde nicht versuchen, einige Abfragen in einem Unit-Test zu schreiben, da Sie die DB-Struktur hinter der Anwendung nicht kennen sollten (oder sie sich ändern könnte). Dies bedeutet, dass Ihr Testfall an Ihrem Code selbst scheitern könnte. Es sei denn, Sie schreiben einen Test für den Test, aber wer wird den Test für den Test testen ...
quelle
Am Ende kommt es darauf an, was Sie testen möchten.
Manchmal reicht es aus, die bereitgestellte Schnittstelle Ihrer Klasse zu testen (z. B. Datensatz wird erstellt und gespeichert und kann später, möglicherweise nach einem "Neustart", abgerufen werden). In diesem Fall ist es in Ordnung (und normalerweise eine gute Idee), die bereitgestellten Methoden zum Testen zu verwenden. Auf diese Weise können die Tests wiederverwendet werden, wenn Sie beispielsweise den Speichermechanismus ändern möchten (andere Datenbank, In-Memory-Datenbank zum Testen, ...).
Auf der anderen Seite müssen Sie in einigen Fällen tatsächlich überprüfen, wie die Daten erhalten bleiben (möglicherweise hängt ein anderer Prozess davon ab, ob die Daten in dieser Datenbank das richtige Format aufweisen?). Jetzt können Sie sich nicht nur darauf verlassen, dass der richtige Datensatz von der Klasse zurückgegeben wird, sondern müssen auch sicherstellen, dass er korrekt in der Datenbank gespeichert ist. Der direkteste Weg dazu ist das Abfragen der Datenbank.
quelle