Wie funktioniert der Unit-Test?

23

Ich versuche, meinen Code robuster zu machen, und habe über Unit-Tests gelesen, finde es aber sehr schwierig, eine wirklich nützliche Verwendung zu finden. Zum Beispiel das Wikipedia-Beispiel :

public class TestAdder {
    public void testSum() {
        Adder adder = new AdderImpl();
        assert(adder.add(1, 1) == 2);
        assert(adder.add(1, 2) == 3);
        assert(adder.add(2, 2) == 4);
        assert(adder.add(0, 0) == 0);
        assert(adder.add(-1, -2) == -3);
        assert(adder.add(-1, 1) == 0);
        assert(adder.add(1234, 988) == 2222);
    }
}

Ich bin der Meinung, dass dieser Test völlig nutzlos ist, da Sie das gewünschte Ergebnis manuell berechnen und testen müssen. Ich bin der Meinung, dass hier ein besserer Einheitentest wäre

assert(adder.add(a, b) == (a+b));

dann codiert dies aber nur die Funktion selbst im Test. Kann mir jemand ein Beispiel geben, bei dem Unit-Tests tatsächlich nützlich sind? Zu Ihrer Information: Ich codiere derzeit hauptsächlich "prozedurale" Funktionen, die ~ 10 Boolesche Werte und ein paar Ints benötigen und mir ein int-Ergebnis liefern. Ich glaube, der einzige Unit-Test, den ich durchführen könnte, wäre, den Algorithmus in der einfach neu zu codieren Prüfung. edit: ich hätte auch präzisieren sollen das ist beim portieren von ruby ​​code (möglicherweise schlecht designt) (den ich nicht gemacht habe)

Lezebulon
quelle
14
How does unit testing work?Niemand weiß es wirklich :)
yannis
30
msgstr "Sie müssen das gewünschte Ergebnis manuell berechnen". Wie ist das "völlig nutzlos"? Wie können Sie sonst sicher sein, dass die Antwort richtig ist?
S.Lott
9
@ S.Lott: Es heißt Fortschritt. In der Antike verwendeten die Leute Computer, um Zahlen zu knabbern und Zeit zu sparen. In der heutigen Zeit verbrachten sie Zeit damit, sicherzustellen, dass Computer Zahlen knabbern können: D
Coder
2
@Coder: Der Zweck der Unit-Tests ist nicht "Zahlen
Andres F.
7
@lezebulon: Das Beispiel aus Wikipedia ist nicht sehr gut, aber das ist ein Problem mit diesem speziellen Testfall, nicht mit Unit-Tests im Allgemeinen. Ungefähr die Hälfte der Testdaten aus dem Beispiel fügt nichts Neues hinzu, was es überflüssig macht (ich fürchte, zu überlegen, was der Autor dieses Tests mit komplexeren Szenarien tun würde). Ein aussagekräftigerer Test würde die Testdaten in mindestens die folgenden Szenarien unterteilen: "Kann er negative Zahlen hinzufügen?", "Ist Null neutral?", "Kann er eine negative und eine positive Zahl hinzufügen?".
Andres F.

Antworten:

26

Unit-Tests, wenn Sie ausreichend kleine Einheiten testen, stellen immer das offensichtliche heraus.

Der Grund, warum add(x, y)sogar von einem Komponententest die Rede ist, ist, dass irgendwann später jemand addeinen speziellen Steuerlogik-Code eingibt, der nicht erkennt, dass add überall verwendet wird.

Bei Unit-Tests geht es vor allem um das Assoziationsprinzip: Wenn A B macht und B C macht, dann macht A C. "A macht C" ist ein Test auf höherer Ebene. Betrachten Sie beispielsweise den folgenden völlig legitimen Geschäftscode:

public void LoginUser (string username, string password) {
    var user = db.FetchUser (username);

    if (user.Password != password)
        throw new Exception ("invalid password");

    var roles = db.FetchRoles (user);

    if (! roles.Contains ("member"))
        throw new Exception ("not a member");

    Session["user"] = user;
}

