Nehmen wir an, ich habe in C # eine Erweiterungsmethode für byte
Arrays geschrieben, die sie wie folgt in Hex-Strings codiert:
public static class Extensions
{
public static string ToHex(this byte[] binary)
{
const string chars = "0123456789abcdef";
var resultBuilder = new StringBuilder();
foreach(var b in binary)
{
resultBuilder.Append(chars[(b >> 4) & 0xf]).Append(chars[b & 0xf]);
}
return resultBuilder.ToString();
}
}
Ich könnte die obige Methode mit NUnit wie folgt testen :
[Test]
public void TestToHex_Works()
{
var bytes = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
Assert.AreEqual("0123456789abcdef", bytes.ToHex());
}
Wenn ich das Extensions.ToHex
Innere meines Projekts verwende, nehmen wir in der Foo.Do
Methode Folgendes an:
public class Foo
{
public bool Do(byte[] payload)
{
var data = "ES=" + payload.ToHex() + "ff";
// ...
return data.Length > 5;
}
// ...
}
Dann Foo.Do
hängen alle Willensprüfungen vom Erfolg ab TestToHex_Works
.
Bei Verwendung freier Funktionen in C ++ ist das Ergebnis dasselbe: Tests, bei denen Testmethoden, die freie Funktionen verwenden, getestet werden, hängen vom Erfolg der Tests freier Funktionen ab.
Wie kann ich mit solchen Situationen umgehen? Kann ich diese Testabhängigkeiten irgendwie auflösen? Gibt es eine bessere Möglichkeit, die obigen Codefragmente zu testen?
unit-testing
static-methods
Akira
quelle
quelle
Then all tests of Foo.Do will depend on the success of TestToHex_works
-- Damit? Sie haben keine Klassen, die vom Erfolg anderer Klassen abhängen?toHex
(oder Swap-Implementierungen) verwenden können. Ansonsten ist alles in Ordnung. Ihr Code, der in Hex konvertiert wird, wird getestet. Jetzt gibt es einen anderen Code, der diesen getesteten Code als Dienstprogramm verwendet, um sein eigenes Ziel zu erreichen.Antworten:
Ja. Deshalb haben Sie Tests für
TextToHex
. Wenn diese Tests bestanden werden, erfüllt die Funktion die in diesen Tests definierte Spezifikation. SoFoo.Do
kann man es sicher anrufen und sich keine Sorgen machen. Es ist bereits abgedeckt.Sie können eine Schnittstelle hinzufügen, die Methode zu einer Instanzmethode machen und in diese einfügen
Foo
. Dann könntest du dich lustig machenTextToHex
. Aber jetzt müssen Sie einen Mock schreiben, der möglicherweise anders funktioniert. Sie benötigen also einen "Integrationstest", um die beiden zusammenzubringen und sicherzustellen, dass die Teile wirklich zusammenarbeiten. Was hat das erreicht, außer die Dinge komplexer zu machen?Die Idee, dass Unit-Tests Teile Ihres Codes isoliert von anderen Teilen testen sollten, ist ein Irrtum. Die "Einheit" in einem Einheitentest ist eine isolierte Ausführungseinheit. Wenn zwei Tests gleichzeitig ausgeführt werden können, ohne sich gegenseitig zu beeinflussen, werden sie isoliert ausgeführt, ebenso wie Unit-Tests. Statische Funktionen, die schnell sind, keinen komplexen Aufbau haben und keine Nebenwirkungen wie Ihr Beispiel haben, können daher direkt in Komponententests verwendet werden. Wenn Sie Code haben, der langsam, komplex einzurichten oder Nebenwirkungen hat, sind Mocks hilfreich. Sie sollten jedoch an anderer Stelle vermieden werden.
quelle
Nun, ich sehe hier keine Abhängigkeit. Zumindest nicht die Art, die uns zwingt, einen Test vor dem anderen durchzuführen. Die Abhängigkeit, die wir zwischen Tests aufbauen (unabhängig von der Art), ist ein Vertrauen .
Wir erstellen einen Code (Test zuerst oder nicht) und stellen sicher, dass die Tests bestanden werden. Dann sind wir in der Lage, mehr Code zu erstellen. Der gesamte Code, der zuerst darauf aufgebaut ist, basiert auf Vertrauen und Gewissheit. Dies ist mehr oder weniger das, was @DavidArno (sehr gut) in seiner Antwort erklärt.
Unit-Tests sollten in beliebiger Reihenfolge, zu jeder Zeit, in jeder Umgebung und so schnell wie möglich ausgeführt werden. Ob
TestToHex_Works
der erste oder der letzte ausgeführt wird, sollte Sie nicht beunruhigen.Wenn
TestToHex_Works
dies aufgrund von Fehlern fehlschlägtToHex
,ToHex
enden alle Tests, auf die Sie sich verlassen, mit unterschiedlichen Ergebnissen und schlagen (idealerweise) fehl. Der Schlüssel hier ist das Erkennen dieser unterschiedlichen Ergebnisse. Wir machen es so, dass Unit-Tests deterministisch sind. Wie du es hier tustWeitere Unit-Tests, auf die man sich stützt,
ToHex
sollten ebenfalls deterministisch sein. Wenn allesToHex
gut geht, sollte das Ergebnis das erwartete sein. Wenn Sie eine andere bekommen, ist irgendwo etwas schiefgegangen, und genau das möchten Sie von einem Komponententest, um diese subtilen Änderungen zu erkennen und schnell zu scheitern.quelle
Mit einem so minimalen Beispiel ist es ein bisschen schwierig, aber lassen Sie uns noch einmal überlegen, was wir tun:
Warum haben Sie dazu eine Erweiterungsmethode geschrieben? Sofern Sie keine Bibliothek schreiben, um Byte-Arrays in Zeichenfolgen zu codieren, ist dies wahrscheinlich nicht die Anforderung, die Sie erfüllen möchten. Wie es aussieht, haben Sie hier versucht, die Anforderung "Validieren einer Nutzlast, bei der es sich um ein Byte-Array handelt" zu erfüllen. Die von Ihnen implementierten Komponententests sollten daher "Bei einer gültigen Nutzlast X gibt meine Methode true zurück" und "Gegeben" lauten Bei einer ungültigen Nutzlast Y gibt meine Methode false zurück. "
Wenn Sie dies zum ersten Mal implementieren, können Sie das "Byte-to-Hex" -Ding in der Methode inline ausführen. Und das ist gut so. Später erhalten Sie dann eine andere Anforderung (z. B. "Nutzdaten als Hex-Zeichenfolge anzeigen"), bei der Sie bei der Implementierung feststellen, dass Sie auch ein Byte-Array in eine Hex-Zeichenfolge konvertieren müssen. An diesem Punkt erstellen Sie Ihre Erweiterungsmethode, überarbeiten die alte Methode und rufen sie aus Ihrem neuen Code auf. Ihre Tests für die Validierungsfunktionalität sollten sich nicht ändern. Ihre Tests zur Anzeige der Nutzdaten sollten gleich sein, unabhängig davon, ob der Byte-zu-Hex-Code inline oder statisch war. Die statische Methode ist ein Implementierungsdetail, das Sie beim Schreiben der Tests nicht berücksichtigen sollten. Der Code in der statischen Methode wird von seinen Verbrauchern getestet. Ich würde nicht'
quelle
Genau. Und dies ist eines der Probleme bei statischen Methoden. Ein weiteres Problem ist, dass OOP in den meisten Situationen eine viel bessere Alternative ist.
Dies ist auch einer der Gründe, warum Dependency Injection verwendet wird.
In Ihrem Fall bevorzugen Sie möglicherweise einen konkreten Konverter , den Sie in eine Klasse einfügen, für die einige Werte in hexadezimal konvertiert werden müssen . Eine von dieser konkreten Klasse implementierte Schnittstelle würde den Vertrag definieren, der zum Konvertieren der Werte erforderlich ist.
Sie können Ihren Code nicht nur einfacher testen, sondern auch Implementierungen später austauschen (da es viele andere Möglichkeiten gibt, Werte in Hexadezimalzahlen umzuwandeln, von denen einige unterschiedliche Ausgaben erzeugen).
quelle
multiply
Methode implementiert , würden Sie sich darüber lustig machen ? Was bedeutet, dass Sie zum Refactor (und stattdessen zum Verwenden des Operators *) jede einzelne Scheindeklaration in Ihrem Code ändern müssen? Sieht für mich nicht nach einfachem Code aus. Alles, was ich sage, isttoHex
sehr einfach und hat überhaupt keine Nebenwirkungen, daher sehe ich keinen Grund, dies zu verspotten oder zu stoppen. Es sind zu viel Kosten für absolut keinen Gewinn. Das heißt nicht, dass wir es nicht injizieren sollten, aber das hängt von der Verwendung ab.String
undint
auch? Oder grundlegende Utility-Klassen aus der Standardbibliothek?