SpecsFor MVC

W ostatnim poście było o SpecsFor, jako alternatywy dla SpecsFlow. Pokazane przykłady były ogólne i nie dotyczyły konkretnej technologii. Z BDD bardzo często korzysta się w celu przetestowania UI. W końcu wpisywane scenariusze, odzwierciedlają typową interakcję użytkownika z aplikacją. Osoby nietechniczne zwykle definiują wymagania z punktu widzenia użytkownika a nie wewnętrznej infrastruktury, która oczywiście nie jest im znana w szczegółach.

Dla aplikacji ASP.NET MVC powstał SpecsFor MVC. Dobra wiadomość jest taka, że sposób korzystania z niego jest analogiczny do klasycznego SpecsFor. Stanowi on jednak testy UI, wiec zamieni standardowe unit testy w wywołania wykonywane przez przeglądarkę (tak samo jak WatIn). W przeciwieństwie do klasycznych testów UI, nie ma tam fazy nagrywania. Zamiast tego, programiści piszą specyfikacje i kod (tak jak w normalnym SpecsFor).

Zaczynajmy od instalacji:

image

Następnie możemy przejść do definiowania testów. Przyznam, że na początku miałem kilka problemów związanych z tym. Przede wszystkim koniecznie należy stworzyć osobną bibliotekę dla testów, (co i tak w środowisku produkcyjnym jest czymś normalnym).

Kolejny etap to konfiguracja środowiska uruchomieniowego. SpecsFlow jak wspomniałem wcześniej, przetłumaczy testy jednostkowe do zwykłych wywołań w przeglądarce. Z tego względu, niezbędny będzie hosting aplikacji w IIS. Testy są wykonywane nie na poziomie kontrolerów, ale całych zapytań HTTP. Z tego względu należy stworzyć następująca klasę:

[SetUpFixture]
public class AssemblyStartup
{
   private SpecsForIntegrationHost _host;

   [SetUp]
   public void SetupTestRun()
   {
       var config = new SpecsForMvcConfig();
       config.UseIISExpress()
             .With(Project.Named("MvcApplication7"))
             .CleanupPublishedFiles()
             .ApplyWebConfigTransformForConfig("Debug");

       config.BuildRoutesUsing(RouteConfig.RegisterRoutes);

       config.UseBrowser(BrowserDriver.InternetExplorer);
       config.InterceptEmailMessagesOnPort(13565);

       _host = new SpecsForIntegrationHost(config);
       _host.Start();
   }

   [TearDown]
   public void TearDownTestRun()
   {
       _host.Shutdown();
   }
}

Kod po prostu uruchamia IIS express, hostując daną aplikację. W tym przypadku jest to MvcApplication7. SpecsFor opublikuje stronę, zgodnie z konfiguracją Debug. Ponadto, w testach będzie wykorzystywana przeglądarka Internet Explorer. Nic nie stoi na przeszkodzie, aby to zmienić ją i skorzystać z jakieś innej – wystarczy spojrzeć na BrowserDriver. Atrybut SetUpFixture oznacza, że klasa zostanie wywołana przed jakimikolwiek testami jednostkowymi, czyli dokładnie to co, czego oczekujemy.

Załóżmy, ze chcemy napisać test, który sprawdzi czy użytkownik po zarejestrowaniu konta, zostanie przekierowany do strony głównej. Innymi słowy:

Given: Użytkownik wszedł na stronę do rejestracji

When: Użytkownik wypełnił dane i nacisnął przycisk rejestruj

Then: Użytkownik został przekierowany do strony głównej.

public class UserRegistrationSpecs
{
   public class WhenCredentialsAreValid : SpecsFor<MvcWebApp>
   {
       protected override void Given()
       {
           SUT.NavigateTo<AccountController>(c => c.Register());
       }

       protected override void When()
       {
           SUT.FindFormFor<RegisterModel>()
               .Field(m => m.UserName).SetValueTo("Piotr")
               .Field(m => m.Password).SetValueTo("Test@1")
               .Field(m => m.ConfirmPassword).SetValueTo("Test@1")
               .Submit();
       }

