Welche UnitTest Strategie ist am sinnvollsten?

456 views
Skip to first unread message

Max

unread,
Feb 17, 2015, 3:14:31 AM2/17/15
to clean-code...@googlegroups.com
Hallo, 

aktuell frage ich mich, was eine gute Strategie für das UnitTesten ist.

Folgendes Beispiel soll meine Frage etwas verdeutlichen:

StreamExtensions.cs
-- ReadBytes(this Stream stream, ...)
-- WriteBytes(this Stream stream, ...)

Diese Extensions für die Stream Klasse ermöglichen es mir, an der aktuellen Position des Streams "n" bytes auszulesen und zurück zu geben. (Ähnlich der Read()-Methode)

File.cs
-- ReadByteBlocksFromFile(...)
-- ReadBytesFromFile(...)
-- WriteBytesToFile(...)
-- AppendByteToFile(...)
-- ...

Diese statischen Methoden ermöglichen es mir, Daten in eine Datei zu schreiben oder von da zu lesen.
Prinzipiell öffnen diese Methoden jeweils nur einen Stream und rufen über diesen die Extensions (siehe oben).

Nun habe ich die StreamExtensions komplett getestet und habe alle Ergebnisse sowie deren Verhalten validiert.

Ist es nun sinnvoll die Methoden der Klasse "File.cs" ebenfalls im Ergebnis zu validieren/testen?
Sprich: kommen wirklich die angeforderten Bytes zurück, sind es die richtigen Bytes, ist es die richtige Anzahl
an Bytes, ...?

Eigentlich habe ich das direkte Lesen aus dem Stream ja schon getestet?

Meine Möglichen Ansätze dazu sind:

- Alle Methoden komplett testen (Blackbox) - also auch nochmal das erwartete Ergebnis validieren
- Nur die jeweiligen "neuen" Dinge testen (Whitebox) - also nur schauen ob der Stream zur richtigen Datei aufgebaut wurde, ... und davon ausgehen, 
das die jeweils gerufenen Methoden das leisten, was ich erwarte (es existieren für diese gerufen Methoden ja jeweils eigene Tests)
- die gerufenen Methoden über Stubs/Fakes mocken, so dass diese immer ein definiertes Ergebnis liefern und dann nur schauen, 
ob die Methode die ich eigentlich teste diese Daten korrekt weiterreicht und ggf. weiterverarbeitet
(somit würden die Tests im Falle eines Fehlers einer gerufenen Methode trotzdem noch funktionieren und behalten ihre Aussagekraft?)

Oder sehe ich das ganze Thema zu einseitig und sollte die Methoden der Klasse "File.cs" eigentlich als eine Art Integrationsmethoden sehen, 
die dann separat (und anders ..??) getestet werden sollten/können?

Ich hoffe ich konnte meine Frage genau genug darlegen, 
Gruß Max

Ralf Westphal

unread,
Feb 18, 2015, 2:20:26 PM2/18/15
to clean-code...@googlegroups.com
Ich verstehe das Verhältnis zwischen den Extension-Methoden nicht und denen in File.cs. Benutzt du die File.cs Methoden in den Extension-Methods - oder umgekehrt?

Egal. Jedenfalls gibt es eine Hierarchie. Irgendwas integriert, z.B. Methode I(). Und irgendwas wird integriert, z.B. O1() und O2().

Ich tue da beides: Wenn ich nach TDD vorgehe, dann fange ich außen an, also mit einem Test für I(). Wenn ich dann merke, dass ich O1() oder O2() brauche, dann mache ich dafür Tests. Allerdings - und das ist der Trick - sind die temporär. Ich nenne sie Gerüsttests. Denn wie ein Gerüst am Bau kommen sie am Ende weg, wenn das stabil steht, was ich haben will. Und das ist I().

Ich nenne das Informed TDD. Hier habe ich dazu einige Artikel geschrieben: http://geekswithblogs.net/theArchitectsNapkin/category/19819.aspx

Max

unread,
Feb 18, 2015, 2:34:14 PM2/18/15
to clean-code...@googlegroups.com
Ich benutze die Extension-Methoden in den File.cs Methoden.

Für welche Methoden erstellst du denn die von dir benannten Gerüsttests? Ich versuche immer alle Methoden zu testen, die von der Sichtbarkeit mindestens internal bzw. public sind.
Zu deinem Beispiel:

Wenn ich jetzt eine Methode I() teste und implementiere und merke, dass ich die Methoden O1() und O2() brauche, dann teste ich diese nach deinem System temporär weil diese Methoden dann
warscheinlich private sind? Mache ich diese Methoden dann internal oder public, dann werden aus den Gerüsttests dann normale?

Ralf Westphal

unread,
Feb 19, 2015, 2:04:45 AM2/19/15
to clean-code...@googlegroups.com

Ich benutze die Extension-Methoden in den File.cs Methoden.

aha, so herum also.

 

Für welche Methoden erstellst du denn die von dir benannten Gerüsttests? Ich versuche immer alle Methoden zu testen, die von der Sichtbarkeit mindestens internal bzw. public sind.

stabile tests gibts bei mir für öffentliche methoden, halt die oberfläche einer klasse, ihr "serviceinterface". das sind dann integrationstests mit mocks hier und da, wo es sinn macht.

