Artykuł

sxc.hu sxc.hu
lut 23 2014
0

Mockowanie obiektów w praktyce z biblioteką Moq

W procesie tworzenia oprogramowania, czymś naturalnym jest pisanie kodu, który będzie składał się z zależności w postaci łańcucha wywołań wielu różnych obiektów. Dzieje się tak często, ponieważ wielokrotnie wykonanie pozornie prostej czynności przez użytkownika, jest poprzedzone szeregiem złożonych operacji w logice biznesowej aplikacji. Co zrobić zatem w przypadku, gdy chcemy przetestować funkcjonalność na końcu tego łańcucha zależności? Czy musimy tworzyć konkretne instancje wszystkich obiektów po drodze i liczyć na łut szczęścia, że za każdym razem uda nam się choćby dojść do naszej konkretnej metody? Otóż nie. Są lepsze rozwiązania, a jednym z nich jest tzw. Mockowanie obiektów (zaślepianie obiektów).

Dziś mam zamiar zaprezentować Wam w praktyce działanie bardzo prostej biblioteki Moq, która będzie symulować pracę niektórych struktur w naszej aplikacji.

Mockowanie w teorii

Na wstępie warto sobie wyjaśnić, czym tak naprawdę jest mockowanie. W sztuce programowania/testowania jest to proces, w którym konkretną implementację obiektu, zastępujemy sztucznym tworem, który jest w stanie zachować się dokładnie tak jak od niego oczekujemy. Innymi słowy jeśli oczekujemy że pewna metoda X obiektu Y, ma zwrócić Z, to Moq (oraz inne tego typu biblioteki) daje nam gwarancję, że tak się właśnie stanie i że nic po drodze się nie wysypie (nie pojawi się żaden nieoczekiwany wyjątek).

Dzięki temu będziemy w stanie przetestować konkretną metodę w dalszej części łańcucha zależności skupiając się już tylko na jej działaniu.

Przykładowy projekt

By móc przetestować działanie mockowania w praktyce, musimy najpierw utworzyć kawałek konkretnego kodu. W tym celu, stworzymy na szybko małe repozytorium produktów oraz prosty kalkulator do liczenia ich wartości. Naszym głównym zadaniem będzie przetestowanie właśnie funkcjonalności kalkulatora, dlatego też nie do końca będzie nas obchodzić konkretna implementacja katalogu produktów. Spójrzcie na poniższy kod przykładowej aplikacji konsolowej:

public class Product
{
    public string Name { get; set; }
    public decimal Value { get; set; }
}

public interface IProductsCatalog
{
    IEnumerable<Product> Products { get; }
}

public class ProductsCatalog : IProductsCatalog
{
    /* Implement class logic */

    public IEnumerable<Product> Products
    {
        get { throw new NotImplementedException(); }
    }
}

public class Calculator
{
    private IProductsCatalog _productsContainer = null;

    public Calculator(IProductsCatalog productsContainer)
    {
        _productsContainer = productsContainer;
    }

    public decimal SumProductsValues()
    {
        return _productsContainer.Products.Sum(product => product.Value);
    }
}

Przyjrzymy się pokrótce co tu się w praktyce dzieje.

Najniżej w hierarchii znajduje się klasa Produkt (1-5), która posiada dwie publiczne właściwości reprezentujące nazwę oraz wartość (dla nas szczególnie istotna jest ta druga).

W następnym kroku mamy interfejs reprezentujący katalog produktów (7-10) oraz jego konkretną implementację (12-20). Ponieważ w tym właśnie miejscu chcemy wstawić nasz sztuczny obiekt, dlatego też opuszczamy szczegóły implementacji tej konkretnej klasy. Tego rodzaju katalog produktów może być przecież bardzo problematyczny - np. mogą pojawić się problemy w trakcie dodawania produktu itp. Dla uproszczenia nasz interfejs zawiera tutaj jednak tylko jedną właściwość, która zwraca już konkretną listę produktów (tyle nam wystarczy na potrzeby testu).

Zwieńczeniem listingu jest klasa Calculator (22-35), która na bazie katalogu produktów, jest w stanie obliczyć wartość wszystkich towarów w nim umieszczonych.

Tak jak napisałem wcześniej, brakuje tutaj konkretnej logiki katalogu produktów, aczkolwiek na potrzeby naszych testów, nie jest ona nam w tej chwili do niczego potrzebna.

Testowy projekt

W momencie gdy mamy już kompilujący się kod głównej aplikacji, dodamy do naszej solucji Unit Test Project (dla uproszczenia nie będziemy wstawiać żadnych zewnętrznych bibliotek typu NUnit).

W kolejnym kroku będziemy musieli dodać również tytułową bibliotekę Moq do naszego projektu. Najłatwiej jest to oczywiście uczynić wykorzystując NuGeta (PPM na projekcie, a następnie Manage NuGet packages). Alternatywnie możemy pobrać biblioteczkę ze strony projektu.

Konfigurację kończymy dodaniem referencji do naszego wcześniej utworzonego projektu (PPM na projekcie, a następnie Add, Reference).

W tym momencie możemy już przystąpić do przygotowania konkretnego testu jednostkowego. Do dodanej automatycznie przez Visuala klasy UnitTest1 dodamy nową metodę testującą:

[TestMethod]
public void TestCalculatorSum()
{
    // Arrange 
    Mock<IProductsCatalog> mock = new Mock<IProductsCatalog>();
    mock.Setup(m => m.Products).Returns(new Product[] {
        new Product { Name = "Monitor", Value = 399.99M },
        new Product { Name = "Myszka", Value = 69.59M }
    });
    Calculator calc = new Calculator(mock.Object);
    // Act
    decimal result = calc.SumProductsValues();
    // Assert
    Assert.AreEqual(result, 469.58M);
}

Jak widać mamy tutaj do czynienia z bardzo prostym testem jednostkowym, który działa według zasady potrójnego A - Arrange - Act - Assert.

Najbardziej interesujący nas kod, dzieje się w części Arrange. Na wstępie tworzymy nowy obiekt mockujący na bazie interfejsu IProductsCatalog. Dzięki tej deklaracji, nasz obiekt będzie symulował działanie katalogu produktów.

W kolejnym kroku (6-9) znajduje się esencja tego przykładu, czyli definicja listy produktów, która ma zostać zwrócona jako właściwość Products katalogu produktów. W tym celu wykorzystujemy metodę Setup, w której za pomocą LINQ wskazujemy właściwość naszego interfejsu. Następnie za pomocą metody Returns definiujemy konkretną kolekcję obiektów, która ma zostać zwrócona w wyniku wywołania wyżej wymienionego propertisa.

Po sfabrykowaniu katalogu produktów, możemy już utworzyć obiekt klasy Calculator (10). Aby uzyskać dostęp do naszego repozytorium, musimy na Mocku wywołać właściwość Object, która zwróci nasz sztuczny twór.

Później wykonujemy obliczenia (12) oraz porównujemy wynik metody z tym czego my oczekujemy (14).

Jeśli wszystko wykonaliście prawidłowo, wystarczy teraz skompilować projekt, a następnie uruchomić test wybierając z menu Test opcję Run, a następnie All Tests. W wyniku tego działania, powinniście otrzymać coś podobnego do tego, co widać na screenie poniżej:

Przykładowy projekt znajdziecie w dziale download.

Data ostatniej modyfikacji: 23.02.2014, 18:46.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus