Wie testet man einen Encoder?

9

Ich habe so etwas:

public byte[] EncodeMyObject(MyObject obj)

Ich habe Unit-Tests wie folgt durchgeführt:

byte[] expectedResults = new byte[3]{ 0x01, 0x02, 0xFF };
Assert.IsEqual(expectedResults, EncodeMyObject(myObject));

EDIT: Die zwei Möglichkeiten, die ich vorgeschlagen habe, sind:

1) Verwenden Sie fest codierte Erwartungswerte wie im obigen Beispiel.

2) Verwenden eines Decoders zum Decodieren des codierten Bytearrays und Vergleichen der Eingabe- / Ausgabeobjekte.

Das Problem, das ich bei Methode 1 sehe, ist, dass es sehr spröde ist und viele hartcodierte Werte erfordert.

Das Problem bei Methode 2 besteht darin, dass das Testen des Encoders davon abhängt, ob der Decoder ordnungsgemäß funktioniert. Wenn der Codierer / Decodierer gleichmäßig (an derselben Stelle) defekt ist, können die Tests zu falsch positiven Ergebnissen führen.

Dies sind möglicherweise die einzigen Möglichkeiten, um diese Art von Methode zu testen. Wenn das der Fall ist, dann gut. Ich stelle die Frage, ob es bessere Strategien für diese Art von Tests gibt. Ich kann die Interna des jeweiligen Encoders, an dem ich arbeite, nicht preisgeben. Ich frage im Allgemeinen, wie Sie diese Art von Problem lösen würden, und ich halte die Interna nicht für wichtig. Angenommen, ein bestimmtes Eingabeobjekt erzeugt immer dasselbe Ausgabebyte-Array.

ConditionRacer
quelle
4
Wie geht es myObjectweiter myObjectnach { 0x01, 0x02, 0xFF }? Kann dieser Algorithmus aufgeschlüsselt und getestet werden? Der Grund, den ich frage, ist derzeit, es sieht so aus, als hätten Sie einen Test, der beweist, dass eine magische Sache eine andere magische Sache hervorbringt. Ihr einziges Vertrauen ist, dass der eine Eingang den einen Ausgang erzeugt. Wenn Sie den Algorithmus aufschlüsseln können, können Sie weiteres Vertrauen in den Algorithmus gewinnen und weniger auf magische Ein- und Ausgänge angewiesen sein.
Anthony Pegram
3
@Codism Was ist, wenn der Encoder und der Decoder an derselben Stelle kaputt sind?
ConditionRacer
2
Bei Tests wird per Definition etwas unternommen und überprüft, ob Sie die erwarteten Ergebnisse erzielt haben. Dies ist die Aufgabe Ihres Tests. Sie müssten natürlich sicherstellen, dass Sie genügend solche Tests durchführen, um sicherzustellen, dass Sie Ihren gesamten Code ausführen und Randfälle und andere Verrücktheiten abdecken.
Blrfl
1
@ Justin984, nun gehen wir tiefer. Ich würde diese privaten Interna nicht als Mitglieder der Encoder-API verfügbar machen, schon gar nicht. Ich würde sie vollständig aus dem Encoder entfernen. Oder besser gesagt, der Encoder würde an eine andere Stelle delegieren, eine Abhängigkeit . Wenn es ein Kampf zwischen einer nicht testbaren Monstermethode oder einer Reihe von Helferklassen ist, wähle ich jedes Mal die Helferklassen aus. Aber auch hier mache ich an dieser Stelle uninformierte Rückschlüsse auf Ihren Code, weil ich ihn nicht sehen kann. Wenn Sie jedoch Vertrauen in Ihre Tests gewinnen möchten, ist es eine Möglichkeit, mit kleineren Methoden weniger Dinge zu tun.
Anthony Pegram
1
@ Justin984 Wenn sich die Spezifikation ändert, ändern Sie die erwartete Ausgabe in Ihrem Test und es schlägt jetzt fehl. Anschließend ändern Sie die Encoder-Logik in Pass. Scheint genau so, wie TDD funktionieren soll und es wird nur dann fehlschlagen, wenn es sollte. Ich sehe nicht, wie das es spröde macht.
Daniel Kaplan