Auf den ersten Blick sieht dies nach einer großartigen Methode zum Komponententest aus, da es einen sehr klaren Zweck hat. Es macht jedoch ungefähr 5 verschiedene Dinge. Jedes Ding hat einen gültigen und einen ungültigen Fall und wird eine große Anzahl von Unit-Tests durchführen. Im Idealfall wird dies weiter aufgeschlüsselt:

public void LoginUser (string username, string password) {

    var user = _userRepo.FetchValidUser (username, password);

    _rolesRepo.CheckUserForRole (user, "member");

    _localStorage.StoreValue ("user", user);
}

Jetzt sind wir bei den Einheiten. Ein Unit Test kümmert sich nicht darum, wofür ein _userRepogültiges Verhalten gilt, sondern nur darum FetchValidUser, dass es aufgerufen wird. Sie können einen anderen Test verwenden, um genau zu überprüfen, was ein gültiger Benutzer darstellt. Ähnliches gilt für CheckUserForRole... Sie haben Ihren Test von der Kenntnis der Rollenstruktur entkoppelt. Sie haben auch Ihr gesamtes Programm von der strengen Bindung abgekoppelt Session. Ich stelle mir vor, dass alle fehlenden Teile hier so aussehen würden:

class UserRepository : IUserRepository
{
    public User FetchValidUser (string username, string password)
    {
        var user = db.FetchUser (username);

        if (user.Password != password)
            throw new Exception ("invalid password");

        return user;
    }
}

class RoleRepository : IRoleRepository
{
    public void CheckUserForRole (User user, string role)
    {
        var roles = db.FetchRoles (user);

        if (! roles.Contains (role))
            throw new Exception ("not a member");
    }
}

class SessionStorage : ILocalStorage
{
    public void StoreValue (string key, object value)
    {
        Session[key] = value;
    }
}

Durch das Refactoring haben Sie mehrere Dinge gleichzeitig erreicht. Das Programm unterstützt Sie viel mehr dabei, zugrunde liegende Strukturen herauszureißen (Sie können die Datenbankschicht für NoSQL aufheben) oder nahtlos Sperren hinzuzufügen, wenn Sie feststellen, dass diese Sessionnicht threadsicher sind oder was auch immer. Sie haben sich jetzt auch sehr direkte Tests gegeben, um für diese drei Abhängigkeiten zu schreiben.

Hoffe das hilft :)

Bryan Boettcher
quelle
13

Momentan codiere ich hauptsächlich "prozedurale" Funktionen, die ~ 10 Boolesche Werte und ein paar Ints benötigen und mir ein int-Ergebnis liefern. Ich glaube, der einzige Unit-Test, den ich durchführen könnte, wäre, den Algorithmus im Test einfach neu zu codieren

Ich bin mir ziemlich sicher, dass jede Ihrer prozeduralen Funktionen deterministisch ist. Daher gibt sie für jeden gegebenen Satz von Eingabewerten ein bestimmtes int-Ergebnis zurück. Im Idealfall hätten Sie eine Funktionsspezifikation, anhand der Sie herausfinden können, welches Ergebnis Sie für bestimmte Mengen von Eingabewerten erhalten sollten. In Ermangelung dessen können Sie den Ruby-Code (der vermutlich ordnungsgemäß funktioniert) für bestimmte Sätze von Eingabewerten ausführen und die Ergebnisse aufzeichnen. Dann müssen Sie die Ergebnisse in Ihren Test HARTCODIEREN. Der Test soll ein Beweis dafür sein, dass Ihr Code tatsächlich Ergebnisse liefert, von denen bekannt ist, dass sie korrekt sind .

Mike Nakis
quelle
+1 zum Ausführen des vorhandenen Codes und Aufzeichnen der Ergebnisse. In dieser Situation ist das wahrscheinlich der pragmatische Ansatz.
MarkJ
12

