Spioniert erprobtes Unterrichtspraktikum aus?

12

Ich arbeite an einem Projekt, in dem klasseninterne Aufrufe üblich sind, die Ergebnisse jedoch oft einfache Werte sind. Beispiel ( kein echter Code ):

public boolean findError(Set<Thing1> set1, Set<Thing2> set2) {
  if (!checkFirstCondition(set1, set2)) {
    return false;
  }
  if (!checkSecondCondition(set1, set2)) {
    return false;
  }
  return true;
}

Unit-Tests für diese Art von Code zu schreiben ist wirklich schwierig, da ich nur das Konditionssystem testen möchte und nicht die Implementierung der tatsächlichen Bedingungen. (Ich mache das in getrennten Tests.) In der Tat wäre es besser, wenn ich Funktionen bestanden hätte, die die Bedingungen implementieren, und in Tests gebe ich einfach ein wenig Schein. Das Problem bei diesem Ansatz ist das Rauschen: Wir verwenden häufig Generika .

Eine funktionierende Lösung; Es ist jedoch wichtig, das getestete Objekt zum Spion zu machen und die Aufrufe der internen Funktionen zu verfälschen.

systemUnderTest = Mockito.spy(systemUnderTest);
doReturn(true).when(systemUnderTest).checkFirstCondition(....);

Hier besteht die Sorge, dass die Implementierung des SUT effektiv geändert wird und es problematisch sein kann, die Tests mit der Implementierung synchron zu halten. Ist das wahr? Gibt es bewährte Methoden, um dieses Chaos interner Methodenaufrufe zu vermeiden?

Beachten Sie, dass es sich um Teile eines Algorithmus handelt. Daher ist es möglicherweise keine gewünschte Entscheidung, ihn auf mehrere Klassen aufzuteilen.

allprog
quelle

Antworten:

14

Unit-Tests sollten die Klassen, die sie testen, als Black Box behandeln. Wichtig ist nur, dass sich die öffentlichen Methoden so verhalten, wie es erwartet wird. Wie die Klasse dies durch interne Zustands- und private Methoden erreicht, spielt keine Rolle.

Wenn Sie der Meinung sind, dass es unmöglich ist, auf diese Weise aussagekräftige Tests zu erstellen, ist dies ein Zeichen dafür, dass Ihre Klassen zu leistungsfähig sind und zu viel leisten. Sie sollten erwägen, einige ihrer Funktionen in separate Klassen zu verschieben, die separat getestet werden können.

Philipp
quelle
1
Ich habe die Idee des Unit-Testens schon vor langer Zeit begriffen und einige davon erfolgreich geschrieben. Es täuscht nur, dass etwas auf dem Papier einfach aussieht, dass es im Code schlechter aussieht, und schließlich bin ich mit etwas konfrontiert, das eine wirklich einfache Oberfläche hat, von mir aber verlangt, die Hälfte der Welt um die Eingaben herum zu verspotten.
Allprog
@allprog Wenn Sie viel verspotten müssen, scheinen Sie zu viele Abhängigkeiten zwischen Ihren Klassen zu haben. Haben Sie versucht, die Kopplung zwischen ihnen zu verringern?
Philipp
@allprog Wenn Sie in dieser Situation sind, ist das Klassendesign schuld.
itsbruce
Es ist das Datenmodell, das die Kopfschmerzen verursacht. Es muss sich an ORM-Regeln und viele andere Anforderungen halten. Mit reiner Geschäftslogik und zustandslosem Code ist es viel einfacher, die richtigen Komponententests zu erstellen.
Allprog
3
Unit-Tests müssen das SUT nicht unbedingt als Backbox behandeln. Aus diesem Grund werden sie Unit-Tests genannt. Durch das Verspotten der Abhängigkeiten kann ich die Umgebung beeinflussen und um zu wissen, was ich verspotten muss, muss ich auch einige der Interna kennen. Dies bedeutet aber natürlich nicht, dass das SUT in irgendeiner Weise geändert werden sollte. Spionage erlaubt jedoch einige Änderungen.
Allprog
4

Wenn beide findError()und checkFirstCondition()usw. öffentliche Methoden Ihrer Klasse sind, findError()handelt es sich tatsächlich um eine Fassade für Funktionen, die bereits über dieselbe API verfügbar sind. Daran ist nichts auszusetzen, aber es bedeutet, dass Sie Tests schreiben müssen, die den bereits vorhandenen Tests sehr ähnlich sind. Diese Vervielfältigung spiegelt einfach die Vervielfältigung in Ihrer öffentlichen Oberfläche wider. Das ist kein Grund, diese Methode anders zu behandeln als andere.

