2 Stimmen

Automatisierte Tests für eine "untestbare" Anwendung in C#. Unit/Integration/Funktionale/Systemtests - Wie geht das?

Ich habe Schwierigkeiten bei der Einführung automatisierter Tests für eine bestimmte Anwendung. Das meiste Material, das ich gefunden habe, konzentriert sich auf Unit-Tests. Das ist bei dieser Anwendung aus mehreren Gründen nicht hilfreich:

  • Die Anwendung ist mehrstufig
  • Nicht mit Blick auf das Testen geschrieben (viele Singletons halten Einstellungen auf Anwendungsebene, Objekte nicht mockbar, "Zustand" ist an vielen Stellen erforderlich)
  • Die Anwendung ist sehr datenorientiert
  • Die meisten "funktionalen Prozesse" sind End-to-End (Client -> Server -> Datenbank)

Stellen Sie sich folgendes Szenario vor:

var item = server.ReadItem(100);
var item2 = server.ReadItem(200);
client.SomethingInteresting(item1, item2);
server.SomethingInteresting(item1, item2);

Definition des Serveraufrufs:

Server::SomethingInteresting(Item item1, Item item2)
{
  // Semi-interesting things before going to the server.

  // Possibly stored procedure call doing some calculation
  database.SomethingInteresting(item1, item2);
}

Wie würde man einige automatisierte Tests für das einrichten, wo es Client-Interaktion, Server-Interaktion dann Datenbank-Interaktion ist?

Ich verstehe, dass dies keine "Einheit" darstellt. Dies ist möglicherweise ein Beispiel für einen Funktionstest.

Hier sind meine Fragen:

  • Ist das Testen einer solchen Anwendung ein hoffnungsloser Fall?
  • Kann dies mit etwas wie nUnit oder MS Test erreicht werden?
  • Würde das "Setup" dieses Tests einfach einen vollständigen Start der Anwendung und eine Anmeldung erzwingen?
  • Würde ich einfach item1 und item2 erneut aus der Datenbank einlesen, um zu überprüfen, ob sie korrekt geschrieben wurden?
  • Habt ihr irgendwelche Tipps, wie man diesen beängstigenden Prozess beginnen kann?

Jede Hilfe wäre sehr willkommen.

2voto

Hector Correa Punkte 25664

Wenn es sich um eine sehr wichtige Anwendung in Ihrem Unternehmen handelt, die voraussichtlich mehrere Jahre laufen wird, würde ich vorschlagen, klein anzufangen. Suchen Sie nach kleinen Teilen, die Sie testen können oder die Sie mit kleinen Änderungen am Code testen können, um sie testbar zu machen.

Es ist nicht ideal, aber in einem Jahr oder so werden Sie wahrscheinlich viel besser sein als heute.

2voto

Ben Fulton Punkte 3948

Sie sollten sich mit Integrationstestläufern wie Gurke ou Fitnesse . Je nach Szenario fungiert ein Integrationstest-Runner entweder als Client für Ihren Server und führt die entsprechenden Aufrufe an den Server aus, oder er teilt sich einen Teil des Domänencodes mit Ihrem bestehenden Client und führt diesen Code aus. Sie geben Ihren Szenarien eine Folge von Aufrufen vor, die ausgeführt werden sollen, und überprüfen, ob die richtigen Ergebnisse ausgegeben werden.

Ich denke, mit ein wenig Arbeit können Sie zumindest einige Teile Ihrer Anwendung testbar machen. Viel Glück!

2voto

Chaitanya Punkte 5013

Sehen Sie sich Pex und Moles an. Pex kann Ihren Code analysieren und Einheitstests erstellen, die alle Grenzbedingungen usw. testen. Sie können mit der Einführung eines Mocking-Frameworks beginnen, um einen Teil des complext-Bits zu stubben.