Da sonst niemand ein konkretes Beispiel geliefert zu haben scheint:

    public void testRoman() {
        RomanNumeral numeral = new RomanNumeral();
        assert( numeral.toRoman(1) == "I" )
        assert( numeral.toRoman(4) == "IV" )
        assert( numeral.toRoman(5) == "V" )
        assert( numeral.toRoman(9) == "IX" )
        assert( numeral.toRoman(10) == "X" )
    }
    public void testSqrt() {
        assert( sqrt(4) == 2 )
        assert( sqrt(9) == 3 )
    }

Du sagst:

Ich halte diesen Test für völlig nutzlos, da Sie das gewünschte Ergebnis manuell berechnen und testen müssen

Der Punkt ist jedoch, dass Sie beim Ausführen der manuellen Berechnungen mit sehr viel geringerer Wahrscheinlichkeit einen Fehler machen (oder zumindest eher Ihre Fehler bemerken) als beim Codieren.

Wie wahrscheinlich ist es, dass Sie einen Fehler in Ihrem Umrechnungscode von Dezimal zu Roman machen? Sehr wahrscheinlich. Wie wahrscheinlich ist es, dass Sie einen Fehler machen, wenn Sie Dezimalzahlen von Hand in römische Ziffern konvertieren? Nicht sehr wahrscheinlich. Deshalb testen wir gegen manuelle Berechnungen.

Wie wahrscheinlich ist es, dass Sie bei der Implementierung einer Quadratwurzelfunktion einen Fehler machen? Sehr wahrscheinlich. Wie wahrscheinlich ist es, dass Sie einen Fehler machen, wenn Sie eine Quadratwurzel von Hand berechnen? Wahrscheinlich wahrscheinlicher. Mit sqrt können Sie jedoch einen Taschenrechner verwenden, um die Antworten zu erhalten.

Zu Ihrer Information: Ich programmiere derzeit hauptsächlich "prozedurale" Funktionen, die ~ 10 Boolesche Werte und ein paar Ints benötigen und mir ein int-Ergebnis liefern. Ich glaube, der einzige Unit-Test, den ich durchführen könnte, wäre, den Algorithmus in der einfach neu zu codieren Prüfung

Also werde ich darüber spekulieren, was hier passiert. Ihre Funktionen sind etwas kompliziert, daher ist es schwierig, aus den Eingaben herauszufinden, wie die Ausgabe aussehen soll. Um dies zu tun, müssen Sie die Funktion (in Ihrem Kopf) manuell ausführen, um herauszufinden, was die Ausgabe ist. Verständlicherweise scheint das irgendwie nutzlos und fehleranfällig zu sein.

Der Schlüssel ist, dass Sie die richtigen Ausgänge finden möchten. Aber Sie müssen diese Ausgaben gegen etwas testen, von dem bekannt ist, dass es korrekt ist. Es ist nicht gut, einen eigenen Algorithmus zu schreiben, um dies zu berechnen, da dies möglicherweise falsch ist. In diesem Fall ist es zu schwierig, die Werte manuell zu berechnen.

Ich würde zum Ruby-Code zurückkehren und diese ursprünglichen Funktionen mit verschiedenen Parametern ausführen. Ich würde die Ergebnisse des Ruby-Codes nehmen und diese in den Unit-Test einfließen lassen. Auf diese Weise müssen Sie die manuelle Berechnung nicht durchführen. Aber Sie testen gegen den Originalcode. Das sollte helfen, die Ergebnisse gleich zu halten, aber wenn es Fehler im Original gibt, hilft es Ihnen nicht. Grundsätzlich können Sie den Originalcode wie den Taschenrechner im sqrt-Beispiel behandeln.

Wenn Sie den tatsächlichen Code angegeben haben, den Sie portieren, können Sie detailliertere Rückmeldungen zur Behebung des Problems erhalten.

Winston Ewert
quelle
Und wenn der Ruby-Code einen Fehler enthält, von dem Sie nicht wissen, dass er nicht in Ihrem neuen Code enthalten ist, und Ihr Code einen auf den Ruby-Ausgaben basierenden Komponententest nicht besteht, wird die Untersuchung, warum er fehlgeschlagen ist, Sie letztendlich rechtfertigen und zu dem führen Es wurde ein latenter Ruby-Bug gefunden. Das ist ziemlich cool.
Adam Wuerl
11