Kilian Foth
quelle
Die internen Methoden werden nur deshalb veröffentlicht, weil sie testbar sein müssen und ich das SUT nicht unterordnen oder die Komponententests in die SUT-Klasse als statische innere Klasse aufnehmen möchte. Aber ich verstehe, worum es geht. Ich konnte jedoch keine guten Richtlinien finden, um solche Situationen zu vermeiden. Tutorials stecken immer auf der Basisebene, die nichts mit echter Software zu tun hat. Andernfalls ist der Grund für das Ausspähen genau der, um eine Verdoppelung des Testcodes zu vermeiden und den Umfang der Testeinheit zu bestimmen.
Allprog
3
Ich bin nicht einverstanden, dass die Hilfsmethoden für ordnungsgemäße Komponententests öffentlich sein müssen. Wenn der Vertrag einer Methode angibt, dass verschiedene Bedingungen geprüft werden, ist es nicht falsch, mehrere Tests gegen dieselbe öffentliche Methode zu schreiben, einen für jeden "Untervertrag". Der Zweck von Komponententests besteht darin, die Abdeckung des gesamten Codes zu erreichen, und nicht die oberflächliche Abdeckung öffentlicher Methoden über eine 1: 1-Methodentestkorrespondenz.
Kilian Foth
Das Testen nur mit der öffentlichen API ist um ein Vielfaches komplexer als das Testen der internen Komponenten nacheinander. Ich argumentiere nicht, ich verstehe, dass dieser Ansatz nicht der beste ist und es hat seinen Rückblick, den meine Frage zeigt. Das größte Problem ist, dass Funktionen in Java nicht komponierbar sind und die Problemumgehungen äußerst knapp sind. Für echte Unit-Tests scheint es jedoch keine andere Lösung zu geben.
Allprog
3

Unit-Tests sollten den Vertrag testen; Für sie ist es das einzig Wichtige. Das Testen von Dingen, die nicht Bestandteil des Vertrags sind, ist nicht nur Zeitverschwendung, sondern auch eine potenzielle Fehlerquelle. Jedes Mal, wenn ein Entwickler die Tests ändert, wenn er ein Implementierungsdetail ändert, sollten Alarmglocken läuten. Dieser Entwickler kann (ob absichtlich oder nicht) seine Fehler verbergen. Das bewusste Testen von Implementierungsdetails erzwingt diese schlechte Angewohnheit, wodurch die Wahrscheinlichkeit steigt, dass Fehler ausgeblendet werden.

Die internen Aufrufe sind ein Implementierungsdetail und sollten nur für die Messung der Leistung von Interesse sein . Welches ist in der Regel nicht die Aufgabe von Unit-Tests.

itsbruce
quelle
Klingt gut. Aber in Wirklichkeit muss ich den "String" eingeben und als Code bezeichnen. Er ist in einer Sprache, die nur sehr wenig über Funktionen weiß. In der Theorie kann ich ein Problem leicht beschreiben und hier und da Substitutionen vornehmen, um es zu vereinfachen. Im Code muss ich viel syntaktisches Rauschen hinzufügen, um diese Flexibilität zu erreichen, die mich davon abhält, sie zu verwenden. Wenn method aeinen Aufruf von method bin derselben Klasse enthält, amüssen tests of tests of enthalten b. Und es gibt keine Möglichkeit, dies zu ändern, solange bes nicht aals Parameter übergeben wird. Aber ich verstehe, dass es keine andere Lösung gibt.
Allprog
1
Wenn bes ein Teil der öffentlichen Schnittstelle ist, sollte es trotzdem getestet werden. Wenn nicht, muss es nicht getestet werden. Wenn Sie es öffentlich gemacht haben, nur weil Sie es testen wollten, haben Sie es falsch gemacht.
Itsbruce
Siehe meinen Kommentar zu @ Philips Antwort. Ich habe es noch nicht erwähnt, aber das Datenmodell ist die Quelle des Bösen. Reiner, staatenloser Code ist ein Kinderspiel.
Allprog
2

Erstens frage ich mich, was an der von Ihnen geschriebenen Beispielfunktion schwer zu testen ist. Soweit ich sehen kann, können Sie einfach verschiedene Eingaben übergeben und überprüfen, ob der richtige Boolesche Wert zurückgegeben wird. Was vermisse ich?

