Unit Testing Void Methoden?

170

Was ist der beste Weg, um eine Methode zu testen, die nichts zurückgibt? Speziell in c #.

Was ich wirklich zu testen versuche, ist eine Methode, die eine Protokolldatei nimmt und sie nach bestimmten Zeichenfolgen analysiert. Die Zeichenfolgen werden dann in eine Datenbank eingefügt. Nichts, was vorher noch nicht gemacht wurde, aber ich bin sehr neu bei TDD. Ich frage mich, ob es möglich ist, dies zu testen oder ob es etwas ist, das nicht wirklich getestet wird.

jdiaz
quelle
55
Bitte verwenden Sie nicht den Begriff "TDD", wenn Sie dies nicht tun. Sie führen Unit-Tests durch, nicht TDD. Wenn Sie TDD machen würden, hätten Sie nie eine Frage wie "Wie teste ich eine Methode?". Der Test würde zuerst existieren, und dann wäre die Frage: "Wie kann dieser Test bestanden werden?" Wenn Sie jedoch TDD ausführen, wird Ihr Code für den Test geschrieben (nicht umgekehrt), und Sie beantworten im Wesentlichen Ihre eigene Frage. Ihr Code würde aufgrund von TDD anders formatiert, und dieses Problem würde niemals auftreten. Nur klarstellen.
Suamere

Antworten:

151

Wenn eine Methode nichts zurückgibt, ist dies eine der folgenden

  • Imperativ - Sie fordern das Objekt entweder auf, sich selbst etwas anzutun. z. B. den Status ändern (ohne eine Bestätigung zu erwarten. Es wird davon ausgegangen, dass dies geschehen wird.)
  • Information - nur jemanden benachrichtigen, dass etwas passiert ist (ohne Maßnahmen oder Reaktionen zu erwarten).

Imperative Methoden - Sie können überprüfen, ob die Aufgabe tatsächlich ausgeführt wurde. Überprüfen Sie, ob tatsächlich eine Statusänderung stattgefunden hat. z.B

void DeductFromBalance( dAmount ) 

kann getestet werden, indem überprüft wird, ob der Kontostand nach dieser Nachricht tatsächlich unter dem Anfangswert von dAmount liegt

Informationsmethoden - sind als Mitglied der öffentlichen Schnittstelle des Objekts selten ... daher normalerweise nicht einheitlich getestet. Wenn Sie jedoch müssen, können Sie überprüfen, ob die Bearbeitung einer Benachrichtigung erfolgt. z.B

void OnAccountDebit( dAmount )  // emails account holder with info

kann getestet werden, indem überprüft wird, ob die E-Mail gesendet wird

Veröffentlichen Sie weitere Details zu Ihrer tatsächlichen Methode, und die Benutzer können besser antworten.
Update : Ihre Methode macht zwei Dinge. Ich würde es tatsächlich in zwei Methoden aufteilen, die jetzt unabhängig getestet werden können.

string[] ExamineLogFileForX( string sFileName );
void InsertStringsIntoDatabase( string[] );

String [] kann leicht überprüft werden, indem die erste Methode mit einer Dummy-Datei und erwarteten Strings versehen wird. Der zweite ist etwas knifflig. Sie können entweder einen Mock (Google oder Search Stackoverflow für Mocking Frameworks) verwenden, um die Datenbank nachzuahmen, oder die tatsächliche Datenbank treffen und überprüfen, ob die Zeichenfolgen an der richtigen Stelle eingefügt wurden. In diesem Thread finden Sie einige gute Bücher ... Ich würde Pragmatic Unit Testing empfehlen, wenn Sie in einer Krise stecken.
Im Code würde es wie verwendet werden

