C# · Programowanie

AutoFixture – jak zacząć?

Zaczniemy od tego, co tak na prawdę robi AutoFixture? Według dokumentacji: pozwala nam w testach tworzyć anonimowe zmienne. Dzięki nim nie musimy ręcznie tworzyć obiektów, które nie są istotne w kontekście samego testu, ale są wymagane do skompilowania kodu.

Można go używać na kilka sposobów:

  1. deklarując odpowiednie zmienne w kodzie
  2. definiując odpowiednie zmienne w wywołaniu metody

Zajmijmy się najpierw tym pierwszym przypadkiem.

Podejście pierwsze – zmienne

Na samym początku można napisać prosty test:

[Fact]
public void IntroductoryTest()
{
    // Arrange
    Fixture fixture = new Fixture();
    int expectedNumber = fixture.Create<int&gt;();
    TestClass sut = fixture.Create<TestClass&gt;();
    // Act
    int result = sut.Echo(expectedNumber);
    // Assert
    Assert.Equal(expectedNumber, result);
}

W powyższym teście deklarujemy obiekt typu Fixture. Potem z wykorzystaniem go, deklarujemy anonimową zmienną expectedNumber o typie int, oraz zmienną sut, o typie TestClass. Jak widać, nie musimy nigdzie deklarować konkretnej wartości zmiennych

var expectedNumer = 123;

czy

var sut = new TestClass();

Co ważne – po wygenerowaniu przez AutoFixture obiektu, za każdym razem będzie on inny. Tyczy się to również jego właściwości.

Podejście drugie – atrybuty

Innym podejściem jest używanie atrybutów. Pozwala to oszczędzić trochę kodu:

[Theory]
[AutoData]
public void IntroductoryTest(int expectedNumber, TestClass sut)
{
    // Act
    int result = sut.Echo(expectedNumber);
    // Assert
    Assert.Equal(expectedNumber, result);
}

Przy takim podejściu nie mamy w ogóle części Arrange. Nie musimy tworzyć sztucznego obiektu Fixture, ani deklarować zmiennych. Wystarczy że użyjemy atrybutu AutoData i dodamy odpowiednie parametry metody. Całą resztę zrobi za nas AutoFixture.

Warto pamiętać, że obydwa atrybuty można umieścić w jednej linii:

[Theory, AutoData]

Według mnie jest jednak czytelniej, gdy mamy je zapisane osobno.

Wstrzykiwanie zależności i atrybut [Frozen]

Może okazać się jednak, że czasami potrzebujemy wstrzyknąć zależność. Np. możemy mieć klasę TestService, z której chcemy wywołać fizyczną metodę, ale może mieć ona zależność do ITimeService, którą chcemy już zmockować (żeby operowała na sztucznych danych dotyczących czasu).

public class TestService(ITimeService timeService)
{
   \\ implementacja
}

Jak to napisać w teście?

[Theory]
[InlineAutoMoqData]
public void IntroductoryTest(
    User expectedUser, 
    DateTime expectedModificationDate,
    [Frozen] Mock timeService,
    TestService sut)
{
    // Arrange
    timeService.Setup(m => m.GetUtcNow())
        .Returns(expectedModificationDate);
    // Act
    await sut.UpdateUser(expectedUser);
    // Assert
    Assert.Equal(expectedModificationDate, expectedUser.ModificationDate);
    timeService.Verify(s => s.GetUtcNow(), Times.Once);
}

Co za magia stała się w tle? Co naprawdę testujemy? Sprawdzamy, czy podczas wywołania metody UpdateUser() została wywołana metoda GetUtcNow() i w obiekcie User właściwość ModificationDate została ustawiona na wartość expectedModificationDate.

Dodatkowo sprawdzamy, czy ta metoda wywołała się tylko raz.

Co ważne, kolejność parametrów nie jest przypadkowa! Parametr z atrybutem Frozen musi poprzedzać parametr, do którego ma zostać wstrzyknięty. Widać to m.in. w tym prostym przykładzie na stringach.

Oczywiście AutoFixture ma o wiele szersze zastosowanie (np. generowanie losowego ciągu znaków o określonej długości). W tym poście opisałam tylko podstawy i konkretny przypadek, który wykorzystałam w mojej pracy.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Google

Komentujesz korzystając z konta Google. Wyloguj /  Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s