Dies ist ein Thema, das mich sehr interessiert. Es gibt viele Puristen, die sagen, dass Sie Technologien wie EF und NHibernate nicht testen sollten. Sie haben Recht, sie sind bereits sehr streng getestet und wie in einer früheren Antwort angegeben, ist es oft sinnlos, viel Zeit damit zu verbringen, zu testen, was Sie nicht besitzen.
Sie besitzen jedoch die Datenbank darunter! Hier bricht dieser Ansatz meiner Meinung nach zusammen. Sie müssen nicht testen, ob EF / NH ihre Arbeit korrekt ausführen. Sie müssen testen, ob Ihre Zuordnungen / Implementierungen mit Ihrer Datenbank funktionieren. Meiner Meinung nach ist dies einer der wichtigsten Teile eines Systems, das Sie testen können.
Genau genommen bewegen wir uns jedoch aus dem Bereich des Unit-Tests in den Bereich des Integrationstests, aber die Prinzipien bleiben dieselben.
Das erste, was Sie tun müssen, ist, Ihre DAL verspotten zu können, damit Ihre BLL unabhängig von EF und SQL getestet werden kann. Dies sind Ihre Unit-Tests. Als nächstes müssen Sie Ihre Integrationstests entwerfen , um Ihre DAL zu beweisen. Meiner Meinung nach sind diese genauso wichtig.
Es gibt ein paar Dinge zu beachten:
- Ihre Datenbank muss bei jedem Test in einem bekannten Zustand sein. Die meisten Systeme verwenden entweder ein Backup oder erstellen dafür Skripte.
- Jeder Test muss wiederholbar sein
- Jeder Test muss atomar sein
Es gibt zwei Hauptansätze zum Einrichten Ihrer Datenbank: Der erste besteht darin, ein UnitTest-Skript zum Erstellen einer Datenbank auszuführen. Dadurch wird sichergestellt, dass sich Ihre Komponententestdatenbank zu Beginn jedes Tests immer im selben Status befindet (Sie können dies entweder zurücksetzen oder jeden Test in einer Transaktion ausführen, um dies sicherzustellen).
Ihre andere Option ist, was ich tue, spezifische Setups für jeden einzelnen Test auszuführen. Ich glaube, dies ist aus zwei Hauptgründen der beste Ansatz:
- Ihre Datenbank ist einfacher, Sie benötigen nicht für jeden Test ein vollständiges Schema
- Jeder Test ist sicherer. Wenn Sie einen Wert in Ihrem Erstellungsskript ändern, werden Dutzende anderer Tests nicht ungültig.
Leider ist Ihr Kompromiss hier Geschwindigkeit. Es braucht Zeit, um alle diese Tests auszuführen und alle diese Setup- / Teardown-Skripte auszuführen.
Ein letzter Punkt: Es kann sehr schwierig sein, eine so große Menge an SQL zu schreiben, um Ihr ORM zu testen. Hier gehe ich sehr böse vor (die Puristen hier werden mir nicht zustimmen). Ich benutze mein ORM, um meinen Test zu erstellen! Anstatt für jeden DAL-Test in meinem System ein separates Skript zu haben, habe ich eine Test-Setup-Phase, in der die Objekte erstellt, an den Kontext angehängt und gespeichert werden. Ich führe dann meinen Test durch.
Dies ist alles andere als die ideale Lösung, aber in der Praxis finde ich, dass es viel einfacher zu verwalten ist (insbesondere wenn Sie mehrere tausend Tests haben), da Sie sonst eine große Anzahl von Skripten erstellen. Praktikabilität über Reinheit.
Ich werde ohne Zweifel in ein paar Jahren (Monaten / Tagen) auf diese Antwort zurückblicken und mit mir selbst nicht einverstanden sein, da sich meine Ansätze geändert haben - dies ist jedoch mein aktueller Ansatz.
Um alles zusammenzufassen, was ich oben gesagt habe, ist dies mein typischer DB-Integrationstest:
[Test]
public void LoadUser()
{
this.RunTest(session => // the NH/EF session to attach the objects to
{
var user = new UserAccount("Mr", "Joe", "Bloggs");
session.Save(user);
return user.UserID;
}, id => // the ID of the entity we need to load
{
var user = LoadMyUser(id); // load the entity
Assert.AreEqual("Mr", user.Title); // test your properties
Assert.AreEqual("Joe", user.Firstname);
Assert.AreEqual("Bloggs", user.Lastname);
}
}
Das Wichtigste dabei ist, dass die Sitzungen der beiden Schleifen völlig unabhängig sind. Bei der Implementierung von RunTest müssen Sie sicherstellen, dass der Kontext festgeschrieben und zerstört wird und Ihre Daten nur für den zweiten Teil aus Ihrer Datenbank stammen können.
Bearbeiten 13/10/2014
Ich habe gesagt, dass ich dieses Modell wahrscheinlich in den kommenden Monaten überarbeiten werde. Während ich weitgehend zu dem oben vertretenen Ansatz stehe, habe ich meinen Testmechanismus leicht aktualisiert. Ich neige jetzt dazu, die Entitäten in TestSetup und TestTearDown zu erstellen.
[SetUp]
public void Setup()
{
this.SetupTest(session => // the NH/EF session to attach the objects to
{
var user = new UserAccount("Mr", "Joe", "Bloggs");
session.Save(user);
this.UserID = user.UserID;
});
}
[TearDown]
public void TearDown()
{
this.TearDownDatabase();
}
Testen Sie dann jede Eigenschaft einzeln
[Test]
public void TestTitle()
{
var user = LoadMyUser(this.UserID); // load the entity
Assert.AreEqual("Mr", user.Title);
}
[Test]
public void TestFirstname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Joe", user.Firstname);
}
[Test]
public void TestLastname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Bloggs", user.Lastname);
}
Für diesen Ansatz gibt es mehrere Gründe:
- Es gibt keine zusätzlichen Datenbankaufrufe (ein Setup, ein Teardown)
- Die Tests sind weitaus detaillierter. Jeder Test überprüft eine Eigenschaft
- Die Setup / TearDown-Logik wird aus den Testmethoden selbst entfernt
Ich denke, dies macht die Testklasse einfacher und die Tests detaillierter ( einzelne Aussagen sind gut )
Bearbeiten 03.05.2015
Eine weitere Überarbeitung dieses Ansatzes. Setups auf Klassenebene sind zwar sehr hilfreich für Tests wie das Laden von Eigenschaften, aber weniger nützlich, wenn die verschiedenen Setups erforderlich sind. In diesem Fall ist das Einrichten einer neuen Klasse für jeden Fall übertrieben.
Um dies zu unterstützen, habe ich jetzt tendenziell zwei Basisklassen SetupPerTest
und SingleSetup
. Diese beiden Klassen legen das Framework nach Bedarf offen.
In der haben SingleSetup
wir einen sehr ähnlichen Mechanismus wie in meiner ersten Bearbeitung beschrieben. Ein Beispiel wäre
public TestProperties : SingleSetup
{
public int UserID {get;set;}
public override DoSetup(ISession session)
{
var user = new User("Joe", "Bloggs");
session.Save(user);
this.UserID = user.UserID;
}
[Test]
public void TestLastname()
{
var user = LoadMyUser(this.UserID); // load the entity
Assert.AreEqual("Bloggs", user.Lastname);
}
[Test]
public void TestFirstname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Joe", user.Firstname);
}
}
Referenzen, die sicherstellen, dass nur die richtigen Entites geladen werden, können jedoch einen SetupPerTest-Ansatz verwenden
public TestProperties : SetupPerTest
{
[Test]
public void EnsureCorrectReferenceIsLoaded()
{
int friendID = 0;
this.RunTest(session =>
{
var user = CreateUserWithFriend();
session.Save(user);
friendID = user.Friends.Single().FriendID;
} () =>
{
var user = GetUser();
Assert.AreEqual(friendID, user.Friends.Single().FriendID);
});
}
[Test]
public void EnsureOnlyCorrectFriendsAreLoaded()
{
int userID = 0;
this.RunTest(session =>
{
var user = CreateUserWithFriends(2);
var user2 = CreateUserWithFriends(5);
session.Save(user);
session.Save(user2);
userID = user.UserID;
} () =>
{
var user = GetUser(userID);
Assert.AreEqual(2, user.Friends.Count());
});
}
}
Zusammenfassend funktionieren beide Ansätze je nachdem, was Sie testen möchten.