Antworten:

1

Sie befinden sich dort in einer unangenehmen Situation. Wenn Sie ein statisches Format hätten, in das Sie codieren, wäre Ihre erste Methode der richtige Weg. Wenn es nur Ihr eigenes Format wäre und niemand anders dekodieren müsste, wäre die zweite Methode der richtige Weg. Aber Sie passen nicht wirklich in eine dieser Kategorien.

Ich würde versuchen, die Dinge nach Abstraktionsebenen aufzuschlüsseln.

Also würde ich mit etwas auf der Bitebene beginnen, das ich so etwas testen würde

bitWriter = new BitWriter();
bitWriter.writeInt(42, bits = 7);
assertEqual( bitWriter.data(), {0x42} )

Die Idee ist also, dass der Bitwriter weiß, wie man die primitivsten Arten von Feldern wie Ints schreibt.

Komplexere Typen würden implementiert und getestet wie:

bitWriter = new BitWriter();
writeDate(bitWriter, new Datetime(2001, 10, 4));

bitWriter2 = new BitWriter();
bitWriter2.writeInt(2001, 12)
bitWriter2.writeInt(10, 4)
bitWriter2.writeInt(4, 6)

assertEquals(bitWriter.data(), bitWriter2.data() )

Beachten Sie, dass dadurch vermieden wird, dass Sie wissen, wie die tatsächlichen Bits gepackt werden. Das wurde vom vorherigen Test getestet, und für diesen Test gehen wir so ziemlich einfach davon aus, dass es funktioniert.

Dann hätten wir auf der nächsten Abstraktionsebene

bitWriter = new BitWriter();
encodeObject(bitWriter, myObject);


bitWriter2 = new BitWriter();
bitWriter2.writeInt(42, 32)
writeDate(bitWriter2, new Datetime(2001, 10, 4));
writeVarString(bitWriter2, "alphanumeric");

assertEquals(bitWriter.data(), bitWriter2.data() )

Daher versuchen wir auch hier nicht, das Wissen darüber einzubeziehen, wie Varstrings, Datumsangaben oder Zahlen tatsächlich codiert sind. In diesem Test interessiert uns nur die von encodeObject erzeugte Codierung.

Das Endergebnis ist, dass Sie, wenn das Format für Datumsangaben geändert wird, die Tests korrigieren müssen, die tatsächlich Datumsangaben enthalten. Alle anderen Codes und Tests befassen sich jedoch nicht damit, wie Datumsangaben tatsächlich codiert werden und sobald Sie den zu erstellenden Code aktualisieren diese Arbeit, alle diese Tests werden gut bestehen.

Winston Ewert
quelle
Ich mag das. Ich denke, das haben einige der anderen Kommentatoren darüber gesagt, wie man es in kleinere Teile zerlegt. Es vermeidet das Problem nicht vollständig, wenn sich die Spezifikation ändert, aber es macht es besser.
ConditionRacer
6

Hängt davon ab. Wenn die Codierung vollständig festgelegt ist und jede Implementierung genau dieselbe Ausgabe erstellen soll, ist es nicht sinnvoll, etwas anderes zu überprüfen, als zu überprüfen, ob die Beispieleingaben genau den erwarteten Ausgaben zugeordnet sind. Das ist der offensichtlichste Test und wahrscheinlich auch der am einfachsten zu schreibende.

Wenn bei alternativen Ausgaben wie im MPEG-Standard Spielraum vorhanden ist (z. B. gibt es bestimmte Operatoren, die Sie auf die Eingabe anwenden können, aber Sie können den Codierungsaufwand gegen die Ausgabequalität oder den Speicherplatz abwägen), ist es besser, die zu verwenden Definieren Sie die Dekodierungsstrategie für die Ausgabe und stellen Sie sicher, dass sie mit der Eingabe identisch ist. Wenn die Codierung verlustbehaftet ist, liegt sie relativ nahe an der ursprünglichen Eingabe. Das ist schwieriger zu programmieren, schützt Sie jedoch vor zukünftigen Verbesserungen an Ihrem Encoder.