Ich glaube, der einzige Unit-Test, den ich durchführen könnte, wäre, den Algorithmus im Test einfach neu zu codieren

Sie sind für eine so einfache Klasse fast richtig.

Versuchen Sie es für einen komplexeren Rechner. Wie ein Bowling-Score-Rechner.

Der Wert von Komponententests ist leichter zu erkennen, wenn Sie komplexere "Geschäfts" -Regeln mit verschiedenen zu testenden Szenarien haben.

Ich sage nicht, dass Sie einen Lauf des Taschenrechners nicht testen sollten (Gibt Ihr Taschenrechnerkonto Werte wie 1/3 aus, die nicht dargestellt werden können? Was macht es mit der Division durch Null?), Aber Sie werden das sehen Wert deutlicher, wenn Sie etwas mit mehr Zweigen testen, um die Abdeckung zu erhalten.

brian
quelle
4
+1 für die Feststellung wird es für komplizierte Funktionen nützlicher. Was ist, wenn Sie sich entschieden haben, adder.add () auf Gleitkommawerte zu erweitern? Matrizen? Leger Kontowerte?
Joshin4colours
6

Trotz religiöser Zeloterie über 100% Code-Abdeckung, werde ich sagen, dass nicht jede Methode einheitlich getestet werden sollte. Nur Funktionalität, die aussagekräftige Geschäftslogik enthält. Eine Funktion, die einfach eine Zahl hinzufügt, ist nicht zu testen.

Ich programmiere derzeit hauptsächlich "prozedurale" Funktionen, die ~ 10 Boolesche Werte und ein paar Ints enthalten und mir ein darauf basierendes int-Ergebnis liefern

Da ist genau das Richtige für Sie. Wenn Unit-Tests unnatürlich schwierig oder sinnlos erscheinen, liegt dies wahrscheinlich an einem Konstruktionsfehler. Wenn es objektorientierter wäre, wären Ihre Methodensignaturen nicht so umfangreich und es gäbe weniger mögliche Eingaben zum Testen.

Ich muss nicht in mein OO gehen, ist der prozeduralen Programmierung überlegen ...

maple_shaft
quelle
In diesem Fall ist die "Signatur" der Methode nicht massiv. Ich habe nur aus einem std :: vector <bool> gelesen, der ein Klassenmitglied ist. Ich hätte auch genau sagen sollen, dass ich Ruby-Code portiere (möglicherweise schlecht designt) (den ich nicht gemacht habe)
lezebulon
2
@lezebulon Unabhängig davon, ob es so viele mögliche Eingaben für diese einzelne Methode gibt, die akzeptiert werden können, tut diese Methode zu viel .
maple_shaft
3

Meiner Meinung nach sind Unit-Tests in Ihrer kleinen Addierer-Klasse sogar nützlich: Denken Sie nicht daran, den Algorithmus neu zu "codieren", sondern stellen Sie sich eine Black Box vor, deren einziges Wissen das Funktionsverhalten ist (sofern Sie damit vertraut sind) Mit der schnellen Multiplikation kennen Sie einige schnellere, aber komplexere Versuche als die Verwendung von "a * b" und Ihrer öffentlichen Schnittstelle. Dann sollten Sie sich fragen: "Was zum Teufel könnte schief gehen?" ...

In den meisten Fällen geschieht dies an der Grenze (ich sehe, dass Sie bereits diese Muster hinzufügen ++, -, + -, 00 - Mal, um diese durch - +, 0+, 0-, +0, -0 zu vervollständigen). Denken Sie daran, was bei MAX_INT und MIN_INT passiert, wenn Sie dort Negative addieren oder subtrahieren (addieren;)). Oder stellen Sie sicher, dass Ihre Tests ziemlich genau aussehen, was bei und um Null passiert.

Alles in allem ist das Geheimnis für einfache Klassen sehr einfach (vielleicht auch für komplexere;)): Denken Sie über die Verträge Ihrer Klasse nach (siehe Vertragsgestaltung) und testen Sie sie dann gegen sie. Je besser Sie Ihre inv's, pre's und post's kennen, desto "vollständiger" werden Ihre Tests sein.

