Gibt es eine bessere Möglichkeit, Komponententests zu schreiben als eine Reihe von 'AssertEquals'?

12

Hier ist ein grundlegendes Beispiel, wie mein Komponententest mit qunit aussehen muss:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>

<link rel="stylesheet" href="qunit/qunit-1.13.0.css">
<script src = "qunit/qunit-1.13.0.js"></script>
<script src = "../js/fuzzQuery.js"></script>

<script>

test("Fuzz Query Basics", function()
        {
            equal(fuzzQuery("name:(John Smith)"), "name:(John~ Smith~)");
            equal(fuzzQuery("name:Jon~0.1"), "name:Jon~0.1");
            equal(fuzzQuery("Jon"), "Jon~");
            //etc

        }
    );

</script>
</head>
<body>
    <div id="qunit"></div>
</body>
</html>

Jetzt dachte ich, das ist ein bisschen repetitiv.

Könnte alle Ein- / Ausgänge in ein Array setzen und durchschleifen.

test("Fuzz Query Basics", function()
        {
            var equals = [
                           ["name:(John Smith)", "name:(John~ Smith~)"],
                           ["name:Jon~0.1", "name:Jon~0.1"],
                           ["Jon", "Jon~"]
                           ];

            for (var i = 0; i<equals.length; i++)
                {
                    equal(fuzzQuery(equals[i][0]), equals[i][1]);               
                }

        }
    );

Und das funktioniert gut.

Der einzige Vorteil, den ich mir für diese zweite Methode vorstellen kann, ist, dass equales einfacher ist, diese Änderung an einer Stelle vorzunehmen, wenn sich herausstellt, dass Sie sie nicht wirklich verwenden möchten .

In Bezug auf die Lesbarkeit denke ich nicht, dass es so oder so schlüssig ist, obwohl ich wahrscheinlich die zweite bevorzuge.

Wenn Sie es weiter abstrahieren, können Sie die Eingabe- / Ausgabefälle in einer separaten CSV-Datei ablegen, was die Änderung möglicherweise vereinfacht.

Die Frage ist: Wie lauten die allgemeinen Konventionen für das Schreiben solcher Komponententests?

Gibt es einen Grund, warum Sie sie nicht in Arrays einordnen sollten?

Dwjohnston
quelle
Wird Ihnen einer dieser Werte mitteilen, welcher Wert fehlgeschlagen ist?
JeffO
1
@JeffO - Ja - Mit QUnit - Wenn ein Test fehlschlägt, zeigt der Ausgang den erwarteten Wert und den tatsächlichen Wert an.
Dwjohnston

Antworten:

8

Ihre überarbeiteten Tests haben einen Geruch: Bedingte Testlogik .

Es gibt zwei Gründe, warum Sie es vermeiden sollten, in Ihren Tests bedingte Logik zu schreiben. Das erste ist, dass es Ihre Fähigkeit untergräbt, sicher zu sein, dass Ihr Testcode korrekt ist, wie im Artikel über verknüpfte xUnit-Muster beschrieben.

Das zweite ist, dass es die Bedeutung der Tests verschleiert. Wir schreiben Testmethoden, weil sie die Logik zum Testen eines bestimmten Verhaltens an einer Stelle platzieren und es uns ermöglichen, ihm einen beschreibenden Namen zu geben (siehe Dan Norths Original-BDD-Artikel für eine Untersuchung des Werts guter Namen für Tests). Wenn Ihre Tests in einer einzelnen Funktion mit einer forSchleife versteckt sind , wird die Bedeutung des Codes für den Leser verdeckt. Der Leser muss nicht nur die Schleife verstehen, sondern auch alle unterschiedlichen Verhaltensweisen, die innerhalb der Schleife getestet werden, mental aufdecken.

Die Lösung besteht wie immer darin, eine Abstraktionsebene nach oben zu bringen. Verwenden Sie ein Testframework, mit dem Sie parametrisierte Tests durchführen können , wie dies xUnit.NET oder Contexts tun (Haftungsausschluss: Ich habe Contexts geschrieben). Auf diese Weise können Sie Triangulationstests für dasselbe Verhalten auf natürliche Weise in Gruppen zusammenfassen und Tests für unterschiedliche Verhalten voneinander trennen.

