Bei den meisten Unit-Testing-Tutorials / Beispielen werden in der Regel die zu testenden Daten für jeden einzelnen Test definiert. Ich denke, dies ist Teil der Theorie "Alles sollte isoliert getestet werden".
Ich habe jedoch festgestellt, dass bei mehrschichtigen Anwendungen mit viel DI der Code, der zum Einrichten der einzelnen Tests erforderlich ist, sehr langwierig ist. Stattdessen habe ich eine Reihe von Testbase-Klassen erstellt, die ich jetzt erben kann und die viele vorgefertigte Testgerüste haben.
Als Teil davon erstelle ich auch gefälschte Datasets, die die Datenbank einer laufenden Anwendung darstellen, wenngleich normalerweise nur eine oder zwei Zeilen in jeder "Tabelle" enthalten sind.
Ist es eine akzeptierte Praxis, die Mehrzahl der Testdaten über alle Komponententests hinweg vorab zu definieren, wenn nicht sogar alle?
Aktualisieren
Aus den Kommentaren unten geht hervor, dass ich mehr Integration als Unit-Tests mache.
Mein aktuelles Projekt ist ASP.NET MVC mit Unit of Work über Entity Framework Code First und Moq zum Testen. Ich habe die UOW und die Repositorys verspottet, aber ich verwende die echten Geschäftslogikklassen und teste die Controlleraktionen. Bei den Tests wird häufig überprüft, ob die UoW festgeschrieben wurde, z.
[TestClass]
public class SetupControllerTests : SetupControllerTestBase {
[TestMethod]
public void UserInvite_ExistingUser_DoesntInsertNewUser() {
// Arrange
var model = new Mandy.App.Models.Setup.UserInvite() {
Email = userData.First().Email
};
// Act
setupController.UserInvite(model);
// Assert
mockUserSet.Verify(m => m.Add(It.IsAny<UserProfile>()), Times.Never);
mockUnitOfWork.Verify(m => m.Commit(), Times.Once);
}
}
SetupControllerTestBase
baut die Schein-UOW und instanziiert die userLogic
.
Viele der Tests setzen voraus, dass ein Benutzer oder ein Produkt in der Datenbank vorhanden ist. In diesem Beispiel habe ich bereits angegeben, was die nachgebildete Benutzeroberfläche zurückgibt. Dies userData
ist lediglich ein IList<User>
Datensatz für einen einzelnen Benutzer.
quelle
Antworten:
Letztendlich möchten Sie so wenig Code wie möglich schreiben, um möglichst viele Ergebnisse zu erzielen. Wenn Sie in mehreren Tests viel denselben Code haben, führt a) in der Regel zu einer Codierung durch Kopieren und Einfügen, und b) wenn sich eine Methodensignatur ändert, müssen Sie möglicherweise viele fehlerhafte Tests reparieren.
Ich verwende den Ansatz, Standard-TestHelper-Klassen zu haben, die mir viele der Datentypen liefern, die ich routinemäßig verwende, damit ich Sätze von Standardentitäts- oder DTO- Klassen für meine Tests erstellen kann, um abzufragen und genau zu wissen, was ich jedes Mal bekomme. Ich kann also anrufen
TestHelper.GetFooRange( 0, 100 )
, um eine Auswahl von 100 Foo-Objekten mit all ihren abhängigen Klassen / Feldern zu erhalten.Insbesondere, wenn in einem ORM-Typsystem komplexe Beziehungen konfiguriert sind, die vorhanden sein müssen, damit die Dinge ordnungsgemäß ausgeführt werden, für diesen Test jedoch nicht unbedingt von Bedeutung sind, wodurch viel Zeit gespart werden kann.
In Situationen, in denen ich in der Nähe der Datenebene teste, erstelle ich manchmal eine Testversion meiner Repository-Klasse, die auf ähnliche Weise abgefragt werden kann (auch dies ist in einer ORM-Umgebung der Fall und wäre für a nicht relevant) reale Datenbank), da das Verspotten der genauen Antworten auf Abfragen viel Arbeit kostet und oft nur geringe Vorteile bietet.
Bei Unit-Tests gilt es jedoch einige Dinge zu beachten:
quelle
Was auch immer die Absicht Ihres Tests lesbarer macht.
Als allgemeine Faustregel gilt:
Wenn die Daten Teil des Tests sind (z. B. sollten keine Zeilen mit dem Status 7 gedruckt werden), codieren Sie sie im Test, damit klar ist, was der Autor beabsichtigt hat.
Wenn es sich bei den Daten nur um Fülldaten handelt, um sicherzustellen, dass sie zu bearbeiten sind (z. B. sollte der Datensatz nicht als vollständig markiert werden, wenn der Verarbeitungsdienst eine Ausnahme auslöst), sollten Sie auf jeden Fall eine BuildDummyData-Methode oder eine Testklasse verwenden, die irrelevante Daten aus dem Test heraushält .
Aber beachte, dass ich Mühe habe, mir ein gutes Beispiel für Letzteres auszudenken. Wenn Sie viele davon in einem Unit-Test-Gerät haben, haben Sie wahrscheinlich ein anderes Problem zu lösen ... Vielleicht ist die zu testende Methode zu komplex.
quelle
Verschiedene Testmethoden
Definieren Sie zunächst, was Sie tun: Unit-Test oder Integrationstest . Die Anzahl der Schichten ist für Komponententests irrelevant, da Sie höchstwahrscheinlich nur eine Klasse testen. Den Rest verspotten Sie. Für Integrationstests ist es unvermeidlich, dass Sie mehrere Ebenen testen. Wenn Sie über gute Komponententests verfügen, besteht der Trick darin, die Integrationstests nicht zu komplex zu gestalten.
Wenn Ihre Komponententests gut sind, müssen Sie beim Integrationstest nicht alle Details wiederholen.
Begriffe, die wir verwenden, sind ein bisschen plattformabhängig, aber Sie können sie in fast allen Test- / Entwicklungsplattformen finden:
Beispielanwendung
Je nach verwendeter Technologie können die Namen unterschiedlich sein, ich werde dies jedoch als Beispiel verwenden:
Wenn Sie eine einfache CRUD-Anwendung mit Modell Product, ProductsController und einer Indexansicht haben, die eine HTML-Tabelle mit Produkten generiert:
Das Endergebnis der Anwendung ist eine HTML-Tabelle mit einer Liste aller aktiven Produkte.
Unit-Test
Modell
Das Modell kannst du ganz einfach testen. Es gibt verschiedene Methoden dafür; Wir verwenden Vorrichtungen. Ich denke, das nennt man "gefälschte Datensätze". Also erstellen wir vor jedem Test die Tabelle und geben die Originaldaten ein. Die meisten Plattformen haben Methoden dafür. Zum Beispiel in Ihrer Testklasse eine Methode setUp (), die vor jedem Test ausgeführt wird.
Dann führen wir unseren Test durch, zum Beispiel: testGetAllActive- Produkte.
Also testen wir direkt in eine Testdatenbank. Wir verspotten die Datenquelle nicht. Wir machen es immer gleich. Dies ermöglicht es uns beispielsweise, mit einer neuen Version der Datenbank zu testen, und es treten Fragen auf.
In der realen Welt kann man nicht immer einer 100% igen Einzelverantwortung folgen . Wenn Sie dies noch besser machen möchten, könnten Sie eine Datenquelle verwenden, die Sie verspotten. Für uns (wir verwenden ein ORM), das das Gefühl hat, bereits vorhandene Technologie zu testen. Außerdem werden die Tests viel komplexer und testen die Abfragen nicht wirklich. Also halten wir es so.
Die fest codierten Daten werden separat in den Fixtures gespeichert. Das Fixture ist also wie eine SQL-Datei mit einer create table-Anweisung und Einfügungen für die von uns verwendeten Datensätze. Wir halten sie klein, es sei denn, es besteht ein tatsächlicher Testbedarf mit vielen Aufzeichnungen.
Regler
Der Controller benötigt mehr Arbeit, da wir das Modell damit nicht testen wollen. Also verspotten wir das Modell. Das heißt: Wir testen die Methode: index (), die eine Liste von Datensätzen zurückgeben soll.
Also verspotten wir die Modellmethode getAllActive () und fügen feste Daten hinzu (zum Beispiel zwei Datensätze). Jetzt testen wir die Daten, die der Controller an die Ansicht sendet, und vergleichen, ob wir diese beiden Datensätze wirklich zurückbekommen.
Das ist genug. Wir versuchen, dem Controller so wenig Funktionalität wie möglich hinzuzufügen, da dies das Testen schwierig macht. Aber natürlich ist immer etwas Code drin. Zum Beispiel testen wir Anforderungen wie: Zeigen Sie diese beiden Datensätze nur an, wenn Sie angemeldet sind.
Der Controller benötigt also normalerweise einen Schein und ein kleines Stück fest codierter Daten. Für ein Login-System vielleicht ein anderes. In unserem Test haben wir eine Hilfsmethode dafür: setLoggedIn (). Das macht es einfach, mit oder ohne Login zu testen.
Ansichten
Views testen ist schwer. Zuerst trennen wir die sich wiederholende Logik. Wir setzen es in Helfer und testen diese Klassen streng. Wir erwarten immer die gleiche Leistung. Beispiel: generateHtmlTableFromArray ().
Dann haben wir einige projektspezifische Ansichten. Diese testen wir nicht. Es ist nicht wirklich erwünscht, solche Einheiten zu testen. Wir bewahren sie für Integrationstests auf. Da wir einen Großteil des Codes in Views herausgenommen haben, haben wir hier ein geringeres Risiko.
Wenn Sie anfangen, diese zu testen, müssen Sie Ihre Tests wahrscheinlich jedes Mal ändern, wenn Sie ein Stück HTML ändern, was für die meisten Projekte nicht nützlich ist.
Integrationstests
Abhängig von Ihrer Plattform können Sie hier mit User Stories usw. arbeiten. Es kann webbasiert sein wie Selenium oder andere vergleichbare Lösungen.
Im Allgemeinen laden wir einfach die Datenbank mit den Fixtures und geben an, welche Daten verfügbar sein sollen. Für vollständige Integrationstests verwenden wir im Allgemeinen sehr globale Anforderungen. Also: Produkt auf aktiv setzen und dann prüfen, ob das Produkt verfügbar ist.
Wir testen nicht alles noch einmal, zum Beispiel, ob die richtigen Felder verfügbar sind. Hier testen wir die größeren Anforderungen. Da wir unsere Tests nicht vom Controller oder aus der Sicht duplizieren wollen. Wenn etwas wirklich der Schlüssel / Kernbestandteil Ihrer Anwendung ist oder aus Sicherheitsgründen (das Kennwort ist NICHT verfügbar), fügen wir es hinzu, um sicherzustellen, dass es richtig ist.
Die fest codierten Daten werden in den Fixtures gespeichert.
quelle
Wenn Sie Tests schreiben, die viel DI und Verkabelung erfordern, bis hin zur Verwendung "echter" Datenquellen, haben Sie wahrscheinlich den Bereich der einfachen Einheitentests verlassen und sind in den Bereich der Integrationstests eingetreten.
Für Integrationstests ist es meiner Meinung nach keine schlechte Idee, eine gemeinsame Dateneinrichtungslogik zu haben. Das Hauptziel solcher Tests ist der Nachweis, dass alles richtig konfiguriert ist. Dies ist ziemlich unabhängig von den konkreten Daten, die über Ihr System gesendet werden.
Für Unit-Tests hingegen würde ich empfehlen, das Ziel einer Testklasse als eine einzige "echte" Klasse zu halten und alles andere zu verspotten. Dann sollten Sie die Testdaten wirklich hart codieren, um sicherzustellen, dass Sie so viele spezielle / vorherige Fehlerpfade wie möglich zurückgelegt haben.
Um Tests ein semi-hartcodiertes / zufälliges Element hinzuzufügen, möchte ich zufällige Modellfabriken vorstellen. In einem Test mit einer Instanz meines Modells benutze ich diese Fabriken, um ein gültiges, aber völlig zufälliges Modellobjekt zu erstellen und dann nur die Eigenschaften hart zu codieren, die für den vorliegenden Test von Interesse sind. Auf diese Weise geben Sie alle relevanten Daten direkt in Ihrem Test an. Gleichzeitig müssen Sie nicht alle irrelevanten Daten angeben und (bis zu einem gewissen Grad) überprüfen, ob keine unbeabsichtigten Abhängigkeiten von anderen Modellfeldern bestehen.
quelle
Ich denke, es ist ziemlich üblich, die meisten Daten für Ihre Tests hart zu codieren.
Stellen Sie sich eine einfache Situation vor, in der ein bestimmter Datensatz einen Fehler verursacht. Sie können speziell einen Komponententest für diese Daten erstellen, um die Korrektur durchzuführen und sicherzustellen, dass der Fehler nicht wieder auftritt. Im Laufe der Zeit werden Ihre Tests eine Reihe von Daten enthalten, die eine Reihe von Testfällen abdecken.
Mit vordefinierten Testdaten können Sie auch einen Datensatz erstellen, der eine Vielzahl bekannter Situationen abdeckt.
Trotzdem denke ich, dass es auch sinnvoll ist, zufällige Daten in Ihren Tests zu haben.
quelle