Tipp für Ihre Testklassen: Versuchen Sie, nur eine Aussage in eine Methode zu schreiben. Geben Sie den Methoden gute Namen (z. B. "testAddingToMaxInt", "testAddingTwoNegatives"), um das beste Feedback zu erhalten, wenn Ihr Test nach einer Codeänderung fehlschlägt.

Sebastian Bauer
quelle
2

Anstatt auf einen manuell berechneten Rückgabewert zu testen oder die Logik im Test zu duplizieren, um den erwarteten Rückgabewert zu berechnen, testen Sie den Rückgabewert für eine erwartete Eigenschaft.

Wenn Sie beispielsweise eine Methode testen möchten, die eine Matrix invertiert, und Ihren Eingabewert nicht manuell invertieren möchten, müssen Sie den Rückgabewert mit der Eingabe multiplizieren und sicherstellen, dass Sie die Identitätsmatrix erhalten.

Um diesen Ansatz auf Ihre Methode anzuwenden, müssen Sie deren Zweck und Semantik berücksichtigen, um zu ermitteln, welche Eigenschaften der Rückgabewert relativ zu den Eingaben hat.

JGWeissman
quelle
2

Unit-Tests sind ein Produktivitätswerkzeug. Sie erhalten eine Änderungsanforderung, implementieren sie und führen dann Ihren Code durch das Unit-Test-Gambit. Dieses automatisierte Testen spart Zeit.

I feel that this test is totally useless, because you are required to manually compute the wanted result and test it, I feel like a better unit test here would be

Ein strittiger Punkt. Der Test im Beispiel zeigt nur, wie eine Klasse instanziiert und durch eine Reihe von Tests ausgeführt wird. Die Konzentration auf die Minutien einer einzelnen Implementierung lässt den Wald vor lauter Bäumen vermissen.

Can someone provide me with an example where unit testing is actually useful?

Sie haben eine Employee-Entität. Die Entität enthält einen Namen und eine Adresse. Der Client beschließt, ein ReportsTo-Feld hinzuzufügen.

void TestBusinessLayer()
{
   int employeeID = 1234
   Employee employee = Employee.GetEmployee(employeeID)
   BusinessLayer bl = new BusinessLayer()
   Assert.isTrue(bl.Add(employee))//assume Add returns true on pass
}

Das ist ein grundlegender Test der BL für die Arbeit mit einem Mitarbeiter. Der Code besteht die soeben vorgenommene Schemaänderung. Denken Sie daran, dass der Test nicht nur Aussagen enthält. Das Durchlaufen des Codes stellt außerdem sicher, dass keine Ausnahmen auftreten.

Mit der Zeit erleichtern die vorhandenen Tests das Vornehmen von Änderungen im Allgemeinen. Der Code wird automatisch auf Ausnahmen und auf Assertions überprüft, die Sie machen. Dies vermeidet einen Großteil des Overheads, der durch manuelles Testen durch eine QS-Gruppe entsteht. Während die Automatisierung der Benutzeroberfläche noch recht schwierig ist, sind die anderen Ebenen im Allgemeinen sehr einfach, vorausgesetzt, Sie verwenden Zugriffsmodifikatoren korrekt.

I feel like the only unit testing I could do would be to simply re-code the algorithm in the test.

Auch prozedurale Logik lässt sich leicht in eine Funktion einbetten. Verkapseln, instanziieren und übergeben Sie das zu testende int / primitive Objekt (oder Scheinobjekt). Kopieren Sie den Code nicht und fügen Sie ihn nicht in einen Komponententest ein. Das besiegt DRY. Außerdem wird der Test komplett zunichte gemacht, da Sie nicht den Code testen, sondern eine Kopie des Codes. Wenn sich der zu testende Code ändert, besteht der Test trotzdem!

P. Brian Mackey
quelle
<pedantry> "gamut", nicht "gambit". </
pedantry
@chao lol lernen jeden Tag etwas Neues.
P.Brian.Mackey
2

