poniedziałek, 27 października 2008

Mocking. Tworzymy i testujemy aplikację. odcinek II.

Zajmijmy się wykorzystaniem mockingu w praktyce. Do tego celu posłuży nam zaczątek kodu, który napisałem w poprzednim odcinku, czyli aplikacja TaxiService.
Główna logika biznesowa będzie zawarta w klasie TaxiService.cs, która to implementuje interfejs ITaxiService:


using System;

namespace MockingTaxiService
{
interface ITaxiService
{
bool Order(DateTime doDate, string toAddress, int clientID);
}
}

Implementacja interfejsu:


using System;

namespace MockingTaxiService
{
public class TaxiService : ITaxiService
{
#region ITaxiService Members

public bool Order(DateTime doDate, string toAddress, int clientID)
{
System.Threading.Thread.Sleep(5000);
return true;
}

#endregion
}
}
Prosta klasa klienta:


using System;

namespace MockingTaxiService
{
public class TaxiClient
{
public bool OrderTaxiTooday(string toAddress, int clientID)
{
TaxiService svc = new TaxiService();
return svc.Order(DateTime.Now, toAddress, clientID);
}

}
}

Jak można zauważyć samo wykonanie metody Order trwa niekrócej niż 5 sekund, ale prosto sobie wyobrazić przypadki, kiedy taka metoda może zabrać znacznie więcej z życia użytkownika tej właśnie metody. Spełniony w tym przypadku został jeden z podpuntów, kiedy warto stosować mocking: przypadek testowy trwa znaczny okres czasu zanim zwróci jakikolwiek wynik.
Czas najwyższy, aby na scenę wszedł wybrany przez nas framework do mockingu i abyśmy zrobili użytek z testów jednostkowych. Tworzymy projekt testów jednostkowych przy użyciu dowolnego środowiska - ja wybrałem darmowy MbUnit (http://mbunit.com), który mogę polecić.
Klasa testująca SmokeTest.cs


using System;
using MbUnit.Framework;

namespace MockingTaxiService.Tests
{
[TestFixture]
class SmokeTest
{
[Test]
public void TestShouldReturnTrue()
{
TaxiService svc = new TaxiService();
bool result = svc.Order(DateTime.Now.AddHours(5), "ul. Nieznana 12, Poznań", 1234);
// w przyp. sukcesu wynik powinien być typu: true
Assert.IsTrue(result);
}
}
}

Wykonajmy za ten pierwszy przypadek testowy, jego rezultat nie powinien być zaskoczeniem :-)






Test trwał długo - za długo aby mógł być wykonywany wraz z innymi. Tworzymy obiekt symulujący za pomocą RhinoMocks.
  1. Pobieramy pliki pakietu RhinoMocks ze strony: http://ayende.com/projects/rhino-mocks/downloads.aspx
  2. W projekcie testowym dodajemy referencję do pliku Rhino.Mocks.dll pobranego wcześniej pakietu RhinoMocks
  3. Zamieniamy ciało przypadku testowego stworzonego wcześniej, na nowe: korzystające z RhinoMocks:

using System;
using MbUnit.Framework;
using Rhino.Mocks;

namespace MockingTaxiService.Tests
{
[TestFixture]
class SmokeTest
{
MockRepository _mocks;

[SetUp]
public void initialize()
{
_mocks = new MockRepository();
}

[Test]
public void TestShouldReturnTrue()
{
var taxiService = _mocks.DynamicMock<ITaxiService>();
using (_mocks.Record())
{
SetupResult.For(taxiService.Order(DateTime.MinValue, null, 0)).IgnoreArguments().Return(true);
}

using (_mocks.Playback())
{
Assert.IsTrue(taxiService.Order(DateTime.Now, "ul. Nieznana 12, Poznań", 123));
}

}
}
}

Ok, przykład wymaga małego wyjaśnienia. Obiekt symulujący działanie tworzony zostaje przy pomocy:


var taxiService = _mocks.DynamicMock<ITaxiService>();

Jako parametr metody DynamicMock, której zadaniem jest dynamiczne stworzenie naszego "głupiego" obiektu jest interfejs bazowy obiektu, który będziemy symulować. W kolejmy fragmecie:


using (_mocks.Record())
{
SetupResult.For(taxiService.Order(DateTime.MinValue, null, 0)).IgnoreArguments().Return(true);
}

"nagrywamy" oczekiwane przez symulowany obiekt zachowanie. Uważny czytelnik zauważy, że przekazujemy do naszej metody Order puste parametry, gdyż one i tak nie będą brane pod uwagę przez środowisko testowe. Ignorowanie paramterów sygnalizujemy metodą IgnoreArguments() a po niej "na sztywno" podajemy jaki wynik zwóci nasza metoda: w tym przypadku true. Tak jak wcześniej pisałem kiedy tworzymy "głupi" obiekt, chcemy jedynie zasymulować działanie obiektu testowanego bez fizycznego zaprzęgania logiki biznesowej jaką w sobie zawiera.
Pozostaje nam teraz jeszcze wykonać test przy pomocy środowika klas testowych:



using (_mocks.Playback())
{
Assert.IsTrue(taxiService.Order(DateTime.Now, "ul. Nieznana 12, Poznań", 123));
}

Wynik testów jest zadowalający! Wystarczy sprawdzić, że test wykonał się błyskawicznie. Na tym zakończymy przygodę z mockingiem. Prawdą jest, że przedstawiony przypadek testowy to jedynie wzmianka o tym co potrafi cały framework RhhinoMocks oraz jego przyjaciel MbUnit. Zachęcam do przejrzenia dokuemntacji w/w frameworków.
Niech kod będzie za Wami!

0 komentarze: