2013-12-20 6 views
7

Ich habe den Gerätetest für einige Zeit vernachlässigt. Ich habe Unit-Tests geschrieben, aber sie waren ziemlich schlecht. Ich lese gerade durch "The Art of Unit Testing", um mich selbst auf den neuesten Stand zu bringen.Gerätetest mit zwei Funktionen

Wenn ich eine Schnittstelle haben, wie:

public interface INotificationService 
{ 
    void AddError(string _error); 
    void AddIssue(string _issue); 

    IEnumerable<string> FetchErrors(); 
    IEnumerable<string> FetchIssues(); 
} 

Eine konkrete Implementierung dieser Schnittstelle enthält:

private readonly ICollection<Message> messages; 

Hinzufügen eines Fehler oder Problem erstellt eine neue Nachricht mit einem Enum seiner Art bezeichnet und fügt es der Sammlung hinzu. Der Aufruf von FetchErrors()/FetchIssues() gibt Nachrichten dieses Typs aus der Auflistung zurück.

Würde der folgende Test gültig ?:

[Test] 
    public void FetchErrors_LoggingEnabledAddErrorFetchErrors_ReturnsError() 
    { 
     notificationService = new NotificationService(); 
     notificationService.AddError("A new error"); 

     Assert.AreEqual(new []{"A new error"}, notificationService.FetchErrors()); 
    } 

Meine Sorge ist, dass ich zum ersten Mal AddError() ich anrufen, dann das Ergebnis der Prüfung FetchErrors(). Also rufe ich zwei Funktionen an. Ist das falsch?

Sollte ich die Sammlung öffentlich machen und direkt bestätigen, dass sie eine Nachricht des entsprechenden Typs enthält, die die protokollierte Fehlermeldung enthält?

Was wäre die beste Vorgehensweise in diesem Szenario?

Antwort

3

Ihr Ansatz sieht OK aus - das Testen der Implementierung über die öffentlichen Methoden ist der einzige verfügbare Zugriff im Design.

Um die Methoden unabhängig voneinander zu testen, müssten Sie eine White-Box-Backdoor testen, um auf die privaten Nachrichten zuzugreifen, um sicherzustellen, dass die entsprechenden Add* Methoden funktionieren. Dies ist keine gute Idee, da Ihre Tests jedes Mal, wenn sich die zugrunde liegende CUT-Implementierung ändert, (zur Laufzeit) unterbrochen werden.

Eine gemeinsame, bessere Alternative zur Reflexion ist eine nicht-Schnittstelle internal Methode auf dem Betonklasse im Test zu, die Ihnen irgendeine Art von ‚Wartungsklappe‘ Zugang zur ICollection<Message> messages; Implementierung Feld erlaubt und erlaubt dann InternalsVisibleTo Zugriff auf dem Komponententestmontage.

Ein Hinweis - Sie werden feststellen, dass Assert.AreEqual auf einem der String-Enumerable einen Referenzvergleich tun und somit fehlschlagen (weil Sie es mit einem neuen Array vergleichen).

Sie werden wahrscheinlich diese wie zu etwas ändern:

Assert.IsTrue(notificationService.FetchErrors().Contains("A new error")); 

bearbeiten

IMO brauchen Sie nicht so weit wie machen Sie das Feld public in .Net zu gehen. Da Sie die Schnittstellenabstraktion bereits an Ort und Stelle haben, die theoretisch die Zugriffsmöglichkeit auf die CUT einschränken sollte, ist es üblich, internal scope und InternalsVisibleTo zu verwenden, um den UT-Spezialzugriff zu erlauben. Jon Skeet et al haben mentioned this here

#region Unit Testing side-door 
internal ICollection<Message> Messages 
{ 
    get { return _messages }; 
    // No Setter 
} 
#endregion 
+0

Hallo StuartLC, danke für die Antwort. Der Grund dafür ist, dass er in Roy Osheroves Buch vorschlägt, Dinge wie das Instantiieren einer neuen Instanz einer getesteten Klasse zu tun, aber die privaten Mitglieder öffentlich zu machen, um das Testen einzelner Funktionen zu erleichtern. Ich war mir nicht sicher, wie weit ich das in Bezug auf das Beispiel in meinem OP angehen sollte. –

2

Ihr Ansatz ist vollkommen in Ordnung.Ein Komponententest sollte der Struktur Arrange-Act-Assert folgen - und Ihr Anruf bei notificationService.AddError(..) ist einfach ein Teil des Abschnitts Arrange des Tests.

Es ist nicht so, dass Sie nicht mehr als eine Methode in einem Test aufrufen können (in vielen Fällen müssen Sie), der Punkt ist, dass Sie nur für eine einzige Tatsache bestätigen/testen sollten. Das machst du, also sieht alles gut aus.

+1

Hallo Thomas, es schien mir ok, aber dann dachte ich: Was passiert, wenn es einen Bug in der AddError-Funktion und/oder FetchErrors-Funktion gibt - indem beide aufgerufen werden und der Test fehlschlägt, wäre es nicht offensichtlich, wo genau der Fehler war . Ich denke, es geht darum, in welches Detail man gehen sollte, bevor man den Test als 'vollständig' betrachtet. –

+1

Nun, streng genommen hast du recht. Um absolut richtig zu sein, müßten Sie direkt gegen das 'messages'-Feld aktiv werden und auch einen zweiten Test schreiben, der die' FetchErrors() 'Methode überprüft. Um dies zu tun, müssten Sie das Feld zugänglich machen, indem Sie es entweder "public" (hässlich, schlechtes Design) oder "intern" machen und Ihrer Test-Assembly über das Attribut "InternalsVisib≤To" Zugriff darauf gewähren. –

1

Wie andere darauf hingewiesen haben, ist es in Ordnung, beide Methoden zu verwenden. Sie testen das Verhalten der Klasse. Wenn ein Fehler veröffentlicht wird, können Sie ihn lesen.

Wenn die Implementierung komplizierter ist, dann müssen Sie wahrscheinlich das Design überdenken, da Ihre Methode (n) zu viel tun. Sehen Sie, was Sie beschreiben, warum eigentlich brauchen Sie den Getter, um eine Methode zu sein?

Im Allgemeinen mag ich nicht die Idee, interne Methode nur für Testzwecke freizulegen. Dies sendet eine falsche Nachricht an denjenigen, der diesen Code in der Zukunft beibehalten wird (auch Sie, nachdem Sie vergessen haben). Es sagt - ja, gehen Sie voran, greifen Sie auf diese Sammlung direkt von jeder Klasse in dieser Versammlung zu.

1

Wir können nicht sagen, ob Ihr Test gültig ist oder nicht, basierend auf dem von Ihnen geposteten Code. Wir wissen nicht, was AddError() tut, ist nicht möglich zu wissen, ob der Komponententest korrekt ist.

Wenn AddError nur einen Fehler in die Sammlung hinzufügen und alles andere tun, dann ist Ihr Test gut und Aufruf FetchErrors() ist gültig, weil es einen separaten Unit-Test haben muss, der es überprüft. In jedem Fall müssen Sie, wenn Ihre AddError-Methode etwas anderes tut, den Test ändern und alle Schritte dieser Methode überprüfen.