Kilian Foth
quelle
2
Angenommen, Sie verwenden den Decoder und vergleichen die Werte. Was ist, wenn sowohl der Encoder als auch der Decoder an derselben Stelle defekt sind? Der Encoder codiert falsch und der Decoder decodiert falsch, aber die Eingabe- / Ausgabeobjekte sind korrekt, da der Vorgang zweimal falsch ausgeführt wurde.
ConditionRacer
@ Justin984 verwenden dann sogenannte "Testvektoren", kennen Eingabe / Ausgabe-Paare, die Sie genau verwenden können, um einen Encoder und Decoder
Ratschenfreak
@ratchet freak Das bringt mich zurück zum Testen mit erwarteten Werten. Was in Ordnung ist, das mache ich gerade, aber es ist ein bisschen spröde, also habe ich gesucht, ob es bessere Möglichkeiten gibt.
ConditionRacer
1
Abgesehen davon, dass der Standard sorgfältig gelesen und für jede Regel ein Testfall erstellt wurde, kann kaum vermieden werden, dass sowohl ein Encoder als auch ein Decoder denselben Fehler enthalten. Nehmen wir zum Beispiel an, dass "ABC" in "xyz" übersetzt werden muss, aber der Encoder weiß das nicht und Ihr Decoder würde "xyz" auch nicht verstehen, wenn er jemals darauf stoßen würde. Die handgefertigten Testfälle enthalten nicht die "ABC" -Sequenz, da der Programmierer diese Regel nicht kannte und auch ein Test mit Codierung / Decodierung zufälliger Zeichenfolgen falsch bestanden würde, da sowohl Codierer als auch Decodierer das Problem ignorieren.
user281377
1
Bemühen Sie sich, Encoder-Ausgaben von anderen Anbietern zu erhalten, um Fehler zu erkennen, die sowohl Decoder als auch Encoder betreffen, die aufgrund fehlender Kenntnisse von Ihnen selbst geschrieben wurden. Versuchen Sie auch, die Ausgabe Ihres Encoders auf den Decodern von Drittanbietern zu testen. Es gibt keine Alternative.
Rwong
3

Testen Sie das encode(decode(coded_value)) == coded_valueund decode(encode(value)) == value. Sie können den Tests eine zufällige Eingabe geben, wenn Sie möchten.

Es ist immer noch möglich, dass sowohl der Encoder als auch der Decoder auf komplementäre Weise defekt sind, aber das scheint ziemlich unwahrscheinlich, es sei denn, Sie haben ein konzeptionelles Missverständnis des Codierungsstandards. Das Durchführen von hartcodierten Tests des Codierers und Decodierers (wie Sie es bereits tun) sollte sich davor schützen.

Wenn Sie Zugriff auf eine andere Implementierung haben, von der bekannt ist, dass sie funktioniert, können Sie sie zumindest verwenden, um sicher zu sein, dass Ihre Implementierung gut ist, auch wenn die Verwendung in den Komponententests unmöglich wäre.

Michael Shaw
quelle
Ich bin damit einverstanden, dass ein komplementärer Encoder / Decoder-Fehler im Allgemeinen unwahrscheinlich ist. In meinem speziellen Fall wird der Code für die Encoder / Decoder-Klassen von einem anderen Tool generiert, das auf Regeln aus einer Datenbank basiert. Daher treten gelegentlich komplementäre Fehler auf.
ConditionRacer
Wie kann es zu "kostenlosen Fehlern" kommen? Dies impliziert, dass es eine externe Spezifikation für die codierte Form und damit einen externen Decodierer gibt.
Kevin Cline
Ich verstehe Ihre Verwendung des Wortes extern nicht. Es gibt jedoch eine Spezifikation für die Codierung der Daten und auch einen Decoder. Ein komplementärer Fehler besteht darin, dass sowohl der Codierer als auch der Decodierer auf eine Weise arbeiten, die komplementär ist, aber von der Spezifikation abweicht. Ich habe ein Beispiel in den Kommentaren unter der ursprünglichen Frage.
ConditionRacer
Wenn der Encoder ROT13 implementieren sollte, aber versehentlich ROT14 und der Decoder auch, dann decodieren (codieren ('a')) == 'a', aber der Encoder ist immer noch defekt. Für Dinge, die viel komplizierter sind, ist es wahrscheinlich viel weniger wahrscheinlich, dass so etwas passieren würde, aber theoretisch könnte es passieren.
Michael Shaw
@MichaelShaw nur eine Kleinigkeit, der Encoder und Decoder für ROT13 sind gleich; ROT13 ist seine eigene Umkehrung. Wenn Sie ROT14 versehentlich implementiert haben, decode(encode(char))wäre dies nicht gleich char(es wäre gleich char+2).
Tom Marthenal
2