Um weiterzukommen, müssen Sie beginnen, die Datenbank mit Integrationstests zu belasten. Dazu müssen Sie die Daten in der Datenbank kontrollieren und nach Beendigung des Tests aufräumen. Hierfür gibt es einige Möglichkeiten. Sie können Sql-Skripte als Teil des Setups/Teardowns für jeden Test ausführen. Sie können ein AOP-Framework wie Postsharp zur Kompilierungszeit verwenden, um die SQL-Skripte auszuführen, indem Sie sie einfach in den Attributen angeben. Wenn Ihr Projekt Postsharp nicht verwenden möchte, können Sie auch ein integriertes Dot-Net-Feature namens "Context Bound Objects" verwenden, um Code zu injizieren, der die Sql-Skripte in einer AOP-Mode ausführt. Mehr Details hier - http://www.chaitanyaonline.net/2011/09/25/improving-integration-tests-in-net-by-using-attributes-to-execute-sql-scripts/

Grundsätzlich würde ich die folgenden Schritte vorschlagen,

1) Installieren Sie Pex und erzeugen Sie einige Unit-Tests. Dies ist der schnellste und am wenigsten aufwändige Weg, um einen guten Satz von Regressionstests zu erstellen.

2) Analysieren Sie die Einheitstests und prüfen Sie, ob es andere Tests oder Permutationen des Tests gibt, die Sie verwenden könnten. Wenn ja, schreiben Sie sie von Hand. Führen Sie bei Bedarf ein Mocking-Framework ein.

3) Beginnen Sie mit der Durchführung von End-to-End-Integrationstests. Schreiben Sie Sql-Skripte zum Einfügen und Löschen von Daten in die Datenbank. Schreiben Sie einen Unit-Test, der die Sql-Skripte zum Einfügen ausführt. Rufen Sie eine High-Level-Methode im Frontend Ihrer Anwendung auf, z. B. "AddNewUser", und stellen Sie sicher, dass sie den ganzen Weg zum Backend zurücklegt. Die High-Level-Methode im Frontend kann eine beliebige Anzahl von Zwischenmethoden in verschiedenen Schichten aufrufen.

4) Wenn Sie feststellen, dass Sie viele Integrationstests nach diesem Muster schreiben: "Sql-Skript ausführen, um Testdaten einzufügen" -> Aufruf einer funktionalen Methode auf hoher Ebene -> "Sql-Skript ausführen, um Testdaten zu bereinigen", können Sie den Code bereinigen und dieses Muster mithilfe von AOP-Techniken wie Postsharp oder Context Bound Methods kapseln.

2voto

Merlyn Morgan-Graham Punkte 56447

Idealerweise sollten Sie Ihren Code im Laufe der Zeit refaktorisieren, damit er besser testbar wird. Im Idealfall erlaubt Ihnen jede schrittweise Verbesserung in diesem Bereich, mehr Unit-Tests zu schreiben, was Ihr Vertrauen in Ihren bestehenden Code und Ihre Fähigkeit, neuen Code zu schreiben, ohne bestehenden getesteten Code zu zerstören, drastisch erhöhen sollte.

Ich finde, dass ein solches Refactoring oft auch andere Vorteile mit sich bringt, wie z. B. ein flexibleres und besser wartbares Design. Diese Vorteile sollte man sich vor Augen halten, wenn man versucht, die Arbeit zu rechtfertigen.

Ist das Testen einer solchen Anwendung ein hoffnungsloser Fall?

Ich führe seit Jahren automatisierte Integrationstests durch und habe festgestellt, dass dies sehr lohnend ist, sowohl für mich als auch für die Unternehmen, für die ich gearbeitet habe :) Ich habe erst in den letzten 3 Jahren oder so angefangen zu verstehen, wie man eine Anwendung vollständig unit-testbar macht, und davor habe ich vollständige Integrationstests (mit benutzerdefinierten implementierten/Hack-Test-Hooks) durchgeführt.

Es ist also kein aussichtsloser Fall, auch wenn die Anwendung so aufgebaut ist, wie sie ist. Aber wenn Sie wissen, wie man Unit-Tests durchführt, können Sie viele Vorteile aus dem Refactoring der Anwendung ziehen: Stabilität und Wartbarkeit der Tests, einfaches Schreiben der Tests und Isolierung von Fehlern auf den spezifischen Bereich des Codes, der den Fehler verursacht hat.

Kann dies mit etwas wie nUnit oder MS Test erreicht werden?

Ja, es gibt Frameworks für Webseiten-/UI-Tests, die Sie aus den .Net-Unit-Testbibliotheken verwenden können, darunter auch ein in Visual Studio integriertes Framework.

