Wir schreiben gerne Tests und das meinen wir ernst.
Damit das für alle von uns so bleibt benutzen wir einen modernen Test Stack und vereinfachen unseren Testalltag soweit wie möglich. Dazu gehört auch, dass wir zu unseren Projekten keine Mehrseitigen Dokumentationen zum Test Setup mitliefern müssen. Wie machen wir das? So!
Wenn wir Tests schreiben dann folgenden wir diesen Grundsätzen:
Als Testdriver setzen wir auf Xunit. Es wird aktiv entwickelt und lässt sich mit der aktuellsten .NET Plattform benutzen.
Fluent Assertions ermöglichen uns die Tests lesbar zu schreiben und drücken die Intention der Autorin oder des Autors klar aus.
So schön Fluent Assertions auch sind, wir wollen es nicht übertreiben. Auch der Vergleich von ganzen JSON Dokumenten ist eher aufwändig mit herkömlichen Methoden. Hier schafft Snapshot Testing abhilfe. Wir benutzen dazu Verify.
Es gibt sogar ein Plugin für Jetbrains basierte IDEs.
Wir möchten unserem Projekt keine mehrseiten Dokumentation für das Test Setup beilegen wollen. Für ein einfaches On-Boarding von neuen Entwickler*innen haben wir uns für FluentDocker entschieden.
Fluent Assertions erlaubt uns elegante Assertions zu schreiben, die auch die Motivation für die Assertion klar zum Ausdruck bringen.
Ein Beispiel dazu findet man auf der Website:
string actual = "ABCDEFGHI";
actual.Should()
.StartWith("AB")
.And.EndWith("HI")
.And.Contain("EF")
.And.HaveLength(9);
Die Intention lässt sich auch in einer allfälligen Fehlermeldung für die Assertion klar hinterlegen:
IEnumerable<int> numbers = new[] { 1, 2, 3 };
numbers.Should().OnlyContain(n => n > 0);
numbers.Should().HaveCount(4,
"because we thought we put four items in the collection");
Ausserdem produziert Fluent Assertions sinnvolle und hilfreiche Fehlermeldungen:
string username = "dennis";
username.Should().Be("jonas");
Die Fehlermeldung sieht dann folgendermassen aus:
Expected username to be "jonas" with a length of 5,
but "dennis" has a length of 6, differs near "den" (index 0).
Wenn man ganze Objekte oder JSON Strings miteinander vergleichen möchte, macht es solten Sinn dies mit einer Assertion Library zu machen. Hier helfen sogenannte Snapshot Erweiterungen weiter. Das Prinzip ist einfach.
Einige Proprties sind dynmaisch und lassen sich nicht über Snapshots testen. Nehmen wir an, dass wir die Id für ein Objekt fortlaufend generieren. Die Verify Library bietet uns dazu ein Settings Objekt an:
var myId = 123;
var myObject = new MyObject { Id = 123 };
var verifySettings = new VerifySettings();
var verifySettings.ModifySerialization(_ =>
{
_.IgnoreMember("Id");
});
...
<Do something important>
...
await Verifier.Verify(myObject, _verifySettings);
myObject.Id.Should.Be(myId);
Wenn dann Änderungen im Code gemacht werden, wiederholt man die Schritte 2, 3 und 4 und schon sind die Tests wieder auf dem neusten Stand.
Für End-To-End Tests verwenden wir Fluent Docker.. So können die Tests ohne Aufwand auf beliebigen Servern (CI / CD) und Computern (Entwickler*innen) ausgeführt werden.
Dazu legen wir eine docker-compose.yml Datei an. Darin werden alle nötigen Services und abhängigkeiten definiert. Diese können dann vor dem effektiven Test Durchlauf hoch- und danach wieder runtergefahren werden.
var dockerComposeFile = Path.Combine(Directory.GetCurrentDirectory(),
"docker-compose.yaml");
var hosts = new Hosts().Discover();
var dockerHost = hosts.FirstOrDefault(x => x.IsNative) ??
hosts.FirstOrDefault(x => x.Name == "default");
Service = new DockerComposeCompositeService(dockerHost,
new DockerComposeConfig
{
ComposeFilePath = new List<string> { dockerComposeFile },
ForceRecreate = true,
RemoveOrphans = true,
StopOnDispose = true,
}
);
Service.Start();
Assert.Equal(ServiceRunningState.Running, Service.State);
Assert.Equal(5, Service.Containers.Count);
Condition.Await(() =>
Service.Containers
.Any(c => c.State != ServiceRunningState.Running), 30 * 1000);