gerüsttests gibt es für privates zeugs. ob ich dafür das private zeug mal internal mache oder mit reflection zugreife, ist zweitrangig. am ende fliegen diese tests weg.

natürlich muss die funktionalität solcher interna durch die integrationstest "an der oberfläche" auch getestet werden. aber nicht so ausführlich. das machen temporär die gerüsttests. und auch sehr gezielt. das sind unit tests.

und wenn ich weiß, das ding läuft... dann kann der gerüsttest weg. im repository ist es ja irgendwo drin :-)
Message has been deleted

Max

unread,
Feb 23, 2015, 1:24:38 PM2/23/15
to clean-code...@googlegroups.com
Bei uns im Team gibt es da sehr geteilte Meinungen zur den UnitTests - folgendes Beispiel:

IFoo:
 -- double[] foo(double[] values)

IBar:
 -- double bar(double value)

Impelemtierung : IFoo, IBar:
 -- private double doForSingleValue(double value)
 -- private double[] doForArray(double[] values)
 -- IFoo.foo()
 -- IBar.bar()

Die Implementierung von doForArray iteriert durch alle Werte des übergebenen Arrays und ruft für jeden Wert die Methode doForSingleValue.
Die Mehode IFoo.foo() ruft die Methode doForArray und die Implementierung IBar.bar() ruft direkt die Mehtode doForSingleValue.

Wie geht man jetzt mit so etwas um? Im Endeffekt würde ich beide Methoden die über ein Interface kommen quasi gleich testen,
da sie ja nur die Funktionalität von "doForSingleValue" ausführen (einmal für mehrere Werte in einem Array und einmal für einen einzelnen Wert).

Wie würdest du sowas sinnvoll testen?!

Ralf Westphal

unread,
Feb 24, 2015, 2:34:31 AM2/24/15
to clean-code...@googlegroups.com
Ich verstehe nicht, inwiefern da ein Interface eine Rolle spielt.

Du hast diese Aufrufhierarchien:

doForArray
  doForSingle

foo
  doForArray
bar
  doForSingle

Ich nehme an, dass foo() und bar() noch andere Sachen machen (Logik enthält), also eine funktionale Abhängigkeit besteht. Du willst also im Grunde nicht nochmal die aufgerufenen Methoden testen. Also musst du sie eigentlich mocken. So ist das, wenn du Logik und Integration vermischst.

Auf der anderen Seite ist die Frage: Was sind denn die API-Methoden, die public Methoden?

Mir scheint das Beispiel aber zu abstrakt. Du musst konkreter werden und auch etwas über die Inhalte von mindestens foo() und bar() sagen.

Max

unread,
Feb 24, 2015, 3:40:37 AM2/24/15
to clean-code...@googlegroups.com
Hallo, für mich ist es schwer die reellen Fälle zu als Beispiele hier darzustellen, deswegen jetzt der richtige Fall.

Es geht um Quantisierungen: davon gibt es verschiedene, eine Lineare, eine Polynomquantisierung, eine nach einer Graußtabelle, ....

Alle Quantisierungen implementieren ein Interface "IQuantization", welche nur eine Methode definiert (double[] Quantize(double[] values)).
In dieser Methode rufe ich ohne weitere Logik die in der Klasse definierte private Mehtode Quantize(double[] value).

Weiterhin gibt es ein Interface IConverter<T1, T2> welches die Methode "double Convert(double value)" definiert.
Die Quantisierungen implementieren dieses Interface ebenfalls explizit.
In dieser IConverter.Convert-Methode rufe ich dann ohne weitere Logik die in der Klasse definierte private Methode Quantize(double value).

Hier die gesamte Struktur:

QuantizationLinear : IQuantization, IConverter<double, double>
-- private QuantizeForArray (macht nichts außer durch das Array zu iterieren und für jeden Wert QuantizeForSingleValue zu rufen)
--> private QuantizeForSingleValue

-- IQuantization.Quantize (ruft nur die Methode QuantizeForArray, keine weitere Logik)
--> private QuantizeForArray

-- IConverter.Convert  (ruft nur die Methode QuantizeForSingleValue, keine weitere Logik)
--> private QuantizeForSingleValue

Die einzige Logik steckt also in der Klassenmethode "QuantizeForSingleValue".

Meine Frage ist jetzt, wie ich das gesamte Konstrukt am effektivsten Test, denn eigentlich sind die zu erwatenden Werte für die Methoden
IQuantization.Quantize und IConverter.Convert das Ergebnis der Funktion QuantizeForSingleValue.

Ich hoffe ich konnte mein Problem jetzt etwas deutlicher darlegen.

Gruß Max

Die über das Interface explizit implementierte Methode ruft ohne weitere Logik eine Klassenmethode "private double[] QuantizeForArray(double[] values)", welche
durch das Array iteriert und für jeden Wert die Klassenmethode "private double QuantizeForSingleValue

Ralf Westphal

unread,
Feb 25, 2015, 3:55:04 AM2/25/15
to clean-code...@googlegroups.com
QuantizeForSingleValue() ist das Arbeitspferd. Das ist zu testen. Der Rest ist doch trivial. Würde ich nicht testen - außer es gibt da Randbedingungen bei der Schleife.
Reply all
Reply to author
Forward
0 new messages