Es kann auch von großem Nutzen sein, den Server direkt aufzurufen, anstatt über die Benutzeroberfläche (ähnlich wie bei einer Umstrukturierung der Anwendung). Sie können auch versuchen, den Server zu mocken und die grafische Benutzeroberfläche und die Geschäftslogik der Client-Anwendung isoliert zu testen.

Würde das "Setup" dieses Tests einfach einen vollständigen Start der Anwendung und eine Anmeldung erzwingen?

Auf der Kundenseite, ja. Es sei denn, Sie wollen die Anmeldung testen :)

Auf der Serverseite können Sie den Server möglicherweise weiterlaufen lassen oder eine Art DB-Wiederherstellung zwischen den Tests durchführen. Eine DB-Wiederherstellung ist mühsam (und wenn man sie falsch schreibt, ist sie unzuverlässig) und verlangsamt die Tests, aber sie hilft ungemein bei der Testisolierung.

Würde ich einfach item1 und item2 erneut aus der Datenbank einlesen, um zu überprüfen, ob sie korrekt geschrieben wurden?

Typische Integrationstests basieren auf dem Konzept einer Endliche Zustandsmaschine . Solche Tests behandeln den zu testenden Code wie eine Reihe von Zustandsübergängen und machen Aussagen über den Endzustand des Systems.

Sie würden also die DB vorher in einen bekannten Zustand versetzen, eine Methode auf dem Server aufrufen und die DB anschließend überprüfen.

Sie sollten zustandsbasierte Assertions immer auf einer Ebene unterhalb der Ebene des Codes durchführen, den Sie trainieren. Wenn Sie also Web-Service-Code trainieren, validieren Sie die DB. Wenn Sie den Client trainieren und gegen eine echte Version des Dienstes laufen, validieren Sie auf der Ebene des Dienstes oder der DB. Sie sollten niemals einen Webdienst trainieren und gleichzeitig Assertions für denselben Webdienst durchführen (zum Beispiel). Auf diese Weise werden Fehler verschleiert, und Sie können nicht sicher sein, dass Sie tatsächlich die vollständige Integration aller Komponenten testen.

Habt ihr irgendwelche Tipps, wie man diesen beängstigenden Prozess beginnen kann?

Teilen Sie Ihre Arbeit auf. Identifizieren Sie alle Komponenten des Systems (jede Baugruppe, jeden laufenden Dienst, usw.). Versuchen Sie, diese in natürliche Kategorien einzuteilen. Verbringen Sie einige Zeit damit, Testfälle für jede Kategorie zu entwerfen.

Entwerfen Sie Ihre Testfälle mit Blick auf die Priorität. Untersuchen Sie jeden Testfall. Denken Sie sich ein hypothetisches Szenario aus, das zu einem Fehlschlag führen könnte, und versuchen Sie abzuschätzen, ob es sich um einen auffälligen Fehler handeln würde, ob der Fehler eine Zeit lang bestehen bleiben könnte oder ob er auf eine andere Version verschoben werden könnte. Weisen Sie Ihren Testfällen eine Priorität zu, die auf diesen Vermutungen basiert.

Bestimmen Sie einen Teil Ihrer Anwendung (möglicherweise eine bestimmte Funktion), von dem möglichst wenig abhängt, und schreiben Sie nur die Testfälle mit der höchsten Priorität für diesen Teil (idealerweise sollten es die einfachsten/grundlegendsten Tests sein). Sobald Sie damit fertig sind, gehen Sie zum nächsten Teil der Anwendung über.

Sobald Sie alle logischen Teile Ihrer Anwendung (alle Baugruppen, alle Funktionen) durch die Testfälle mit der höchsten Priorität abgedeckt haben, tun Sie alles, was Sie können, um diese Testfälle jeden Tag laufen zu lassen. Beheben Sie Testfehler in diesen Fällen, bevor Sie an der Implementierung weiterer Tests arbeiten.

Führen Sie dann die Codeabdeckung für Ihre zu testende Anwendung durch. Sehen Sie, welche Teile der Anwendung Sie übersehen haben.

Wiederholen Sie den Vorgang mit jedem weiteren Testfall mit höherer Priorität, bis die gesamte Anwendung auf einer bestimmten Ebene getestet ist und Sie sie ausliefern können :)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X