Benjamin Hodgson
quelle
Übrigens eine gute Frage
Benjamin Hodgson
1
1) Wenn Sie eine Abstraktionsebene aufsteigen, verbergen Sie dann nicht genau die Details, von denen Sie sagten, dass sie durch die for-Schleife verdeckt werden? 2) nicht sicher, ob parametrisierte Tests hier anwendbar sind. Es scheint, als gäbe es hier irgendwo Parallelen, aber ich hatte viele Situationen ähnlich wie bei OPs, in denen ich einen Datensatz von 10-20 Werten hatte und einfach alle durch SUT laufen lassen wollte. Ja, jeder Wert ist anders und testet möglicherweise verschiedene Grenzwerte, aber es scheint tatsächlich so, als ob Testnamen für jeden einzelnen Wert "erfunden" würden, was ein Overkill wäre. Ich fand ein optimales Verhältnis zwischen Wert und Codegröße bei der Verwendung ähnlicher ...
DXM
... Schleifen. Solange der Test fehlschlägt und die Zusicherung genau das druckt, was fehlgeschlagen ist, hat der Entwickler genügend Feedback, um das Problem genau zu lokalisieren.
DXM
@DXM 1) Das Testframework bietet die parametrierte Testfunktionalität. Wir vertrauen dem Testframework implizit, sodass wir keine Tests dafür schreiben. 2) Parametrisierte Tests dienen genau diesem Zweck: Sie führen jedes Mal genau die gleichen Schritte aus, jedoch mit unterschiedlichen Ein- / Ausgabewerten. Das Testframework erspart Ihnen das Schreiben von Namen für jeden einzelnen, indem Sie die verschiedenen Eingaben mit derselben Testmethode ausführen.
Benjamin Hodgson
5

Anscheinend möchten Sie wirklich einen datengesteuerten Unit-Test. Da Sie die Verwendung von QUnit erwähnt haben, habe ich ein Plugin gefunden, das parametrisierte Tests ermöglicht:

https://github.com/AStepaniuk/qunit-parameterize

An einem datengetriebenen Test ist ideologisch nichts auszusetzen, solange der Testcode selbst nicht bedingt ist. Wenn Sie Ihren Testcode betrachten, scheint er ein sehr guter Kandidat für einen datengesteuerten Test zu sein.

Beispielcode für die GitHub README:

QUnit
    .cases([
        { a : 2, b : 2, expectedSum : 4 },
        { a : 5, b : 5, expectedSum : 10 },
        { a : 40, b : 2, expectedSum : 42 }
    ])
    .test("Sum test", function(params) {
        var actualSum = sum(params.a, params.b);
        equal(actualSum, params.expectedSum);
    });
Greg Burghardt
quelle
1
Einverstanden, es sieht aus wie ein datengetriebener Test. Aber anscheinend hat er das bereits in seinem zweiten Codebeispiel.
Robert Harvey
1
@RobertHarvey - Richtig. Es gibt eine akzeptierte Bezeichnung für das, was er erreichen möchte, und es gibt ein Plugin für das Testframework, das verwendet wird, um das Schreiben solcher Tests zu vereinfachen. Ich dachte, es lohnt sich, auf eine Antwort für die Zukunft hinzuweisen, das ist alles.
Greg Burghardt
1

Sie wiederholen sich weniger, indem Sie das Array verwenden, das besser gewartet werden kann. Ein Ansatz, den ich gerne verwende, ist eine separate Methode, die die Tests anordnet, durchführt und bestätigt, aber die Eingabeparameter akzeptiert, mit denen ich teste, sodass ich 1 Testmethode pro Eingabesatz habe.

Auf diese Weise kann ich sofort feststellen, welche Tests / Eingaben fehlschlagen.

Kevin
quelle
0

Ich mag Ihren zweiten Ansatz, aber ich würde 2 Punkte hinzufügen

  • Verwenden Sie keine Arrays zum Speichern getesteter Daten, da die Arbeit mit Indizes nicht sauber ist
  • Verwenden Sie keine forSchleifen

`

[
    {
        process: "name:(John Smith)",
        result: "name:(John~ Smith~)"
    },
    {
        process: "name:Jon~0.1", 
        result: "name:Jon~0.1"
    },
    {
        process: "Jon", 
        result: "Jon~"
    }
]
.forEach(function(data){

    var result = fuzzQuery(data.process);
    equal(result, data.result);
});

Ich bin mir bei qunit nicht sicher, aber ein guter Testläufer zeigt Ihnen, welche Eingabezeichenfolge fehlgeschlagen ist und was das erwartete Ergebnis war

tenbits
quelle