“Płynne asercje”, czyli jak ułatwić sobie życie korzystając z Fluent Assertions?

Wzorzec projektowy “fluent interface” (polski odpowiednik… płynny interfejs…?) przyjął się w środowisku .NETowym bardzo dobrze. I słusznie! “Płynna syntaktyka” znacznie poprawia czytelność pisanego kodu.

Jednym z sztandarowych przykładów jej użycia są asercje w testach. Pytanie który z poniższych zapisów jest bardziej naturalny?

Zwykły proszek, ekhm asercja:

Assert.AreEqual("Jan", firstName);

czy odpowiednik “fluent”:

firstName.ShouldBe("Jan");

Wybór pozostawiam Tobie, czytelnikowi, gdyż każdy ma swój gust. Niemniej jednak warto zaznajomić wiedzę na temat płynnych asercji.

Dla C# pojawiło się kilka bibliotek wspierających zapis asercji w sposób “płynny”, m.in. Fluent Assertions, Shouldly, Should Assertion Library. Wszystkie biblioteki są do siebie podobne, polecam zaznajomić się z każdą by wyrobić sobie opinię i samemu wybrać najodpowiedniejszą. Najczęściej korzystam z dwóch pierwszych, natomiast w tym wpisie chciałbym przyjrzeć się bibliotece Fluent Assertions.

Proste asercje

Nawiązując do pierwszego przykładu, zapis w Fluent Assertions będzie wyglądać następująco:

firstName.Should().Be("Jan");

Co więcej, nasze asercje możemy łączyć za pomocą koniunkcji:

firstName.Should().StartWith("Ag").And.EndWith("a").And.Contain("szk");

Dokumentacja Fluent Assertions przedstawia sporo przykładów zastosowania biblioteki w praktyce. Poniżej wkleiłem proste i co ciekawsze przykłady, aby pokazać w jaki sposób biblioteka radzi sobie z asercjami dla obiektów, kolekcji, liczb, dat, typów Booleanowych:

// Obiekty:
theObject.Should().BeNull();
theObject.Should().NotBeNull();
theObject.Should().BeOfType();
theInt.Should().HaveValue(); // Nullable type

// Boolean:
theBoolean.Should().BeTrue();
theBoolean.Should().BeFalse();

// Stringi:
theString.Should().BeNullOrWhiteSpace();
theString.Should().Contain("is a");
theString.Should().NotContain("is a");
theString.Should().BeEquivalentTo("THIS IS A STRING"); // case insensitive
theString.Should().StartWith("This");
emailAddress.Should().Match("*@*.com"); // wildcards
emailAddress.Should().MatchEquivalentOf("*@*.COM"); // case insensitive
someString.Should().MatchRegex("h.*\\sworld.$");

// Typy numeryczne:
number.Should().BeGreaterOrEqualTo(5);
number.Should().BeGreaterThan(4);
number.Should().BeLessOrEqualTo(5);
number.Should().BeLessThan(6);
number.Should().BePositive();
number.Should().Be(5);
number.Should().NotBe(10);
number.Should().BeInRange(1,10);

// Daty:
theDatetime.Should().BeAfter(1.February(2010));
theDatetime.Should().BeBefore(2.March(2010));
theDatetime.Should().BeOnOrAfter(1.March(2010));
theDatetime.Should().Be(1.March(2010).At(22, 15));
theDatetime.Should().NotBe(1.March(2010).At(22, 16));
theDatetime.Should().HaveDay(1);
theDatetime.Should().HaveMonth(3);
theDatetime.Should().HaveYear(2010);
theDatetime.Should().HaveHour(22);
theDatetime.Should().HaveMinute(15);
theDatetime.Should().HaveSecond(0);

// Kolekcje:
collection.Should().NotBeEmpty()
.And.HaveCount(4)
.And.ContainInOrder(new[] { 2, 5 })
.And.ContainItemsAssignableTo();
collection.Should().HaveCount(3);
collection.Should().ContainSingle(x => x > 3);
collection.Should().NotContain(x => x > 10);
collection.Should().NotContainNulls();
collection.Should().BeEmpty();
collection.Should().BeNullOrEmpty();
collection.Should().NotBeNullOrEmpty();
collection.Should().IntersectWith(otherCollection);

Asercje dla wyjątków

Aby wykorzystać FluentAssertions w łapaniu wyjątków należy zadeklarować metodę, która rzuca wyjątkiem do zmiennej typu Action, a następnie wywołać metodę ShouldThrow. Wygląda to następująco:

Action act = () => obiekt.Foo(null);

act.ShouldThrow();

Bajecznie proste i czytelne.

Komunikaty asercji

Jednym z kluczowych czynników oceny bibliotek do testowania jest sposób komunikacji w przypadku testów czerwonych. Fluent Assertions radzi sobie na tym polu bardzo dobrze.

Przykładowy komunikat dla kolekcji, która ma mieć 4 elementy, a w rzeczywistości (wirtualnej) ma 3:

"Expected 4 items because we thought we put three items in the collection, but found 3."

Kompatybilność

Fluent Assertions działa z bibliotekami:

  • MSTest (Visual Studio 2010, 2012 Update 2, 2013 and 2015)
  • NUnit
  • XUnit, XUnit2
  • MBUnit
  • Gallio
  • NSpec
  • MSpec

Werdykt

Jest to kwestia czysto-subiektywna, ale mi osobiście bardziej odpowiada pod względem czytelności zapis asercji w wersji fluent niż standardowej. Przyznam szczerze, że odzwyczaiłem się już od pisania zwykłych asercji (Assert.That, Assert.AreEqual, itd.) na rzecz Fluent Assertions. Polecam zaznajomić się z tą biblioteką, a jak już będziecie w jej obsłudze “fluent”, to polecam poeksperymentować z konkurencją – Shouldly, Should Assertion Library, itp.

Źródła i linki

3 thoughts on ““Płynne asercje”, czyli jak ułatwić sobie życie korzystając z Fluent Assertions?

  1. Pingback: Kurs TDD cz. 14: Testowanie zależności – atrapy obiektów | DariuszWoźniak .NET

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s