Automated Testing/de
From OpenSimulator
Languages: |
English Deutsch |
Contents |
Einleitung
OpenSimulator verwendet NUnit, um eine automatisierte Code-Level-Test-Suite zu implementieren. Die Suite wird von http://jenkins.opensimulator.org nach jedem Code-Commit im Haupt-Git-Zweig ausgeführt. Sie kann auch manuell ausgeführt werden, wie unten beschrieben.
Die Suite dient dazu, Regression-Bugs zu reduzieren, Refactoring zu erleichtern und die Funktionalität auf verschiedenen Plattformen zu überprüfen, unter anderem. Erweiterungen der Suite oder Fehlerberichte über Fehler sind sehr willkommen.
Durchführung von Tests
Nant
Sie können alle Tests für OpenSimulator auf Ihrem System manuell ausführen, indem Sie nant test als Nant-Ziel ausführen. Dies führt alle Tests im Baum aus, wobei die Datenbankebenentests ignoriert werden, wenn Sie dafür keine Datenbank konfiguriert haben (siehe unten). Die Datenbankebenentests umfassen nur einen kleinen Teil der Testsuite und betreffen nur die Datenbankebene - andere Tests verwenden bei Bedarf In-Memory-Implementierungen der Datenbankebene.
NUnit Console
Wenn Sie nur Tests für eine Assembly ausführen möchten, können Sie dies mit der NUnit Console tun. Auf Linux führen Sie einfach nunit-console2 OpenSim.Foo.Tests.dll aus, und es werden nur die Tests für OpenSim.Foo.Tests.dll ausgeführt. Wenn Sie nur Änderungen an einer DLL vornehmen und nur eine schnelle Überprüfung der Integrität wünschen, ist dies der schnellste Weg.
Jenkins
Bei jedem Commit zu OpenSim werden alle Tests auf dem Jenkins-Build-Server auf opensimulator.org ausgeführt. Der Prozess dauert etwa 5 Minuten, um zu bauen, zu testen und die Ergebnisse über den osmantis-Bot auf #opensim-dev zurückzumelden.
Datenbankebenentests
Die Verbindungszeichenfolgen für die datenbankabhängigen Unittests können in der Datei OpenSim/Data/Tests/Resources/TestDataConnections.ini konfiguriert werden. Diese Datei ist eine eingebettete Ressource, daher muss das Projekt neu kompiliert werden, bevor Änderungen wirksam werden. Wenn die Verbindungszeichenfolgen nicht konfiguriert sind, werden die entsprechenden Tests einfach ignoriert.
Entwicklung von Tests
Mit der Reife von OpenSimulator sind wir sehr daran interessiert, mehr automatisierte Überprüfungen in den OpenSimulator-Quellcode zu integrieren. Tests dienen nicht nur dazu, das Einschleichen von Fehlern zu verhindern, sondern auch als frühzeitige Warnung, dass sich das Verhalten des Systems geändert hat und Tests möglicherweise aktualisiert werden müssen.
In OpenSimulator verwenden wir heute NUnit-Tests. Unsere Konventionen sind:
- Tests sollten nicht in Laufzeit-Assemblies existieren, da dies NUnit zu einer Produktanforderung macht.
- Tests sollten in .Tests.dll-Assemblies sein. Zum Beispiel sollten die Tests für OpenSim.Data.SQLite.dll in der OpenSim.Data.SQLite.Tests.dll-Assembly sein. Dies ermöglicht das einfache Entfernen von Test-Assemblies in Produkten.
- Tests sollten so nah wie möglich am Code sein, aber nicht vermischt. Die Tests für OpenSim/Data/SQLite sollten also in OpenSim/Data/SQLite/Tests/ sein. Durch die Verwendung des Exclude-Schlüsselworts in der prebuild.xml können Sie sicherstellen, dass das Verzeichnis Teil von OpenSim.Data.SQLite.Tests.dll ist und nicht von OpenSim.Data.SQLite.dll. Siehe Ausschlussklarstellung im Abschnitt zum Schreiben von Unittests.
- Tests, die eine Klasse testen, sollten in einer Testklassendatei mit dem Namen xxxTest.cs gruppiert sein, wobei xxx der Name der getesteten Klasse ist.
- Tests sollten sicher in einer Produktionsumgebung ausgeführt werden können. Das bedeutet, dass darauf geachtet werden muss, keine Daten auf dem Gerät zu beschädigen, auf dem es ausgeführt wird.
- Tests sollten deterministisch sein, mit anderen Worten, wiederholbar. Vermeiden Sie Zufälligkeit in Tests. Vermeiden Sie mehrere Threads, wenn möglich - innerhalb von OpenSimulator können möglicherweise spezielle Testmodi erstellt werden, um Einzel-Thread-Tests zu erleichtern. Siehe gute und schlechte Testpraktiken unten.
Fehlende Unittests für Kernfunktionalität
Dies ist eine Liste von Funktionen, die nicht durch Unittests abgedeckt sind und als hoch wünschenswertes Testziel identifiziert wurden:
- Datenbankmodule (Dies sind MySQL-Tabellen)
- Bereich sperren
- Land
- Zugangsliste zum Land
Gute / Schlechte Testpraktiken
Gute Tests zu erstellen ist eine Kunst, keine Wissenschaft. Tests sind nützlich, je nachdem, wie viele Fehler sie finden oder wie viele Fehler sie vermeiden. Dinge, über die Sie beim Erstellen guter Tests nachdenken sollten, sind:
- Randfälle werfen, wie 0, "", oder Null, auf Parameter. Dies stellt sicher, dass Funktionen gegen unvollständige Daten abgesichert sind. Viele unserer Abstürze resultieren aus dem Fehlen dieser Absicherung, die genau zur falschen Zeit auftritt.
- Zufällige Tests sind keine gute Idee. Wir benötigen deterministische Testergebnisse. Tests müssen also wiederholbar sein. Wenn Sie einen Bereich testen möchten, ist es eine gute Idee, separate Tests für Min- und Max-Werte zu erstellen. Zufällige Werte in Feldern können zufällig fehlschlagen. Wenn etwas zum Beispiel im Datenbankschema schief geht, wird der Entwickler möglicherweise nicht bemerken, dass die gespeicherten Werte zufällig sind. Andererseits ist es schwer, zufällig fehlschlagende Tests zu debuggen, da Sie nicht wissen, welcher spezifische Wert den Fehler verursacht hat.
- Tests sollten unabhängig sein und sich nicht darauf verlassen, dass ein anderer Test ausgeführt wird, bestanden oder fehlgeschlagen ist. Ein Auszug aus xUnit Patterns:
Wenn Tests miteinander interagieren und (noch schlimmer) voneinander abhängig sind, berauben wir uns des nützlichen Feedbacks, das Testausfälle bieten. Interagierende Tests [...] neigen dazu, in einer Gruppe zu versagen. Der Ausfall eines Tests, der das [ Testobjekt] in den für den abhängigen Test erforderlichen Zustand versetzt hat, führt ebenfalls zum Ausfall des abhängigen Tests. Mit beiden Tests, die fehlschlagen, wie können wir feststellen, ob es sich um ein Problem im Code handelt, von dem beide auf irgendeine Weise abhängig sind, oder ob es ein Problem im Code ist, von dem nur der erste abhängig ist. Mit beiden fehlgeschlagenen Tests können wir das nicht feststellen. Hier sprechen wir nur von zwei Tests. Stellen Sie sich vor, wie viel schlimmer dies bei zehn oder hundert Tests ist.
- In einem Test sollte nur eine Funktion des Testobjekts getestet werden. Beim Testen eines Datenbankzugriffsobjekts sollten beispielsweise separate Tests zum Erstellen von DB-Einträgen, Aktualisieren von ihnen und Entfernen von ihnen geschrieben werden.
- Verwenden Sie nicht das Testobjekt, um den Zustand für den Test einzurichten oder das Ergebnis zu überprüfen. Verwenden Sie eine andere Methode. Beim Testen eines Datenbankzugriffsobjekts verwenden Sie beispielsweise raw SQL, um die anfänglichen Daten in die Datenbank einzufügen, führen Sie dann die zu testende Methode aus. Um zu überprüfen, ob der Vorgang erfolgreich war, verwenden Sie erneut raw SQL, um zu überprüfen, ob die Datenbank wie erwartet geändert wurde.
- Verwenden Sie immer aussagekräftige Asserts, wenn Sie können. Alles, was Sie tun müssen, ist ein zusätzliches , zur Assert()-Methode hinzuzufügen und einen String zu schreiben, der angezeigt wird, wenn dieser Test fehlschlägt. Zum Beispiel:
Assert.That(i,Is.EqualTo(5),"i ist nicht gleich 5! in Beispiel.Test1()");
- Wenn ein Test aufgrund einer unbehandelten Ausnahme, wie z.B. NullReference, fehlschlägt, gibt NUnit nicht an, wo dies passiert ist, und lässt Debugger im Dunkeln. Eine gute Praxis ist es, etwas am Anfang jedes Tests in Ihrer Testdatei zu schreiben. Auf diese Weise könnte jemand die zuletzt geschriebenen Zeilen lesen, wenn eine Ausnahme auftritt, und sehen, zumindest in welchem Test sie fehlgeschlagen ist. Glücklicherweise ist diese Routine bereits in OpenSim.Tests.Common.TestHelper InMethod() implementiert.
Schreiben von Tests
Siehe NUnit Quick Start für eine Einführung in Unittests mit NUnit.
Das Schreiben eines neuen Unittests ist ziemlich einfach und sehr hilfreich, um die Stabilität von OpenSim durch das Beheben von Fehlern zu erhöhen. Ich werde hier ein Beispiel für das Testen von SQLite-Assets präsentieren, um zu zeigen, wie einfach ein solcher Testfall zu schreiben ist. Die tatsächlichen im Baum vorhandenen SQLite-Asset-Tests sind etwas anders, da der Code so faktoriert wurde, dass er leicht auf jeden Datenbanktreiber angewendet werden konnte. Machen Sie sich also keine Sorgen darüber, dass das, was Sie hier sehen, nicht im Baum ist.
Ausschlussklärung: Stellen Sie sicher, dass Ihr Masterprojekt (nicht das Testprojekt) einen Eintrag für Dateien wie die folgenden hat, damit der Testcode nicht in die Masterprojektdll aufgenommen wird:
```xml <Files>
<Match pattern="*.cs" recurse="true"> <Exclude name="Tests" pattern="Tests" /> </Match>
</Files> ```
NUnit-Konventionen
Eine NUnit-Testsuite:
- ist eine Klasse mit einem Standardkonstruktor (nimmt keine Argumente an)
- hat öffentliche Methoden, die Tests sind
- verwendet Annotationen, um festzustellen, was Tests sind
- führt ihre Tests in alphabetischer Reihenfolge nach Methodennamen aus
Eine NUnit-Testmethode:
- muss öffentlich sein
- muss void zurückgeben
- darf keine Argumente haben
- ist erfolgreich, wenn beim Ausführen keine Ausnahme oder Assert geworfen wird
Sobald die Tests zu NUnit 2.5+ verschoben sind:
- Eine Testklasse kann generisch sein und kann einen oder mehrere Konstruktoren mit Parametern haben. In diesem Fall müssen eine oder mehr [TestFixture(...)]-Attribute verwendet werden, um die Typen für die generischen Argumente und Werte für die Konstruktoren bereitzustellen. Das bedeutet, dass es möglich ist, eine einzelne Testklasse zu erstellen, die beispielsweise Tests für verschiedene Datenbank-Engines bereitstellt.
- Jede Testmethode kann von Attributen wie [TestCase(...)] oder [Values] bereitgestellte Parameter haben. Der Test wird automatisch für verschiedene Kombinationen dieser Attribute ausgeführt.
- NUnit garantiert nicht mehr, dass die Tests in einer bestimmten Reihenfolge durchgeführt werden.
Die Ausführungsreihenfolge ist wichtig, wenn Sie frühe Tests haben möchten, die einen komplizierten Zustand einrichten (wie das Erstellen von Objekten) und spätere Tests diesen Zustand entfernen oder aktualisieren. Aus diesem Grund finde ich es sehr hilfreich, alle Testmethoden immer als Txxx_somename zu benennen, wobei xxx eine Zahl zwischen 000 und 999 ist. Das garantiert keine Überraschungen in der Ausführungsreihenfolge.
Einrichten / Aufräumen von Fixtures
Siehe Beispieltest SQLite-Assets für dieses Code-Schnipsel im Zusammenhang.
```csharp [TestFixtureSetUp] public void Init() {
uuid1 = UUID.Random(); uuid2 = UUID.Random(); uuid3 = UUID.Random(); name1 = "asset one"; name2 = "asset two"; name3 = "asset three";
asset1 = new byte[100]; asset1.Initialize(); file = Path.GetTempFileName() + ".db"; connect = "URI=file:" + file + ",version=3"; db = new SQLiteAssetData(); db.Initialise(connect);
}
[TestFixtureTearDown] public void Cleanup() {
db.Dispose(); System.IO.File.Delete(file);
} ```
Im Falle des Testens von etwas wie der Datenbankebene müssen wir tatsächlich versuchen, Dinge in einer Datenbank zu speichern/abzurufen. Gemäß Regel Nr. 4 guter Tests möchten wir sicherstellen, dass wir die Produktionsdatenbanken nicht berühren, um unsere Tests auszuführen. Daher generieren wir beim Start einen temporären Dateinamen, der garantiert nicht bereits auf dem System existiert, und verwenden diesen als unseren Datenbankdateinamen. Durch Ausführen von `db.Initialize()` wird der OpenSimulator-Migrationscode diese Datenbank korrekt mit dem neuesten Schema füllen.
Nach Abschluss der Tests möchten wir sicherstellen, dass wir keine unnötigen temporären Dateien auf dem System des Benutzers hinterlassen. Daher entfernen wir diese erstellte Datei.
Während des Setups erstellen wir auch eine Reihe von Statusvariablen, wie z.B. 3 UUIDs, 3 Zeichenketten und einen Datenblock. Sie könnten diese immer direkt eingefügt haben, aber Variablen sind aus einem Grund vorhanden, also verwenden Sie sie.
Test-Setup / -Teardown
Was in Beispieltest SQLite-Assets fehlt, sind individuelle Test-Setup- und -Teardown-Methoden. Diese Methoden ermöglichen es jedem Test, vollständig eigenständig zu sein, ohne den Code-Duplikationsbedarf für die Einrichtung der Testumgebung am Anfang jedes Tests.
Nehmen wir an, die Klasse `SQLiteAssetData
` bietet eine `FetchAsset()
`-Methode und eine `UpdateAsset()
`-Methode an. Da jeder Test unabhängig von jedem anderen Test sein sollte und FetchAsset()
und UpdateAsset()
in separaten Tests getestet werden sollten, bedeutet dies, dass jeder Test seine eigenen Einträge in der Asset-Tabelle erstellen muss, um erfolgreich zu sein. Sie könnten etwas wie dies haben (siehe Beispieltest SQLite-Assets für eine Erklärung von `sqldb.executeSQL()
`):
```csharp [Test] public void TestFetchAsset() {
AssetBase a1 = new AssetBase(...); sqldb.executeSQL("INSERT INTO assets VALUES({0}, ...)", a1.uuid, ...);
AssetBase a1_actual = db.FetchAsset(a1.uuid);
Assert.Equal(a1_actual.uuid, a1.uuid); Assert.Equal(a1_actual.Name, a1.Name); // etc
}
[Test] public void TestUpdateAsset() {
AssetBase a1 = new AssetBase(...); sqldb.executeSQL("INSERT INTO assets VALUES({0}, ...)", a1.uuid, ...);
a1.Name = "neuer Name";
db.UpdateAsset(a1.uuid, a1);
AssetBase a1_actual = sqldb.executeSQL("SELECT * FROM assets WHERE uuid = {0}", a1.uuid);
Assert.Equal(a1_actual.uuid, a1.uuid); Assert.Equal(a1_actual.Name, a1.Name); // etc
} ```
Sie werden feststellen, dass beide Tests denselben Code oben haben, in dem sie einen Eintrag in die Assets-Tabelle erstellen. Dieser Duplikatcode kann in eine Setup-Methode ausgegliedert werden, die vor jedem Test ausgeführt wird (nehmen Sie an, `a1
` ist ein Klassenattribut):
```csharp [SetUp] public void SetUp() {
a1 = new AssetBase(...); sqldb.executeSQL("INSERT INTO assets VALUES({0}, ...)", a1.uuid, ...);
}
[TearDown] public void TearDown() {
// nach uns aufräumen, damit der nächste Test eine saubere DB hat sqldb.executeSQL("DELETE FROM assets");
}
[Test] public void TestFetchAsset() {
AssetBase a1_actual = db.FetchAsset(a1.uuid);
Assert.Equal(a1_actual.uuid, a1.uuid); Assert.Equal(a1_actual.Name, a1.Name); // etc
}
[Test] public void TestUpdateAsset() {
a1.Name = "neuer Name";
db.UpdateAsset(a1.uuid, a1);
AssetBase a1_actual = sqldb.executeSQL("SELECT * FROM assets WHERE uuid = {0}", a1.uuid);
Assert.Equal(a1_actual.uuid, a1.uuid); Assert.Equal(a1_actual.Name, a1.Name); // etc
} ```
Beachten Sie auch die Methode `TearDown()
`; sie wird nach jedem Test aufgerufen, unabhängig davon, ob der Test bestanden oder fehlgeschlagen ist. Sie löscht alle Einträge in der `assets
`-Tabelle, damit in der Datenbank keine überflüssigen Daten für den nächsten Test vorhanden sind.
```
```markdown
Mehrere Setup-Methoden
Nicht alle Einrichtungs- und Aufräumarbeiten müssen in den Methoden [SetUp] und [TearDown] erfolgen. Es kann nützlich sein, Methoden bereitzustellen, die einen Teil der Einrichtung durchführen, und sie von jedem Test aus aufzurufen, der sie benötigt:
private AssetBase InsertAssetWithRandomData(UUID assetUuid) { AssetBase asset = new AssetBase(assetUuid); asset.Name = somethingRandom(); asset.Data = somethingRandom(); sqldb.executeSQL("INSERT INTO assets VALUES({0}, ...)", asset.uuid, ...); return asset; } [Test] public void TestNeedsTwoAssets() { AssetBase a1 = InsertAssetWithRandomData(uuid1); AssetBase a2 = InsertAssetWithRandomData(uuid2); // etc } [Test] public void TestNeedsFiveAssets() { AssetBase a1 = InsertAssetWithRandomData(uuid1); AssetBase a2 = InsertAssetWithRandomData(uuid2); AssetBase a3 = InsertAssetWithRandomData(uuid3); AssetBase a4 = InsertAssetWithRandomData(uuid4); AssetBase a5 = InsertAssetWithRandomData(uuid5); // etc }
Beachten Sie, dass InsertAssetWithRandomData
als private
deklariert ist, da es nur innerhalb der Klasse aufgerufen wird.
Asserts
Im Code sehen Sie an verschiedenen Stellen Assert.That(...). Diese werfen eine Ausnahme, wenn die Bedingung nicht gültig ist. Diese Form von Behauptungen wird im NUnit als Constraint Model bezeichnet und bietet eine große Anzahl von Tests mit dem Geschmack von:
- Assert.That(foo, Is.Null)
- Assert.That(foo, Is.Not.Null)
- Assert.That(foo, Is.True)
- Assert.That(foo, Is.EqualTo(bar))
- Assert.That(foo, Text.Matches( "*bar*" ))
Bemerkenswert ist, dass Is.EqualTo die Equals-Funktion von foo verwendet, sodass dies nur bei Objekten verwendet werden kann, die IComparable sind. Die meisten OpenSimulator-Basisklassen sind dies nicht, daher müssen Sie Felder in Tests manuell vergleichen.
Für den vollständigen Satz von Bedingungen können Sie die Constraint Model NUnit-Dokumentation einsehen. Obwohl es eine andere Syntax für Tests gibt, wird das Constraint Model bevorzugt, da es wesentlich besser lesbar ist.
Einfache Negative Tests
Siehe Beispieltest SQLite-Assets für diesen Code-Schnipsel im Kontext.
[Test] public void TestLoadEmpty() { Assert.That(db.ExistsAsset(uuid1), Is.False); Assert.That(db.ExistsAsset(uuid2), Is.False); Assert.That(db.ExistsAsset(uuid3), Is.False); }
Test T001 ist ein Beispiel für einen einfachen negativen Test. Wir gehen davon aus, dass eine neue Datenbank keine dieser Assets enthält. Obwohl der Wert dieses Tests niedrig erscheinen mag, bietet er eine Grundlinie, um sicherzustellen, dass die Datenbankverbindung vorhanden ist, dass diese richtig false zurückgeben und dass keine andere Ausnahme ausgelöst wird. Negative Tests sind eine gute Möglichkeit, Grenzwerte zu erzwingen und sicherzustellen, dass Ihr Code nicht nur das zurückgibt, was Sie erwarten, sondern auch das, was Sie nicht erwarten. Anders ausgedrückt stellt dies sicher, dass Ihr Code in gewisser Weise defensiv ist und nicht auf schlechte oder unerwartete Daten abbricht.
Einfache Positive Tests
Siehe Beispieltest SQLite-Assets für diesen Code-Schnipsel im Kontext.
[Test] public void TestStoreSimpleAsset() { AssetBase a1 = new AssetBase(uuid1, name1); AssetBase a2 = new AssetBase(uuid2, name2); AssetBase a3 = new AssetBase(uuid3, name3); a1.Data = asset1; a2.Data = asset1; a3.Data = asset1; db.CreateAsset(a1); db.CreateAsset(a2); db.CreateAsset(a3); AssetBase a1a = db.FetchAsset(uuid1); Assert.That(a1a.ID, Is.EqualTo(uuid1)); Assert.That(a1a.Name, Is.EqualTo(name1)); AssetBase a2a = db.FetchAsset(uuid2); Assert.That(a2a.ID, Is.EqualTo(uuid2)); Assert.That(a2a.Name, Is.EqualTo(name2)); AssetBase a3a = db.FetchAsset(uuid3); Assert.That(a3a.ID, Is.EqualTo(uuid3)); Assert.That(a3a.Name, Is.EqualTo(name3)); }
T010 ist ein Beispiel für einen einfachen positiven Test. Darin erstellen und speichern wir 3 Assets (ohne dass Ausnahmen auftreten), laden dann diese 3 Assets aus der Datenbank zurück und stellen sicher, dass die Felder korrekt sind. Da AssetBase nicht IComparible ist, überprüfen wir nur die ID- und Name-Felder mit Gleichheitstests. Wenn eines der Asserts fehlschlägt, schlägt der gesamte Test fehl.
Spekulative Tests
Spekulative Tests sind Tests, die in einer bestimmten Situation möglicherweise anwendbar sind oder nicht. Das Testen von MySQL im OpenSimulator-Tree erfolgt durch spekulative Tests. Die Tests werden nur ausgeführt, wenn eine ordnungsgemäß konfigurierte Datenbank vorhanden ist, andernfalls werden sie nicht ausgeführt. Wenn Sie in einem Test Assert.Ignore() ausführen, wird der Test beendet und ignoriert. Wenn Sie in TestFixtureSetup Assert.Ignore() ausführen, werden alle Tests im Test-Fixture übersprungen und ignoriert.
Spekulatives Testen ermöglicht es Ihnen, Tests zu erstellen, die bestimmte Voraussetzungen erfordern, die Sie nicht auf allen Plattformen/Konfigurationen garantieren können, und ist ein wichtiger Bestandteil des tiefen Testens.
Hinzufügen von Tests zum Projekt
Wie bereits erwähnt, sollten alle Tests für die Assembly OpenSim.Foo (im Verzeichnis OpenSim/Foo):
- in der Assembly OpenSim.Foo.Tests.dll sein
- nicht in der OpenSim.Foo.dll-Assembly sein
- im Verzeichnis OpenSim/Foo/Tests sein
Außerdem müssen Sie, wenn Sie eine neue Testassembly erstellt haben, Referenzen in .nant/local.include hinzufügen, um sicherzustellen, dass die Assembly dem automatisierten kontinuierlichen Integration
stestserver sowie dem Nant-test-Ziel hinzugefügt wird.
Zum Beispiel:
<exec program="${nunitcmd}" failonerror="true" resultproperty="testresult.opensim.my.new.tests"> <arg value="./bin/OpenSim.Framework.My.New.Tests.dll" /> </exec> <fail message="Fehler in den Unit-Tests gemeldet." unless="${int::parse(testresult.opensim.my.new.tests)==0}" />
Debugging von Tests
Hierfür gibt es eine spezielle Seite. Siehe Debugging Unit Tests.
Weitere Informationen
Sie sollten auf jeden Fall die Dokumentation auf der NUnit-Homepage lesen, wenn Sie mehr über das Testen erfahren möchten. Es ist eine sehr gute Referenz für alle APIs in NUnit, die Sie für die Erstellung von Tests verwenden können.
Links zu weiteren Informationen über das Unit Testing
- NUnit Schnellstart
- xUnit Patterns Buchhomepage, mit vielen Informationen zu bewährten Methoden, Mustern, Code-Smells, usw.
- Es gibt viele Informationen zum Unit Testing auf der Cunningham & Cunningham, Inc Wiki auf c2.com
```