Nowa składnia asercji w NUnit 2.4

Podstawą każdego testu jednostkowego jest asercja, czyli polecenie sprawdzenia, czy testowany obiekt spełnia pewną własność. W wersjach NUnit wcześniejszych niż linia 2.4 podstawowym sposobem do wyrażenia asercji w kodzie testu było użycie statycznych metod klasy Assert z przestrzeni nazw NUnit.Framework. Na przykład:
[Test]
public void StringBuilderShouldGlueStringsTogether() {
    StringBuilder builder = new StringBuilder();
    builder.Append("NUnit ");
    builder.Append("2.4.7");
    Assert.AreEqual("NUnit 2.4.7", builder.ToString());

}
Podstawowym i najczęściej chyba używanym typem asercji jest sprawdzenie, czy dwie wartości są sobie równe (metoda Assert.AreEqual), tak jak w powyższym przykładzie. Klasa Assert dostarcza jeszcze wielu innych metod, które pozwalają w łatwy sposób sprawdzać własności obiektów. Na przykład:
[Test]
public void MathSinShouldReturnValuesGreaterOrEqualToMinusOne() {
    // Nigdy nie powinniśmy losować wartości w testach!
    Random generator = new Random();
    double argument = generator.NextDouble();
    double sin = Math.Sin(argument);
    Assert.GreaterOrEqual(sin, -1.0);

}
Oprócz klasy Assert mamy do dyspozycji także bardziej wyspecjalizowane klasy:
Klasy te umożliwiają łatwe sprawdzanie bardziej wyszukanych własności, takich jak na przykład to, czy kolekcja zawiera jakiś element:
[Test]
public void CollectionShouldContainItsElements() {
    int[] integers = new int[] { 1, 2, 3, 4, 5 };
    CollectionAssert.Contains(integers, 3);

}
W wersji 2.4 biblioteki NUnit wprowadzono nowy sposób wyrażania asercji oparty o ograniczenia (ang. constraint model). W tym modelu do wyrażenia każdej asercji używamy jednej i tej samej metody Assert.That, a logika związana ze sprawdzeniem własności testowanego obiektu jest zaimplementowana w odpowiednim obiekcie, który przekazujemy jako jeden z parametrów metody Assert.That. Obiekty te noszą nazwę ograniczeń. Na przykład:
[Test]
public void StringBuilderShouldGlueStringsTogether() {
    StringBuilder builder = new StringBuilder();
    builder.Append("NUnit ");
    builder.Append("2.4.7");
    Assert.That(
        builder.ToString(),
        new EqualConstraint("NUnit 2.4.7"));

}
Obiekt klasy EqualConstraint (z przestrzeni nazw NUnit.Framework.Constraints) implementuje logikę zawartą poprzednio w kodzie metody Assert.AreEqual. W czym ten sposób wyrażania asercji jest lepszy od poprzedniego? Siła nowego modelu leży w klasach pomocniczych zawartch w przestrzeni nazw NUnit.Framework.SyntaxHelpers, które pozwalają w łatwy sposób tworzyć i łączyć obiekty implementujące ograniczenia. Klasy pomocnicze, o których tutaj mowa, stanowią fluent interface (opisany przez Martina Fowlera), co czyni API do tworzenia obiektów implementujących ograniczenia bardzo czytelnym i intuicyjnym w użyciu. Niech przykłady mówią same za siebie:
[Test]
public void StringBuilderShouldGlueStringsTogether() {
    StringBuilder builder = new StringBuilder();
    builder.Append("NUnit ");
    builder.Append("2.4.7");
    Assert.That(builder.ToString(), Is.EqualTo("NUnit 2.4.7"));

}
[Test]
public void MathSinShouldReturnValuesGreaterOrEqualToMinusOne() {
    // Nigdy nie powinniśmy losować wartości w testach!
    Random generator = new Random();
    double argument = generator.NextDouble();
    double sin = Math.Sin(argument);
    Assert.That(sin, Is.GreaterThanOrEqualTo(-1.0));

}
[Test]
public void CollectionShouldContainItsElements() {
    int[] integers = new int[] { 1, 2, 3, 4, 5 };
    Assert.That(integers, Has.Member(3));

}
Miłego testowania!
Opublikowane 08 czerwca 08 12:04 przez Marcin Hoppe
Filed under: ,

Komentarze:

# Procent said on czerwca 8, 2008 13:01:

"Fluent interface" to strasznie fajna sprawa. Wprowadzenie tego designu do nUnit to zdecydowanie dobre posunięcie:). Wcześniej mogliśmy użyć go w praktyce między innymi w nMock i RhinoMocks.

# Marcin Hoppe said on czerwca 8, 2008 13:32:

Fluent interface to tylko jedna strona medalu. Nowy model umożliwia rozszerzanie NUnit o własne asercje i ograniczenia (wystarczy zaimplementować niezbyt skomplikowany interfejs IConstraint). Z tego, co widzę, to całość oparta jest o wzorzec composite, więc nasze własne ograniczenia powinny świetnie działać ze "złożonymi" ograniczeniami z NUnit, takimi jak np. NotConstraint (łatwo zgadnąć co to robi i jak działa).

Komentarze anonimowe wyłączone