Wie detailliert sollten TDD-Tests sein?

18

Während der TDD-Schulung auf der Grundlage eines medizinischen Softwarefalls wird die folgende Story implementiert: "Wenn der Benutzer die Schaltfläche Speichern drückt, sollte das System einen Patienten hinzufügen, ein Gerät hinzufügen und Gerätedatensätze hinzufügen."

Die endgültige Implementierung sieht ungefähr so ​​aus:

if (_importDialog.Show() == ImportDialogResult.SaveButtonIsPressed)
{
   AddPatient();
   AddDevice();
   AddDeviceDataRecords();
}

Wir haben zwei Möglichkeiten, dies umzusetzen:

  1. Drei Tests, bei denen jeweils eine Methode überprüft wurde (AddPatient, AddDevice, AddDeviceDataRecords)
  2. Ein Test, der alle drei Methoden verifiziert, wurde aufgerufen

Im ersten Fall, wenn eine Bedingung nicht erfüllt ist, schlagen alle drei Tests fehl. Aber im zweiten Fall, wenn der Test fehlschlägt, sind wir uns nicht sicher, was genau falsch ist. Welchen Weg würdest du bevorzugen?

SiberianGuy
quelle

Antworten:

8

Aber im zweiten Fall, wenn der Test fehlschlägt, sind wir uns nicht sicher, was genau falsch ist.

Ich denke, das würde weitgehend davon abhängen, wie gute Fehlermeldungen der Test erzeugt. Im Allgemeinen gibt es verschiedene Möglichkeiten, um zu überprüfen, ob eine Methode aufgerufen wurde. Wenn Sie beispielsweise ein Scheinobjekt verwenden, erhalten Sie eine genaue Fehlermeldung, die beschreibt, welche erwartete Methode während des Tests nicht aufgerufen wurde. Wenn Sie überprüfen, ob die Methode durch Erkennen der Auswirkungen des Aufrufs aufgerufen wurde, müssen Sie eine beschreibende Fehlermeldung erstellen.

In der Praxis hängt die Wahl zwischen Option 1 und 2 auch von der Situation ab. Wenn ich den oben gezeigten Code in einem Legacy-Projekt sehe, wähle ich den pragmatischen Ansatz von Fall 2, um zu überprüfen, ob jede der drei Methoden ordnungsgemäß aufgerufen wird, wenn die Bedingung erfüllt ist. Wenn ich diesen Code gerade entwickle, werden die 3 Methodenaufrufe höchstwahrscheinlich nacheinander zu unterschiedlichen Zeitpunkten (möglicherweise Tage oder Monate voneinander entfernt) hinzugefügt, sodass ich einen neuen, separaten Komponententest hinzufügen würde um jeden Anruf zu verifizieren.

Beachten Sie auch, dass Sie in beiden Fällen getrennte Komponententests durchführen müssen, um zu überprüfen, ob die einzelnen Methoden genau das tun, was sie tun sollen.

Péter Török
quelle
Finden Sie es nicht vernünftig, diese drei Tests irgendwann zu einer zu kombinieren?
SiberianGuy
@Idsa, kann eine vernünftige Entscheidung sein, obwohl ich mich in der Praxis selten mit dieser Art von Refactoring beschäftige. Andererseits arbeite ich mit Legacy-Code, bei dem die Prioritäten unterschiedlich sind: Wir konzentrieren uns darauf, die Testabdeckung des vorhandenen Codes zu erhöhen und die wachsende Anzahl von Komponententests wartbar zu halten.
Péter Török,
30

Die Granularität in Ihrem Beispiel scheint der Unterschied zwischen Unit- und Acceptance-Tests zu sein.

Ein unittest testet eine einzelne Funktionseinheit mit möglichst wenigen Abhängigkeiten. In deinem Fall könnten es 4 Unittests sein

  • Fügt AddPatient einen Patienten hinzu (dh ruft die relevanten Datenbankfunktionen auf)?
  • fügt AddDevice ein Gerät hinzu?
  • Fügt AddDeviceDataRecords die Datensätze hinzu?
  • führt die Unamend-Hauptfunktion in Ihrem Beispiel aus und ruft AddPatient, AddDevice und AddDeviceFunctions auf

