2009-07-24 7 views
4

Ich habe noch nie Einheitentests aus verschiedenen Gründen geschrieben. Ich habe jetzt die Möglichkeit, Tests bequem zu schreiben, weil ich eine kleine App von Grund auf neu erstellen muss.Wie würden Sie in dieser Situation Unit Tests anwenden?

Allerdings bin ich ein wenig verwirrt. Die Anwendung soll einen Drucker mit einem Chipkartenleser verwenden, um Daten auf einer Chipkarte zu programmieren. Hier ist die Reihenfolge der Aktionen: Gerätekontext erstellen, Druckermodus einstellen, ein Dokument initialisieren, eine Karte in den Drucker einführen, mit einem Kartenleser verbinden, etwas auf die Karte schreiben, Karte herausziehen, Dokument beenden, entsorgen Gerätekontext.

Okay, Komponententests sollen eine Funktion für jeden Test testen, und jeder Test soll unabhängig vom Ergebnis anderer Tests laufen. Aber sehen wir mal - ich kann das Schreiben auf Smartcard nicht testen, wenn ich es nicht richtig im Drucker positioniert habe und wenn ich es nicht angeschlossen habe. Und ich kann das nicht per Software nachahmen - ich kann nur testen, ob das Schreiben wirklich passiert ist, wenn die echte Karte richtig positioniert und verbunden ist. Und wenn die Verbindung zur Karte fehlschlägt, gibt es keine Möglichkeit, das Schreiben auf die Karte zu testen - so ist das Testunabhängigkeitsprinzip gebrochen.

Bisher kam ich mit einem Test wie folgt auf (es gibt auch andere Test, der ‚richtigen‘ sind und andere Dinge testen zu)

[Test] 
public void _WriteToSmartCard() 
{ 
//start print job 
printer = new DataCardPrinter(); 
reader = new SCMSmartCardReader(); 
di = DataCardPrinter.InitializeDI(); 
printer.CreateHDC(); 
Assert.AreNotEqual(printer.Hdc, 0, "Creating HDC Failed"); 
Assert.Greater(di.cbSize, 0); 

int res = ICE_API.SetInteractiveMode(printer.Hdc, true); 
Assert.Greater(res, 0, "Interactive Mode Failed"); 

res = ICE_API.StartDoc(printer.Hdc, ref di); 
Assert.Greater(res, 0, "Start Document Failed"); 

res = ICE_API.StartPage(printer.Hdc); 
Assert.Greater(res, 0, "Start Page Failed"); 

res = ICE_API.RotateCardSide(printer.Hdc, 1); 
Assert.Greater(res, 0, "RotateCardSide Failed"); 

res = ICE_API.FeedCard(printer.Hdc, ICE_API.ICE_SMARTCARD_FRONT + ICE_API.ICE_GRAPHICS_FRONT); 
Assert.Greater(res, 0, "FeedCard Failed"); 

bool bRes = reader.EstablishContext(); 
Assert.True(bRes, "EstablishContext Failed"); 

bRes = reader.ConnectToCard(); 
Assert.True(bRes, "Connect Failed"); 

bRes = reader.WriteToCard("123456"); 
Assert.True(bRes, "Write To Card Failed"); 

string read = reader.ReadFromCard(); 
Assert.AreEqual("123456", read, "Read From Card Failed"); 

bRes = reader.DisconnectFromCard(); 
Assert.True(bRes, "Disconnect Failde"); 

res = ICE_API.SmartCardContinue(printer.Hdc, ICE_API.ICE_SMART_CARD_GOOD); 
Assert.Greater(res, 0, "SmartCardContinue Failed"); 

res = ICE_API.EndPage(printer.Hdc); 
Assert.Greater(res, 0, "End Page Failed"); 

res = ICE_API.EndDoc(printer.Hdc); 
Assert.Greater(res, 0, "End Document Failed"); 
} 

Der Test funktioniert, aber die Prinzipien gebrochen werden - es testet mehrere Funktionen, und viele von ihnen. Und jede folgende Funktion hängt vom Ergebnis des vorherigen ab. Nun kommen wir zu der Frage: Wie sollte ich unter diesen Umständen auf Unit-Tests zugehen?

+0

Jede Chance auf Zugriff auf die C# -Wrapper? Wir machen das gleiche, greifen jedoch auf VB6- und C++ - Code zurück. Würde es lieben, alles in einer besseren IDE und einem besseren Framework zu tun. – fuzz

Antwort

4

Ihr Testcode wird oft als Integrationstest bezeichnet. Kurz gesagt, Integrationstests werden oft als Tests definiert, die die Integration zwischen Komponenten eines Systems überprüfen. Während, wie David Reis erwähnt, Unit-Tests oft einzelne Methoden testen.

Beide Klassen von Tests sind nützlich. Integrationstests, wie Ihres, üben das System von Anfang bis Ende aus, um sicherzustellen, dass alles gut zusammenarbeitet. Aber sie sind langsam und haben oft Abhängigkeiten von außen (wie ein Kartenleser). Unit-Tests sind kleiner, schneller und sehr konzentriert, aber es ist schwer, den Wald für die Bäume zu sehen, wenn alles nur Komponententests sind.

Platzieren Sie Ihre Komponententests in einem separaten Verzeichnis von Ihren Integrationstests. Verwenden Sie kontinuierliche Integration. Führen Sie Ihre Integrationstests möglicherweise nur ein paar Mal am Tag durch, da sie langsamer sind und mehr Setup/Bereitstellung erfordern. Führen Sie Ihre Komponententests die ganze Zeit aus.