       [Test]
       public void ThenUserShouldBeRedirectedToHomePage()
       {
           SUT.Route.ShouldMapTo<HomeController>(c => c.Index());
       }            
   }
}

NavigateTo śluzy oczywiście do przekierowań na podstawie routing. Proszę zauważyć, że nie korzystamy tutaj z adresów HTTP a zwykłych kontrolerów, z którymi programista ASP.NET MVC jest bardzo dobrze obeznany.

Ciekawszą metodą jest When. Przykład pokazuje jak łatwo można edytować pola w formularzu. Kod wpisze nazwę użytkownika, hasło, a następnie zasymuluje naciśniecie przycisku Submit. Wszystko to za pomocą silnie typowanego kodu, a nie identyfikacji za pomocą tagów HTML.

W klauzuli Then, sprawdzamy po prostu czy aktualna strona to ta obsługiwana przez kontroler HomeController oraz akcję Index. Po uruchomieniu testów, najpierw pojawi się aplikacja konsolowa, która odpala serwer i dokonuje publikacji strony:

image

Po chwili z kolei sterownik uruchomi daną przeglądarkę i wykona operacje zdefiniowane w specyfikacji, czyli wpisze dane uwierzytelniające i zarejestruje użytkownika:

image

W klasie konfigurującej (AssemblyStartup) z pewnością dostrzegliście następująca linię kodu:

config.InterceptEmailMessagesOnPort(13565);

SpecsFor ma wsparcie dla obsługi mailow i w testach można sprawdzać czy dany email został wysłany. Na przykład, jeśli rejestracja wysyła email to można sprawdzić czy faktycznie tak stało się:

SUT.Mailbox().MailMessages.Count().ShouldEqual(1);

Mamy dostęp nawet do informacji o konkretnym emailu, tzn.:

SUT.Mailbox().MailMessages[0].To[0].Address.ShouldEqual("to@pzielinski.com");
SUT.Mailbox().MailMessages[0].From.Address.ShouldEqual("from@pzielinski.com");

ASP.NET MVC ma wsparcie dla walidacji formularzy i tak samo w SpecsFor można sprawdzić czy wskazane pole posiada prawidłowe dane. Naturalne jest, że trzeba napisać kilka testów negatywnych, czyli sprawdzić, co się stanie, jak nazwa użytkownika nie została podana w prawidłowym formacie.:

SUT.FindFormFor<RegisterModel>().Field(m => m.UserName).ShouldBeInvalid();
SUT.FindFormFor<RegisterModel>().Field(m => m.Email).ShouldBeInvalid();

Większość formularzy ma również ValidationSummary ze szczegółami błędu. Bardziej zaawansowane testy mogą również weryfikować czy podany komunikat błędu jest prawidłowy:

SUT.ValidationSummary.Text.ShouldContain("The user name or password provided is incorrect.");

Podobnie jak wartości poł edycyjnych, można sprawdzić czy etykieta ma prawidłową wartość:

SUT.FindDisplayFor<AboutViewModel>().DisplayFor(m => m.User.UserName).Text.ShouldEqual("text");

Powyższa zmienna została wyświetlona w widoku za pomocą:

@Html.DisplayFor(m => m.BusinessDays[i])

Istnieje oczywiście dużo więcej helper’ow umożliwiających weryfikację treści strony. API jest łatwe i nie ma sensu tutaj opisywać wszystkich możliwości. Bardzo podoba mi się SpecsFor i preferuje operować na kontrolerach niż na czystym URL jak to miało miejsce w WatIn. Dużo łatwiej wykorzystywać istniejące już testy jednostkowe, ponieważ infrastruktura nie zmienia się. Jedyna niedogodność to problemy jakie miałem podczas konfiguracji i publikacji aplikacji. Wynikały one głownie z moich lokalnych ustawień IIS, ale mimo wszystko, wyrzucane wyjątki nic nie mówiły o realnym problemie a kończyło to się po prostu komunikatem „Build Failed”.

Leave a Reply

Your email address will not be published.