TDD na przykładzie Grails – klasy domenowe i kontrolery

Zaczynam się sypać. Poważnie. Znów mnie coś w kręgosłupie strzyka, nic fajnego, siedzenie jeszcze jakoś mi wychodzi, ale chodzenie to już o wiele gorzej. Choć jedyna rzecz pozytywna z tego wynika – porządki na dysku i kolejny wpis na blogu.

W poprzednim wpisie przedstawiłem specyfikę TDD na przykładzie ograniczeń dla klas domenowych. Przed rozpoczęciem dalszej zabawy przytoczę dodatkowe asercje, które oferuje nam klasa GroovyTestCase, która jest używana zarówno do testów jednostkowych jak i integracyjnych w Grails (oczywiście skopiowane z dokumentacji).

  • assertArrayEquals(Object[] expected, Object[] value)
  • assertLength(int length, char[] array)
  • assertLength(int length, int[] array)
  • assertLength(int length, Object[] array)
  • assertContains(char expected, char[] array)
  • assertContains(int expected, int[] array)
  • assertToString(Object value, String expected)
  • assertInspect(Object value, String expected)
  • assertScript(final String script) // assert that a script runs without exceptions
  • shouldFail(Closure code) // assert that an exception was thrown in that closure
  • shouldFail(Class clazz, Closure code) // the same but for a class

Trochę ułatwiają życie, bez konieczności pisania własnych asercji na szybko. Przetestowaliśmy już ograniczenia dla kasy domenowej, teraz zajmijmy się jej metodami. Założenie, że wypadałoby testować takie metody jak save() czy metody wyszukujące jest trochę kontrowersyjne. Z jednej strony dobrze byłoby testować także i je, ale z drugiej strony to tak, jakbyśmy wątpili w umiejętności programistyczne twórców frameworka. Dlatego też nie jest to zalecana praktyka. Skupimy się zatem na przetestowaniu dopisanej przez programistę metody – a najprostszą metodą tego typu będzie oczywiście toString().

Nasz HtjTopic będzie oczywiście służył do ładnego wylistowania jego zawartości na stronie w formie tabeli, czy ładnej listy, ale zaprezentowana metoda toString() ma tylko charakter podglądowy (dodałem własności pominięte w poprzednim wpisie).

String toString() {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd")
"${question} zadane: " + formatter.format(adddate) + " przez ${author.nick}"
}

Test napisany dla tej metody wygląda tak:

void testToString() {
DateFormat df = DateFormat.getDateInstance()
String adddateString = "2009-04-28"
Date adddate = df.parse(adddateString)
def topic = new HtjTopic( question: "Czy potrzebuję komputera?", author: new Htj_Users( nick: "Chlebik" ), adddate: adddate )
assertEquals "Czy potrzebuję komputera? zadane: 2009-04-28 przez Chlebik", topic.toString()
}

I tyle. Zasadniczo wywołanie metod napisanych przez nas samych w klasie domenowej jest podręcznikowym wręcz przykładem testu jednostkowego. Nie trzeba nawet odwoływać się do innych metod, dziedziczenia, etc. Sprawa jest zatem dość prosta i nie trzeba więcej komentarza.

Zajmijmy się teraz kontrolerami. Tutaj sprawa jest o wiele trudniejsza, gdyż kontroler (no chyba, że służy tylko jako ‘zaplecze’ dla wyświetlenia widoku) może zwracać często różne wyniki w zależności od przekazanych parametrów, rodzaju odebranego żądania i tak dalej. To kontrolery z definicji są najbardziej pokomplikowanymi elementami aplikacji zgodnej z wzorcem MVC. Mimo to da się je oczywiście testować, co zresztą zaraz zrobimy.

Testowanie kontrolerów to już nie są testy jednostkowe, choć typologia w tej dziedzinie jest momentami kulawa. Grailsy domyślnie przy tworzeniu klas domenowych tworzą klasę w katalogu z testami jednostkowymi, zaś przy tworzeniu kontrolerów odpowiedni test zostaje stworzony w ktalogu z tzw. testami integracyjnymi. Jak można się domyślić, testy integracyjne służą przetestowaniu relacji i przepływu w danym artefakcie (tutaj akurat kontrolerze). Nie odchodząc za bardzo znów do tworzenia kodu do testów, spróbujmy odnieść się do kodu klasy UsersController, który powstał przy okazji pisania kodu autoryzującego.

Grailsy (jak podaje dokumentacja) domyślnie wrappują tego typu testy (integracyjne) w obiekty Spring Mock Library, co pozwala na odwoływanie się do fałszywych obiektów żądania czy odpowiedzi (sesji też). Dzięki temu możemy stworzyć dla każdego testu warunki identyczne z tymi, w których najczęściej dana akcja będzie pracowała (i z tymi skrajnymi też, testowanie warunków brzegowych to istotna część procesu testowania). By nie przedłużać i komplikować napiszemy szybki test dla akcji login. Sprawdza ona czy użytkownik jest zalogowany, jeśli nie jest to wyświetla formularz, albo dobiera się do tablicy POST. Jednakże kiedy użytkownik nie jest zalogowany (session.user jest puste), wówczas redirectuje na stronę główną oraz wyświetla komunikat o tym, iż użytkownik jest już zalogowany. Oto jak wygląda test:

void testLogin() {
def c = new UsersController()
c.session.user = "jakisobiekt"
c.login()
assertEquals "/", c.response.redirectedUrl
}

I oczywiście test przechodzi nasz kod poprawnie. Należy zwrócić uwagę na ciekawą metodę redirectedUrl, która umozliwia zbadanie docelowego adresu, pod który zostanie skierowane nasze żądanie. Więcej tego typu metod można znaleźć w dokumentacji Spring Mock.

Oczywiście takie same założenia co do testowania odnoszą się do usług, czy command objectów. Testować można również własne znaczniki GSP. Zasadniczo testować można wszystko do czego zresztą gorąco zachęcam.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s