Unittests sind für die Entwickler , damit sie sich darauf verlassen können, dass ihr Code technisch korrekt ist

Die Abnahmetests sollten die kombinierte Funktionalität aus Sicht des Benutzers testen. Sie sollten entlang der User Stories modelliert werden und so hoch wie möglich sein. Sie müssen also nicht prüfen, ob Funktionen aufgerufen werden, sondern ob ein für den Benutzer sichtbarer Nutzen erzielt wird:

Wenn der Benutzer die Daten eingibt, klickt er auf OK und ...

  • ... zur Patientenliste geht, sollte er einen neuen Patienten mit dem angegebenen Namen sehen
  • ... in die Geräteliste geht, sollte er ein neues Gerät sehen
  • ... zu den Details des neuen Gerätes geht, sollte er neue Datenaufzeichnungen sehen

Akzeptanztests sind für die Kunden oder zum Aufbau einer besseren Kommunikation mit ihnen.

Zur Beantwortung Ihrer Frage "Was würden Sie bevorzugen?"

keppla
quelle
13

Wir haben zwei Möglichkeiten, dies umzusetzen:

Das ist falsch

Drei Tests, bei denen jeweils eine Methode überprüft wurde (AddPatient, AddDevice, AddDeviceDataRecords)

Sie müssen dies tun, um sicherzustellen, dass es funktioniert.

Ein Test, der alle drei Methoden verifiziert, wurde aufgerufen

Sie müssen dies auch tun, um sicherzustellen, dass die API funktioniert.

Die Klasse - als Einheit - muss vollständig getestet werden. Jede Methode.

Sie können mit einem Test beginnen, der alle drei Methoden abdeckt, aber nicht viel aussagt.

Wenn der Test fehlschlägt, sind wir uns nicht sicher, was genau falsch ist.

Richtig. Deshalb testen Sie alle Methoden.

Sie müssen die öffentliche Schnittstelle testen. Da diese Klasse drei Dinge plus eins macht (auch wenn sie aufgrund von User Storys in einer Methode gebündelt sind), müssen Sie alle vier Dinge testen. Drei Low-Level- und ein Bundle.

S.Lott
quelle
2

Wir schreiben unsere Komponententests für aussagekräftige Funktionssätze, die oft einer Methode zugeordnet werden (wenn Sie Ihren Code gut geschrieben haben), aber manchmal größer werden und viele Methoden umfassen.

Stellen Sie sich zum Beispiel vor, dass beim Hinzufügen eines Patienten zu Ihrem System einige Unterprogramme (untergeordnete Funktionen) aufgerufen werden müssen:

  1. VerifyPatientQualification
  2. EnsureDoctorExistence
  3. CheckInsuranceHistory
  4. Stellen Sie sicher, dass sich EmptyBed befindet

Wir könnten auch einen Unit-Test für jede dieser Funktionen schreiben.

Saeed Neamati
quelle
2

Eine einfache Faustregel, die ich befolgt habe, ist, den Test so zu benennen, dass sie genau beschreibt, was der Test tut. Wenn der Name des Tests zu komplex wird, ist dies ein Zeichen dafür, dass der Test möglicherweise zu viel bewirkt. Wenn Sie beispielsweise einen Test benennen, um das zu tun, was Sie in Option 2 vorschlagen, sieht dies möglicherweise wie folgt aus: PatientIsAddedDeviceIsAddedAndDeviceDataRecordsWhenSaved. Dies ist weitaus komplexer als drei separate Tests: PatientIsAddedWhenSaved, DeviceIsAddedWhenSaved, DataRecordsWhenSaved. Ich denke auch, dass die Lehren, die man aus BDD ziehen kann, ziemlich interessant sind, wenn jeder Test wirklich eine einzelne Anforderung darstellt, die in einer natürlichen Sprache beschrieben werden könnte.

jpierson
quelle