Testen Sie die Anforderungen .

Wenn die Anforderungen nur "In einen Byte-Stream codieren, der beim Decodieren ein äquivalentes Objekt erzeugt" sind, testen Sie den Encoder einfach durch Decodieren. Wenn Sie sowohl den Encoder als auch den Decoder schreiben, testen Sie sie einfach zusammen. Sie können keine "Übereinstimmungsfehler" haben. Wenn sie zusammenarbeiten, besteht der Test.

Wenn es andere Anforderungen für den Datenstrom gibt, müssen Sie diese testen, indem Sie die codierten Daten untersuchen.

Wenn das codierte Format vordefiniert ist, müssen Sie entweder die codierten Daten wie erwartet anhand des erwarteten Ergebnisses überprüfen oder (besser) einen Referenzdecoder erhalten, dem Sie bei der Überprüfung vertrauen können. Durch die Verwendung eines Referenzdecoders wird die Möglichkeit ausgeschlossen, dass Sie die Formatspezifikation falsch interpretiert haben.

Kevin Cline
quelle
1

Abhängig von dem von Ihnen verwendeten Testframework und -paradigma können Sie weiterhin das Arrange Act Assert-Muster verwenden, wie Sie bereits gesagt haben.

[TestMethod]
public void EncodeMyObject_ForValidInputs_Encodes()
{
    //Arrange object under test
    MyEncoder encoderUnderTest = new MyEncoder();
    MyObject validObject = new MyOjbect();
    //arrange object for condition under test

    //Act
    byte[] actual = encoderUnderTest.EncodeMyObject(myObject);

    //Assert
    byte[] expected = new byte[3]{ 0x01, 0x02, 0xFF };
    Assert.IsEqual(expected, actual);
}

Sie sollten die Anforderungen für EncodeMyObject()dieses Muster kennen und es verwenden können, um anhand jedes dieser Kriterien gültige und ungültige Kriterien zu testen, indem Sie jedes von ihnen anordnen und das erwartete Ergebnis für expected, ähnlich für den Decoder, fest codieren .

Da die erwarteten fest codiert sind, sind diese zerbrechlich, wenn Sie eine massive Änderung haben.

Möglicherweise können Sie mit etwas Parametergesteuertem automatisieren (siehe Pex ) oder, wenn Sie DDD oder BDD ausführen, Gerkin / Gurke .

StuperUser
quelle
1

Sie können entscheiden, was Ihnen wichtig ist.

Ist es Ihnen wichtig, dass ein Objekt die Rundreise überlebt und das genaue Drahtformat nicht wirklich wichtig ist? Oder ist das genaue Drahtformat ein wichtiger Bestandteil der Funktionalität Ihres Encoders und Decoders?

Wenn erstere, dann stellen Sie einfach sicher, dass Objekte die Rundreise überleben. Wenn sowohl der Encoder als auch der Decoder genau komplementär defekt sind, ist es Ihnen eigentlich egal.

In letzterem Fall müssen Sie testen, ob das Drahtformat für die angegebenen Eingänge den Erwartungen entspricht. Dies bedeutet, entweder das Format direkt zu testen oder eine Referenzimplementierung zu verwenden. Wenn Sie jedoch die Grundlagen getestet haben, können Sie zusätzliche Round-Trip-Tests nutzen, die sich leichter in Volumen schreiben lassen.

Bill Michell
quelle