SpecsFor–kolejny framework do BDD

Przez kilka ostatnich wpisów poruszałem temat BDD, a konkretniej jednego z framework’ow – SpecFlow. Dzisiaj o kolejnym rozwiązaniu, które jest przydatne, gdy programiści definiują specyfikacje. SpecsFor można zainstalować standardowo z NuGet:

image

Załóżmy, że będziemy rozpatrywać następujący kod:

public interface IAuthorization
{
   bool Authorize(string login, string password);
}
public class OrderController
{
   private readonly IAuthorization _authorization;
   private List<string>  _orders=new List<string>();

   public List<string> Orders
   {
       get { return _orders; }
   }

   public OrderController(IAuthorization authorization)
   {
       _authorization = authorization;
   }    

   public void LogIn(string login,string password)
   {
       if(!_authorization.Authorize(login, password))
           throw new AuthorizationException();
   }
   public void SubmitOrder(List<string> items)
   {
       _orders=new List<string>(items);
   }     
}

Wiem, że przykład nie ma sensu, ale chodzi mi po prostu o kilka metod + IoC. SpecsFor to również BDD, wiec bardzo przypomina w użyciu SpecFlow. Różnica polega na tym, kto definiuje specyfikacje. W SpecFlow, specyfikacja ma formę zdań w naturalnym języku. Z kolei w SpecsFor, definiujemy je przez nazwy metod. Jeśli zależy nam na dobrej komunikacji miedzy developerami a osobami nietechnicznymi, wtedy SpecFlow moim zdaniem jest dużo ulepszy. W przypadku jakiś zadań niskopoziomowych, narzędzi dla programistów itp. SpecsFor może okazać się łatwiejszy w użyciu.

Jak wspomniałem, w poprzednim poście, SpecsFor składa się m.in z nUnit, Moq, expectedObjects, Should Library więc nie musimy nic dodatkowo instalować. Wszystko jest wbudowane już w SpecsFor.

Skoro jest to BDD, zwykle chcemy zdefiniować metody GWT (Given, When, Then). Jeśli nie jest to znane Wam, zachęcam do przeczytania poprzednich postów.

Przykład szablonu mógłby wyglądać następująca:

class OrderSpecs
{
   class WhenUserIsAuthorized : SpecsFor.SpecsFor<OrderController>
   {
       // warunki wstepne
       protected override void Given()
       {
           base.Given();
       }

       // na skutek jakiegos zdarzenia...
       protected override void When()
       {
           base.When();
       }

       // Nastepnie implementujemy jedna lub wiecej metod sprawdzajace post-conditions.
       [Test]
       public void ThenListOfOrdersCanBeReturned()
       {

       }
   }
   class WhenUserIsNotAuthorized : SpecsFor.SpecsFor<OrderController>
   {
       // warunki wstepne
       protected override void Given()
       {
           base.Given();
       }

       // na skutek jakiegos zdarzenia...
       protected override void When()
       {
           base.When();
       }

       // Nastepnie implementujemy jedna lub wiecej metod sprawdzajacych post-conditions.
       [Test]
       public void ThenErrorMessageShouldBeDisplayed()
       {

       }
   }
}

Zwykle definiuje się jedną klasę ze specyfikacjami. Następnie każda z zagnieżdżonych klas zawiera metody GWT. Given i When możemy przeładować, z kolei Then definiujemy jako własne metody w formie testów jednostkowych. Proszę zauważyć, że nazwy klas, opisują scenariusze użycia, dlatego warto je pogrupować w jedną klasę ze specyfikacjami. Oczywiście nic nie stoi na przeszkodzie, aby zdefiniować, każdy spec w osobnym pliku, ale myślę, że wzorzec zaproponowany przez twórców frameowrk’a ma sens.

Kolejny krok to implementacja metod:

public class WhenUserIsAuthorized : SpecsFor.SpecsFor<OrderController>
{
  private readonly List<string> _orders = new List<string>() {"Book", "Game", "Music"};
  private string _login = "piotr";
  private string _password = "haslo";
  
  // warunki wstepne
  protected override void Given()
  {
      GetMockFor<IAuthorization>().Setup(x => x.Authorize(It.IsAny<string>(), _password)).Returns(true);
      SUT.LogIn(_login, _password);
      SUT.SubmitOrder(_orders);

      base.Given();
  }

  // na skutek jakiegos zdarzenia...
  protected override void When()
  {
      SUT.SubmitOrder(_orders);

      base.When();
  }

  // Nastepnie implementujemy jedna lub wiecej metod sprawdzajacych post-conditions.
  [Test]
  public void ThenListOfOrdersShouldBeExposed()
  {
      SUT.Orders.ShouldLookLike(_orders);
      GetMockFor<IAuthorization>().Verify(x => x.Authorize(_login, _password), Times.Once);
  }
}

SUT to instancja testowanego obiektu. SpecsFor jest odpowiedzialny za stworzenie tego obiektu i w przeciwieństwie do klasycznych testów jednostkowych, nie musimy wstrzykiwać sami zależności. SpecsFor stworzy obiekt i wstrzyknie zależności.

GetMockFor służy właśnie do definiowania mock’ow, które automatycznie zostaną wstrzyknięte. W naszym przypadku jest to IAuthorization, który zostanie przekazany do konstruktora OrderController.

ShouldLookLike z kolei oparty jest na expectedObjects, który opisałem w poprzednim wpisie. Dzięki temu możliwe jest porównywanie całych obiektów (kolekcji, zagnieżdżonych klas itp.).

Verify z kolei to znów element Moq, umożliwiający sprawdzenie czy dana metoda w mock’u została wywołana.

Powyższy przykład pokazał więc, jak SpecsFor integruje się z nUnit, Moq, ShouldLib i expectedObject. Nic nie stoi na przeszkodzie, by używać frameowrk’a w stary, klasyczny sposób na zasadzie czystych testów jednostkowych. W przeciwieństwie do SpecsFlow jest to bardziej elastyczne narzędzie, co zwykle powoduje, ze programiści źle z niego korzystają. Osobiście wolę SpecsFlow bo jest dla mnie bardziej przejrzysty i łatwo zaprezentować potem scenariusze, które zostały przetestowane. Wystarczy skopiować tekst z feature files i mamy jeden dokument opisujący co zostało przewidziane w czasie definiowania testów.

One thought on “SpecsFor–kolejny framework do BDD”

  1. “W przypadku jakiś zadań niskopoziomowych”
    powinno być
    “W przypadku jakiCHś zadań niskopoziomowych”

    To są dwa różne słowa!

Leave a Reply

Your email address will not be published.