Nun, wie testen Sie Unit Ihre spezielle Situation, wo Methoden von anderen Methoden abhängen? Es ist unklar, wie viel Code Sie kontrollieren, wie viel in den Bibliotheken ist, aber in Ihrem Code lernen Sie Dependency Injection (DI) so viel wie möglich zu verwenden.

Ihre Leser Methode sieht wie folgt aus etwas Angenommen (in Pseudo-Code)

boolean WriteToCard(String data){ 
    // do something to data here 
    return ICE_API.WriteToCard(ICE_API.SOME_FLAG, data) 
} 

Nun, Sie sollten dies wie etwas ändern können: für WriteToCard

ICE_API api = null 

    ICE_API setApi(ICE_API api) { 
     this.api = api 
    } 

    ICE_API getApi() { 
     if (api == null) { 
     api = new ICE_API() 
     } 
    } 

    boolean WriteToCard(String data){ 
     // do something to data here  
     return getApi().WriteToCard(ICE_API.SOME_FLAG, data) 
    } 

Dann in Ihrem Test in die Einrichtung, die Sie tun würden

void setup() 
    _mockAPI = new Mock(ICE_API) 
    reader.setApi(_mockAPI) 

void testWriteToCard() 
    reader.writeToCard("12345") 
    // assert _mockAPI.writeToCard was called with expected data and flags. 
+0

Sorry, ich weiß nicht C# wissen, dass es bessere Möglichkeiten haben DI zu tun . Auch in Java, DI mit statischen Methoden irgendwie saugt, können Sie die statischen Aufrufe in eine Methode umbrechen, dann in Ihrem Test stub out diese Wrapper-Methoden, um Ihre Behauptungen zu tun. –

+0

Mocking Dinge scheint hier nicht nützlich sein: Ich kann nicht schreiben auf die Karte, wenn ich "Schein" Positionierung der Karte. Die Karte muss physisch da sein. Also bin ich darauf angewiesen, dass der Drucker eingeschaltet ist, die Karte richtig gefüttert wird usw., bevor ich "WriteToCard" testen kann. Ich denke, ich werde diesen Test als Integrationstest bezeichnen und ihn vom Rest trennen. – Evgeny

3

Es gibt nichts von Natur aus falsch mit einer Reihe von Tests, die auf sie abhängen, außer dass Sie keine vollständige Liste von Fehlern, wenn mehrere Dinge sind gebrochen, weil der erste Test wird zum Scheitern verurteilt die man berichtete.

Eine Möglichkeit, dies obwohl beheben könntest, ist durch eine Test-Initialisierungsroutine zu schaffen (mit dem [SetUp] Attribute in Ihrer [TestFixture] Klasse), die das System in einen bekannten Zustand bringt, bevor Sie einen einzigen Test zu tun.

Beachten Sie auch, dass dieses Szenario nicht vollständig für Komponententests geeignet ist, da es möglicherweise manuelle Schritte außerhalb der Software erfordert. Komponententests eignen sich inhärent besser zum Testen von Softwaremodulen, die nicht mit nicht reproduzierbaren Komponenten interagieren. Möglicherweise möchten Sie die Operationen in der Reader-API abstrahieren (indem Sie eine Schnittstelle für die benötigten Operationen erstellen und eine Klasse, die diese Aufrufe an die tatsächliche API weitergibt), und dann ein Scheinobjekt verwenden, um so zu tun Leser, damit Sie die Hauptlogik Ihrer Klasse (n) testen können, ohne sich auf Hardware verlassen zu müssen.

Dann können Sie das Testen der tatsächlichen API implementieren, entweder in einen Komponententest oder in etwas anderes, das eine minimale menschliche Interaktion erfordert ... im Grunde würden Sie den Menschen in Ihren Testprozess einkapseln;)

4

Dies sieht nicht wie ein Komponententest aus. Der Komponententest sollte schnell und durchsetzungsfähig sein, d. H. Sie sollten nicht überprüfen müssen, ob eine Operation in einer Hardware tatsächlich stattgefunden hat. Ich würde diesen Code als "Testautomatisierung" klassifizieren, da Sie diese Aufgabe ausführen müssen und sicher sein müssen, dass etwas passiert ist.

Der Code ist auch prozedural und sieht schwer zu testen. Die Verwendung mehrerer Assertionen in der gleichen Testmethode zeigt an, dass es geteilt werden sollte.

Meine bevorzugte Referenz für Unit-Tests ist Misko Hevery's site. Ich hoffe es hilft!

+0

Nun, darum ging es in meiner Frage - es ist kein Unit-Test! Ich habe andere Tests, die mehr wie Unit-Tests aussehen, das heißt [Test] public void ConverLongStringToHexArray() { byte [] erwartet = new byte [] {0x31, 0x32, 0x33, 0x34, 0x35, 0x31, 0x32, 0x33, 0x34, 0x35, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36}; byte [] Ergebnis = Leser.StringToHexArray ("1234512345123456zzzzzzzzzzzzzzzzzzzzzzzzzzzzz"); Assert.AreEqual (erwartet, Ergebnis, "Fehler beim Umwandeln langer Zeichenfolge in Byte-Array"); } Aber die große ist nicht leicht Danke für den Link btw zu teilen, sehr nützlich – Evgeny

+0

Platzierung Code in Kommentaren ist eine schlechte Idee ... – Evgeny