Was Spione betrifft, so ist die Art des sogenannten "White-Box" -Tests, bei dem Spione und Spott verwendet werden, um Größenordnungen arbeitsintensiver, nicht nur, weil so viel mehr Testcode zu schreiben ist, sondern zu jedem Zeitpunkt, an dem die Implementierung stattfindet geändert, müssen Sie auch die Tests ändern (auch wenn die Schnittstelle gleich bleibt). Diese Art von Tests ist auch weniger zuverlässig als Black-Box-Tests, da Sie sicherstellen müssen, dass der gesamte zusätzliche Testcode korrekt ist und Sie darauf vertrauen können, dass Black-Box-Unit-Tests fehlschlagen, wenn sie nicht mit der Schnittstelle übereinstimmen Sie können dem nicht vertrauen, wenn es um die Überbeanspruchung von Mocks mit Code geht, da der Test manchmal nicht einmal viel echten Code testet - nur Mocks. Wenn die Mocks nicht korrekt sind, ist es wahrscheinlich, dass Ihre Tests erfolgreich sind, Ihr Code jedoch weiterhin fehlerhaft ist.

Jeder, der Erfahrung mit White-Box-Tests hat, kann Ihnen sagen, dass es Ihnen schwerfällt, diese zu schreiben und zu pflegen. Zusammen mit der Tatsache, dass sie weniger zuverlässig sind, sind White-Box-Tests in den meisten Fällen einfach weit unterlegen.

BT
quelle
Danke für den Hinweis. Die Beispielfunktion ist um Größenordnungen einfacher als alles, was Sie in einem komplexen Algorithmus schreiben müssen. Eigentlich stellt sich die Frage eher wie folgt: Ist es problematisch, Algorithmen mit Spionen in mehreren Teilen zu testen? Dies ist kein zustandsbehafteter Code. Alle Zustände sind in Eingabeargumente unterteilt. Das Problem ist die Tatsache, dass ich die komplexe Funktion im Beispiel testen möchte, ohne sinnvolle Parameter für die Unterfunktionen angeben zu müssen.
Allprog
Mit dem Beginn der funktionalen Programmierung in Java 8 ist dies etwas eleganter geworden, aber die Funktionalität in einer einzelnen Klasse beizubehalten, kann bei Algorithmen die bessere Wahl sein, als die verschiedenen (allein nicht nützlichen) Teile in "einmal verwendbar" zu extrahieren. Klassen nur wegen der Testbarkeit. In dieser Hinsicht tun Spione das Gleiche wie Mocks, ohne jedoch den zusammenhängenden Code visuell in die Luft sprengen zu müssen. Tatsächlich wird der gleiche Setup-Code wie bei Mocks verwendet. Ich halte mich gerne von Extremen fern, jede Art von Test kann an bestimmten Orten angebracht sein. Testen ist irgendwie viel besser als nohow. :)
allprog
"Ich möchte die komplexe Funktion testen ... ohne vernünftige Parameter für die Unterfunktionen angeben zu müssen" - ich verstehe nicht, was Sie dort meinen. Welche Unterfunktionen? Sprechen Sie über die internen Funktionen, die von der 'komplexen Funktion' verwendet werden?
BT
Dafür ist Spionage in meinem Fall nützlich. Die internen Funktionen sind recht komplex zu steuern. Nicht wegen des Codes, sondern weil sie etwas logisch Komplexes implementieren. Das Verschieben von Inhalten in eine andere Klasse ist eine natürliche Option, aber diese Funktionen allein sind überhaupt nicht nützlich. Daher erwies es sich als bessere Option, die Klasse zusammenzuhalten und sie über die Spionagefunktionalität zu steuern. Hat fast ein Jahr lang einwandfrei gearbeitet und konnte Modellwechseln problemlos standhalten. Ich habe dieses Muster seitdem nicht mehr verwendet, dachte nur gut, um zu erwähnen, dass es in einigen Fällen praktikabel ist.
Allprog
@allprog "logisch komplex" - Wenn es komplex ist, benötigen Sie komplexe Tests. Daran führt kein Weg vorbei. Spione werden es dir nur schwerer und komplexer machen. Sie sollten verständliche Unterfunktionen erstellen, die Sie selbst testen können, anstatt mit Spionen ihr spezielles Verhalten in einer anderen Funktion zu testen.
BT