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 :)