Nehmen Sie Ihr Beispiel (mit ein bisschen Refactoring),

assert(a + b, math.add(a, b));

hilft nicht

  • verstehen, wie math.addsich intern verhält,
  • wissen, was mit Randfällen passieren wird.

Es ist so ziemlich so, als würde man sagen:

  • Wenn Sie wissen möchten, was die Methode bewirkt, sehen Sie sich die Hunderte von Zeilen Quellcode selbst an (denn ja, es math.add können Hunderte von LOC enthalten sein; siehe unten).
  • Ich muss nicht wissen, ob die Methode richtig funktioniert. Es ist in Ordnung, wenn sowohl die erwarteten als auch die tatsächlichen Werte von den Erwartungen abweichen .

Dies bedeutet auch, dass Sie keine Tests wie die folgenden hinzufügen müssen:

assert(3, math.add(1, 2));
assert(4, math.add(2, 2));

Sie helfen auch nicht, oder zumindest, wenn Sie die erste Behauptung aufgestellt haben, bringt die zweite nichts Sinnvolles.

Was ist stattdessen mit:

const numeric Pi = 3.1415926535897932384626433832795;
const numeric Expected = 4.1415926535897932384626433832795;
assert(Expected, math.add(Pi, 1),
    "Adding an integer to a long numeric doesn't give a long numeric result.");
assert(Expected, math.add(1, Pi),
    "Adding a long numeric to an integer doesn't give a long numeric result.");

Dies ist selbsterklärend und sowohl für Sie als auch für die Person, die den Quellcode später pflegen wird, verdammt hilfreich.Stellen Sie sich vor, diese Person nimmt eine geringfügige Änderung an der vor math.add, um den Code zu vereinfachen und die Leistung zu optimieren, und sieht das Testergebnis wie folgt aus :

Test TestNumeric() failed on assertion 2, line 5: Adding a long numeric to an
integer doesn't give a long numeric result.

Expected value: 4.1415926535897932384626433832795
Actual value: 4

Diese Person wird sofort verstehen, dass die neu geänderte Methode von der Reihenfolge der Argumente abhängt: Wenn das erste Argument eine Ganzzahl und das zweite eine lange Zahl ist, ist das Ergebnis eine Ganzzahl, während eine lange Zahl erwartet wurde.

Auf die gleiche Weise erhält man den tatsächlichen Wert von 4.141592 ist es selbsterklärend der ersten Behauptung zu ermitteln: Sie wissen, dass die Methode eine große Präzision aufweisen muss , aber tatsächlich scheitert sie.

Aus dem gleichen Grund können zwei der folgenden Aussagen in einigen Sprachen sinnvoll sein:

// We don't expect a concatenation. `math` library is not intended for this.
assert(0, math.add("Hello", "World"));

// We expect the method to convert every string as if it was a decimal.
assert(5, math.add("0x2F", 5));

Und was ist mit:

assert(numeric.Infinity, math.add(numeric.Infinity, 1));

Auch selbsterklärend: Sie möchten, dass Ihre Methode mit der Unendlichkeit richtig umgehen kann. Gehen Unendlichkeit hinauszugehen oder eine Ausnahme auszulösen, ist kein erwartetes Verhalten.

Oder macht das je nach Sprache mehr Sinn?

/**
 * Ensures that when adding numbers which exceed the maximum value, the method
 * fails with OverflowException, instead of restarting at numeric.Minimum + 1.
 */
TestOverflow()
{
    UnitTest.ExpectException(ofType(OverflowException));

    numeric result = math.add(numeric.Maximum, 1));

    UnitTest.Fail("The tested code succeeded, while an OverflowException was
        expected.");
}
Arseni Mourzenko
quelle
1

Für eine sehr einfache Funktion wie add kann das Testen als unnötig angesehen werden. Wenn Ihre Funktionen jedoch komplexer werden, wird immer offensichtlicher, warum ein Testen erforderlich ist.

