fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
5 minut

Weryfikacja parametrów metod w mock objects


03.08.2009

Dość sporo teorii mamy za sobą, nadeszła więc pora na praktyczne przykłady. Tym razem spojrzymy na konfigurację zachowania stubów w zależności od parametrów przekazywanych do ich metod (oraz analizę wartości przekazanych do mocków przez testowane obiekty, co jest scenariuszem bardzo podobnym technicznie).

Konkretne wartości

W poniższym przypadku konfigurujemy zachowanie authenticationService tylko dla jednej pary parametrów o wartościach przekazanych w zmiennych userName i password. Każde inne wartości parametrów spowodują domyślnie zachowanie stuba, czyli (jak dowiedzieliśmy się tu) zwrócenie domyślnej wartości dla typu bool => false.

  1:  authenticationService.Stub(x => x.Authenticate(userName, password)).Return(true);

Dowolne wartości

Możemy również zastosować pewien myk powodujący, że uzyskamy symulację zawsze poprawnego uwierzytelnienia. Aby to osiągnąć musimy poinstruować RhinoMocks, aby parametry wywołania metody nie były brane pod uwagę:

  1:  authenticationService.Stub(x => x.Authenticate(null, null)).IgnoreArguments().Return(true);

Jak widać służy do tego metoda IgnoreArguments() konfigurująca odpowiednie zachowanie. Nieważne co przekażemy w parametrach podczas definiowania zachowania, i tak podczas weryfikacji wartości te zostaną pominięte. Zwyczajowo jednak w takich sytuacjach przekazuje się null (bądź odpowiednie wartości domyślne w przypadku typów nie-nullowalnych).

Poniżej ta sama instrukcja, ale zastosowana w momencie weryfikacji wykonania metody (czyli fazy Assert), a nie konfiguracji stuba (czyli fazy Act). Wszystkie dalsze przykłady przepisuje się analogicznie, po prostu warunki przekazujemy jako odpowiednie wyrażenie lambda w drugim parametrze metody AssertWasCalled:

  1:  authenticationService.AssertWasCalled(x => x.Authenticate(null, null), opts => opts.IgnoreArguments());

Zdefiniowane ograniczenia

Omawiany mechanizm byłby bardzo ubogi, gdyby nie oferował większej swobody w sprawdzaniu wartości parametrów. Na szczęście tak nie jest i mamy do dyspozycji cały wachlarz ograniczeń jakie możemy nałożyć na parametr. Na przykład poniższa linijka kodu definiuje poprawne uwierzytelnienie dla dowolnego loginu z hasłem nie będącym nullem:

  1:  authenticationService.Stub(x => x.Authenticate(null, null)).Constraints(Is.Anything(), Is.NotNull()).Return(true);

Po prostu wywołujemy metodę Constraints() z tyloma ograniczeniami, ile mamy parametrów w konfigurowanej akcji.

Centralnym miejscem zbierającym zdefiniowane ograniczenia dla pojedynczych wartości jest statyczna klasa Rhino.Mocks.Constraints.Is, służąca za punkt wyjściowy do zabawy z tym mechanizmem.

Jeżeli natomiast chcemy zastosować ograniczenie ocierające się o kolekcje, czyli mamy parametr będący kolekcją bądź sprawdzamy poprawność parametru względem kolekcji, rozpoczniemy od klasy Rhino.Mocks.Constraints.List. Analogicznie: jeżeli obszarem naszych zainteresowań są napisy, to odpowiednie ograniczenie udostępni nam Rhino.Mocks.Constraints.Text.

Wykorzystajmy tą wiedzę w praktyce: kod poniżej definiuje poprawne uwierzytelnienie dla loginów zawartych w tablicy validLogins z hasłem zgodnym z wyrażeniem regularnym (minimum 6 liter lub cyfr):

  1:  string[] validLogins = new[] {"abc", "def"};
  2:  authenticationService.Stub(x => x.Authenticate(null, null))
  3:  	.Constraints(List.OneOf(validLogins), Text.Like("[\\w|\\d]{6,}")).Return(true);

Dzięki przeciążonym operatorom ograniczenia możemy łączyć, jak przy konfigurowaniu poprawnego uwierzytelnienia dla loginów rozpoczynających się od “a” i kończących się na “z”:

  1:  authenticationService.Stub(x => x.Authenticate(null, null))
  2:  	.Constraints(Text.StartsWith("a") & Text.EndsWith("z"), Is.Anything()).Return(true);

Własne ograniczenia

Może się zdarzyć, że ograniczenia nie są dla nas wystarczające. Wówczas na ratunek przychodzi nam najbardziej ogólne i elastyczne z nich przyjmujące wyrażenie lambda wykonywane dla wartości parametru. Z jego pomocą jesteśmy w stanie zasymulować poprawne logowanie dla loginu, który po ucięciu na końcach białych znaków okaże się mieć długość większą niż 8:

  1:  authenticationService.Stub(x => x.Authenticate(null, null))
  2:  	.Constraints(Is.Matching<string>(s => s.Trim().Length > 8), Is.Anything()).Return(true);

Takie rozwiązanie daje nam możliwość wplecenia w ten proces dowolnej logiki.

Rozszerzanie Rhino Mocks

Poza wykorzystaniem przedstawionej konstrukcji Is.Matching mamy jeszcze jedną drogę do zastosowania własnoręcznie zdefiniowanych, dowolnie skomplikowanych zasad. Uzyskamy to poprzez napisanie własnej klasy dziedziczącej z AbstractConstraint, analogicznie do ograniczeń już istniejących w bibliotece. Zauważyć bowiem należy, że zarówno metoda Matching<T>() jak i wszystkie pozostałe “skrótowe” metody pozwalające na badanie parametrów zwracają instancje klas dziedziczących z AbstractConstraint. Dla Is.Matching<T>() jest to Rhino.Mocks.Consontraints.PredicateConstraint<T>, dla List.OneOf() – Rhino.Mocks.Consontraints.OneOf, dla Text.EndsWith – Rhino.Mocks.Constraints.EndsWith. I tak dalej.

Napiszmy więc, wzorując się na podejrzanej Reflectorem implementacji EndsWith, własne parametryzowane rozszerzenie pozwalające na to samo co przykład zastosowany wyżej:

  1:  public class TrimmedLongerThan : AbstractConstraint
  2:  {
  3:  	private readonly int _minLength;
  4:  
  5:  	public TrimmedLongerThan(int minLength)
  6:  	{
  7:  		_minLength = minLength;
  8:  	}
  9:  
 10:  	public override bool Eval(object obj)
 11:  	{
 12:  		return (obj != null) && obj.ToString().Trim().Length >= _minLength;
 13:  	}
 14:  
 15:  	public override string Message
 16:  	{
 17:  		get { return "trimmed is longer than " + _minLength; }
 18:  	}
 19:  }
 20:

A zastosujemy to tak:

  1:  authenticationService.Stub(x => x.Authenticate(null, null))
  2:  	.Constraints(new TrimmedLongerThan(8), Is.Anything()).Return(true);

Przedstawione mechanizmy oferują nam całkowitą kontrolę nad wartościami parametrów metod w testach jednostkowych. Następnym razem przyjrzymy się jakie ciekawe zachowania możemy przypisać wywoływanym w testach metodom.

Comments are closed.

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również