InsertStringsIntoDatabase( ExamineLogFileForX( "c:\OMG.log" ) );
Gishu
quelle
1
Hey Gishu, gute Antwort. Das Beispiel, das Sie gegeben haben ... sind das nicht mehr Integrationstests ...? und wenn ja, bleibt die Frage, wie man Void-Methoden wirklich testet ... vielleicht ist es unmöglich?
Andy
2
@andy - hängt von Ihrer Definition von 'Integrationstests' ab. Imperative Methoden ändern normalerweise den Status, sodass Sie durch einen Komponententest überprüft werden können, der den Status des Objekts abfragt. Informationsmethoden können durch einen Komponententest überprüft werden, bei dem ein Schein-Listener / Mitarbeiter angeschlossen wird, um sicherzustellen, dass die Testperson die richtige Benachrichtigung ausgibt. Ich denke, beide können durch Unit-Tests sinnvoll getestet werden.
Gishu
@undy Die Datenbank kann durch eine Accessor-Schnittstelle verspottet / getrennt werden, sodass die Aktion anhand der an das Scheinobjekt übergebenen Daten getestet werden kann.
Peter Geiger
62

Testen Sie die Nebenwirkungen. Das beinhaltet:

  • Wirft es irgendwelche Ausnahmen? (Wenn dies der Fall sein sollte, überprüfen Sie, ob dies der Fall ist. Wenn dies nicht der Fall sein sollte, versuchen Sie es mit einigen Eckfällen, die möglicherweise auftreten, wenn Sie nicht vorsichtig sind. Nullargumente sind die offensichtlichste Sache.)
  • Spielt es gut mit seinen Parametern? (Wenn sie veränderlich sind, mutiert es sie, wenn es nicht sollte und umgekehrt?)
  • Hat es die richtige Auswirkung auf den Status des Objekts / Typs, auf dem Sie es aufrufen?

Natürlich gibt es eine Grenze, wie viel Sie testen können. Sie können beispielsweise im Allgemeinen nicht mit jeder möglichen Eingabe testen. Testen Sie pragmatisch - genug, um Ihnen das Vertrauen zu geben, dass Ihr Code angemessen entworfen und korrekt implementiert ist, und genug, um als ergänzende Dokumentation für das zu dienen, was ein Anrufer erwarten könnte.

Jon Skeet
quelle
31

Wie immer: Testen Sie, was die Methode tun soll!

Sollte es irgendwo den globalen Zustand ändern (ähm, Codegeruch!)?

Sollte es eine Schnittstelle aufrufen?

Sollte es eine Ausnahme auslösen, wenn es mit den falschen Parametern aufgerufen wird?

Sollte es keine Ausnahme auslösen, wenn es mit den richtigen Parametern aufgerufen wird?

Sollte es ...?

David Schmitt
quelle
11

Void Return-Typen / Subroutinen sind alte Nachrichten. Ich habe seit 8 Jahren keinen Void-Rückgabetyp mehr erstellt (es sei denn, ich war extrem faul) (ab dem Zeitpunkt dieser Antwort, also nur ein bisschen bevor diese Frage gestellt wurde).

Anstelle einer Methode wie:

public void SendEmailToCustomer()

Erstellen Sie eine Methode, die dem Paradigma int.TryParse () von Microsoft folgt:

public bool TrySendEmailToCustomer()

Möglicherweise gibt es keine Informationen, die Ihre Methode zur langfristigen Verwendung zurückgeben muss, aber die Rückgabe des Status der Methode nach Ausführung ihrer Aufgabe ist für den Aufrufer eine enorme Verwendung.

Außerdem ist bool nicht der einzige Statustyp. Es gibt eine Reihe von Fällen, in denen eine zuvor erstellte Subroutine tatsächlich drei oder mehr verschiedene Zustände zurückgeben kann (Gut, Normal, Schlecht usw.). In diesen Fällen würden Sie nur verwenden

public StateEnum TrySendEmailToCustomer()

Während das Try-Paradigma diese Frage zum Testen einer ungültigen Rückgabe etwas beantwortet, gibt es auch andere Überlegungen. Während / nach einem "TDD" -Zyklus würden Sie beispielsweise "Refactoring" durchführen und feststellen, dass Sie mit Ihrer Methode zwei Dinge tun ... und damit das "Prinzip der Einzelverantwortung" brechen. Das sollte also zuerst erledigt werden. Zweitens haben Sie möglicherweise eine Abhängigkeit identifiziert ... Sie berühren "Persistente" Daten.

Wenn Sie den Datenzugriff in der betreffenden Methode ausführen, müssen Sie eine Umgestaltung in eine n-Tier- oder n-Layer-Architektur vornehmen. Wir können jedoch davon ausgehen, dass Sie mit "Die Zeichenfolgen werden dann in eine Datenbank eingefügt" tatsächlich eine Geschäftslogikschicht oder etwas anderes aufrufen. Ja, das nehmen wir an.

Wenn Ihr Objekt instanziiert wird, verstehen Sie jetzt, dass Ihr Objekt Abhängigkeiten aufweist. In diesem Fall müssen Sie entscheiden, ob Sie eine Abhängigkeitsinjektion für das Objekt oder für die Methode durchführen möchten. Das bedeutet, dass Ihr Konstruktor oder die betreffende Methode einen neuen Parameter benötigt:

public <Constructor/MethodName> (IBusinessDataEtc otherLayerOrTierObject, string[] stuffToInsert)

Jetzt, da Sie eine Schnittstelle Ihres Geschäfts- / Datenebenenobjekts akzeptieren können, können Sie diese während der Komponententests verspotten und haben keine Abhängigkeiten oder Angst vor "versehentlichen" Integrationstests.

In Ihrem Live-Code übergeben Sie also ein REAL- IBusinessDataEtcObjekt. Bei Ihrem Unit-Test übergeben Sie jedoch ein MOCK- IBusinessDataEtcObjekt. In diesem Mock können Sie Nicht-Schnittstelleneigenschaften wie int XMethodWasCalledCountoder etwas einschließen , dessen Status beim Aufruf der Schnittstellenmethoden aktualisiert werden.

Ihr Unit-Test geht also Ihre fraglichen Methoden durch, führt die jeweils vorhandene Logik aus und ruft eine oder zwei oder einen ausgewählten Satz von Methoden in Ihrem IBusinessDataEtcObjekt auf. Wenn Sie Ihre Aussagen am Ende Ihres Unit-Tests machen, müssen Sie jetzt einige Dinge testen.

  1. Der Zustand der "Subroutine", die jetzt eine Try-Paradigm-Methode ist.
  2. Der Status Ihres Mock- IBusinessDataEtcObjekts.

Weitere Informationen zu Ideen für die Abhängigkeitsinjektion auf Konstruktionsebene ... in Bezug auf Unit-Tests ... finden Sie in den Builder-Entwurfsmustern. Es fügt eine weitere Schnittstelle und Klasse für jede aktuelle Schnittstelle / Klasse hinzu, die Sie haben, aber sie sind sehr klein und bieten RIESIGE Funktionserweiterungen für bessere Unit-Tests.

Suamere
quelle
Der erste Teil dieser großartigen Antwort dient als erstaunlicher allgemeiner Rat für alle Anfänger / fortgeschrittenen Programmierer.
Pimbrouwers
sollte es nicht so etwas sein public void sendEmailToCustomer() throws UndeliveredMailException?
A. Emad
1
@ A.Emad Gute Frage, aber nein. Das Steuern des Codeflusses durch Auslösen von Ausnahmen ist eine seit langem bekannte schlechte Praxis. Bei voidMethoden, insbesondere in objektorientierten Sprachen, war dies jedoch die einzige echte Option. Die älteste Alternative von Microsoft ist das von mir diskutierte Try-Paradigma und Paradigmen im funktionalen Stil wie Monaden / Maybes. Auf diese Weise können Befehle (in CQS) weiterhin wertvolle Statusinformationen zurückgeben, anstatt sich auf das Werfen zu verlassen, das einem ähnlich ist GOTO(von dem wir wissen, dass es schlecht ist). Werfen (und gehen) sind langsam, schwer zu debuggen und keine gute Übung.
Suamere
Vielen Dank, dass Sie das geklärt haben. Ist es C # -spezifisch oder ist es im Allgemeinen eine schlechte Praxis, Ausnahmen auch in Sprachen wie Java und C ++ auszulösen?
A. Emad
9

Versuche dies:

[TestMethod]
public void TestSomething()
{
    try
    {
        YourMethodCall();
        Assert.IsTrue(true);
    }
    catch {
        Assert.IsTrue(false);
    }
}
Nathan Alard
quelle
1
Dies sollte nicht notwendig sein, kann aber als solches erfolgen
Nathan Alard
Willkommen bei StackOverflow! Bitte erwägen Sie, Ihrem Code eine Erklärung hinzuzufügen. Danke dir.
Aurasphere
2
Das ExpectedAttributesoll diesen Test deutlicher machen.
Martin Liversage
8

Sie können es sogar so versuchen:

[TestMethod]
public void ReadFiles()
{
    try
    {
        Read();
        return; // indicates success
    }
    catch (Exception ex)
    {
        Assert.Fail(ex.Message);
    }
}
Reyan Chougle
quelle
1
Dies ist wohl der einfachste Weg.
Navin Pandit
5

Dies hat Auswirkungen auf ein Objekt .... Abfrage des Ergebnisses des Effekts. Wenn es keine sichtbare Wirkung hat, lohnt es sich nicht, es zu testen!

Keith Nicholas
quelle
4

Vermutlich macht die Methode etwas und kehrt nicht einfach zurück?

Angenommen, dies ist der Fall, dann:

  1. Wenn der Status des Eigentümerobjekts geändert wird, sollten Sie testen, ob sich der Status korrekt geändert hat.
  2. Wenn ein Objekt als Parameter verwendet und dieses Objekt geändert wird, sollten Sie testen, ob das Objekt korrekt geändert wurde.
  3. Wenn in bestimmten Fällen Ausnahmen ausgelöst werden, testen Sie, ob diese Ausnahmen korrekt ausgelöst werden.
  4. Wenn sich das Verhalten je nach Status des eigenen Objekts oder eines anderen Objekts ändert, stellen Sie den Status ein und testen Sie, ob die Methode über eine der drei oben genannten Testmethoden korrekt ist.

Wenn Sie uns mitteilen, was die Methode bewirkt, könnte ich genauer sein.

David Arno
quelle
3

Verwenden Sie Rhino Mocks , um festzulegen , welche Aufrufe, Aktionen und Ausnahmen zu erwarten sind. Angenommen, Sie können Teile Ihrer Methode verspotten oder ausstechen. Schwer zu wissen, ohne hier einige Einzelheiten über die Methode oder sogar den Kontext zu kennen.

Taube
quelle
1
Die Antwort auf den Unit-Test sollte niemals darin bestehen, ein Drittanbieter-Tool zu erwerben, das dies für Sie erledigt. Wenn eine Person weiß, wie man Unit-Tests durchführt, ist es akzeptabel, ein Drittanbieter-Tool zu verwenden, um dies zu vereinfachen.
Suamere
2

Kommt darauf an, was es macht. Wenn es Parameter enthält, übergeben Sie Mocks, die Sie später fragen können, ob sie mit dem richtigen Parametersatz aufgerufen wurden.

André
quelle
Einverstanden - die Überprüfung des Verhaltens der Mocks, die die Methode testen, wäre eine Möglichkeit.
Jeff Schumacher
0

Welche Instanz auch immer Sie verwenden, um die void-Methode aufzurufen, Sie können einfach Folgendes verwenden:Verfiy

Beispielsweise:

In meinem Fall _Logist es die Instanz und LogMessagedie zu testende Methode:

try
{
    this._log.Verify(x => x.LogMessage(Logger.WillisLogLevel.Info, Logger.WillisLogger.Usage, "Created the Student with name as"), "Failure");
}
Catch 
{
    Assert.IsFalse(ex is Moq.MockException);
}

Ist das VerifyAuslösen eine Ausnahme aufgrund eines Fehlers der Methode, bei der der Test fehlschlagen würde?

Shreya Kesharkar
quelle