Überlegen Sie, was Sie beim Programmieren tun (ohne Unit-Tests). Normalerweise schreiben Sie Code, führen ihn aus, stellen sicher, dass er funktioniert, und fahren mit dem nächsten Schritt fort, oder? Wenn Sie mehr Code schreiben, insbesondere in einem sehr großen System / GUI / einer sehr großen Website, müssen Sie immer mehr "Laufen und sehen, ob es funktioniert". Sie müssen dies und das versuchen. Dann nehmen Sie einige Änderungen vor und müssen dieselben Dinge noch einmal versuchen. Es wird sehr deutlich, dass Sie Zeit sparen könnten, indem Sie Komponententests schreiben, die den gesamten Teil "Laufen und sehen, ob es funktioniert" automatisieren würden.

Je größer Ihre Projekte werden, desto unrealistischer wird die Anzahl der Dinge, für die Sie "ausführen und prüfen müssen, ob es funktioniert". Sie müssen also nur ein paar wichtige Komponenten der Benutzeroberfläche / des Projekts ausführen und ausprobieren und dann hoffen, dass alles andere in Ordnung ist. Dies ist ein Rezept für eine Katastrophe. Natürlich können Sie als Mensch nicht wiederholt auf jede mögliche Situation testen, die Ihre Kunden möglicherweise verwenden, wenn die Benutzeroberfläche von buchstäblich Hunderten von Menschen verwendet wird. Wenn Sie Komponententests durchgeführt haben, können Sie den Test einfach ausführen, bevor Sie die stabile Version ausliefern, oder sogar bevor Sie eine Übergabe an das zentrale Repository vornehmen (sofern Ihr Arbeitsplatz eine verwendet). Und wenn es später noch Fehler gibt, können Sie einfach einen Komponententest hinzufügen, um dies in Zukunft zu überprüfen.

Asaf
quelle
1

Einer der Vorteile des Schreibens von Komponententests besteht darin, dass Sie damit robusteren Code schreiben können, indem Sie gezwungen werden, über Randfälle nachzudenken. Wie wäre es mit einem Test auf Randfälle wie Ganzzahlüberlauf, Dezimaltrunkierung oder Behandlung von Nullen für die Parameter?

Cliff
quelle
0

Vielleicht nehmen Sie an, dass add () mit der Anweisung ADD implementiert wurde. Wenn ein Junior-Programmierer oder Hardware-Ingenieur die Funktion add () mit ANDS / ORS / XORS neu implementiert hat, das Bit invertiert und verschiebt, möchten Sie es möglicherweise mit dem ADD-Befehl testen.

Wenn Sie die Eingeweide von add () oder des zu testenden Geräts durch eine Zufallszahl oder einen Ausgabegenerator ersetzen, woher wissen Sie im Allgemeinen, dass etwas kaputt ist? Kodieren Sie dieses Wissen in Ihren Unit-Tests. Wenn niemand erkennen kann, ob es kaputt ist, dann checken Sie einfach einen Code für rand () ein und gehen Sie nach Hause, Ihre Arbeit ist erledigt.

hotpaw2
quelle
0

Ich hätte es vielleicht in allen Antworten verpasst, aber für mich geht es beim Hauptanliegen von Unit Testing weniger darum, die Korrektheit einer Methode heute zu beweisen, sondern darum , dass es die fortgesetzte Korrektheit dieser Methode beweist, wenn [je] Sie sie ändern .

Nehmen Sie eine einfache Funktion, wie die Rückgabe der Anzahl der Artikel in einer Sammlung. Wenn Ihre Liste heutzutage auf einer internen Datenstruktur basiert, die Sie gut kennen, könnten Sie denken, dass diese Methode so schmerzhaft offensichtlich ist, dass Sie keinen Test dafür benötigen. In einigen Monaten oder Jahren entscheiden Sie (oder jemand anderes ), die interne Listenstruktur zu ersetzen . Sie müssen noch wissen, dass getCount () den richtigen Wert zurückgibt.

Hier kommen Ihre Unit Tests voll zur Geltung.

Sie können die interne Implementierung Ihres Codes ändern, aber für alle Benutzer dieses Codes bleibt das Ergebnis gleich.

Phill W.
quelle