15 lutego 2009

Dokończenie rozdziału 4. o kontrolerach z "The Definitive Guide to Grails, Second Edition"

Nie chciałem dzielić kolejnego rozdziału 5. o widokach w Grails, więc dzisiaj będzie krótko - dokończenie rozdziału 4. "Understanding Controllers" o kontrolerach grailsowych z książki "The Definitive Guide to Grails, Second Edition".

Uwaga: "krótko" nie musi oznaczać tego, co wielu z Wam kojarzy się z "krótko" ;-)

W Grails mamy do dyspozycji mechanizm przechwytywania wywołania metod znany z programowania aspektowego (ang. AOP - Aspect-Oriented Programming). Grails jest jedynie zawężeniem dostępnych możliwości AOP i udostępnia "rozszerzenia" (ang. advice) przed (before), po (after) oraz wokół (around) wykonania metod(y). Spełnia to jednak podstawowe potrzeby przechwytywania wywołań akcji kontrolerów w Grails.

Deklaracja metody przechwytującej "przed" sprowadza się do stworzenia atrybutu beforeInterceptor w postaci domknięcia w ramach danego kontrolera, np. (Listing 4-37):
 def beforeInterceptor = {
log.trace("Przechwyciłem wykonanie metody $actionName z parametrami $params")
}
Mamy możliwość bardziej precyzyjnego określenia, kiedy i która akcja ma być przechwycona z literałem mapowym, np. (Listing 4-38):
 class AlbumController {
private trackCountry = {
...
}

def beforeInterceptor = [action:trackCountry, only:"show"]
}
Parametr action określa akcję do wykonania, a drugi - only lub except - wskazują, której akcji kontrolera dotyczy.

Zwrócenie false przez metodę przechwytującą wstrzymuje wykonanie docelowej akcji kontrolera.

Deklaracja metody przechwytującej "po" sprowadza się do deklaracji atrybutu afterInterceptor, której wartością jest domknięcie do wykonania. Parametr wejściowy model domknięcia to model wynikowy z akcji, np. (Listing 4-40):
 def afterInterceptor = {model ->
log.trace("Wykonano akcję $actionName, która zwróciła model $model")
}
Grails udostępnia specjalną klasę testową ControllerUnitTestCase do testowania kontrolerów, w której znajdziemy "zaślepki" (imitacje obiektów, ang. mocks) otaczających kontroler obiektów z Servlet API, np. HttpServletRequest oraz metod render i redirect.

Utworzenie klasy testowej kontrolera to grails create-unit-test <klasa-kontrolera>. Klasa, której nazwa kończy się na Tests, powstanie w katalogu test/unit.

Klasa bazowa ControllerUnitTestCase rozszerza GrailsUnitTestCase, która udostępnia metody pomocnicze z imitacją działania klas dziedzinowych i kontrolerów, np. mockDomain, która przesłania dostęp do bazy danych (Listing 4-42):
 void testList() {
mockDomain(Album, [new Album(title: "Tytuł"),
new Album(title: "Inny tytuł")])
def model = controller.list()
assertEquals 2, model.albumList.size()
}
Za pomocą mockDomain przesłoniliśmy dostęp do bazy danych i wykonanie list() zwróciło (a przynajmniej powinno!) wyłącznie 2 albumy. mockDomain ustanawia aktualne dane do skontruowania modelu i wszystkie metody pracują wyłącznie na nich.

Klasa MockHttpServletResponse pozwala na analizę stanu bieżącej odpowiedzi kontrolera. W szczególności, metoda getContentAsString() zwraca treść odpowiedzi, np. (Listing 4-43):
 void testIndex() {
controller.index()
assertEquals "Welcome...", controller.response.contentAsString
}
W powyższym przykładzie, jeśli zadaniem akcji index() jest wypisanie (np. przez render) tekstu "Welcome..." obiekt controller.response z pomocą metody contentAsString pomoże nam w sprawdzeniu tego.

Jeśli w akcji korzystamy z bardziej zaawansowanej postaci render, np. render(view: "show", model:[album:Album.get(params.id)]), wtedy sprawdzenie tego to skorzystanie z kolejnej imitacji (zaślepki) - renderArgs, np.
 mockParams.id = 1
controller.show()
assertEquals "show", renderArgs.view
assertEquals 1, renderArgs.model.album.id
Użyliśmy również mockParams, który imituje obiekt params. Z jego pomocą możemy wypełnić do właściwymi danymi przez wykonaniem akcji kontrolera.

Mamy do dyspozycji imitacje - mockRequest (typu org.springframework.mock.web.MockHttpServletRequest), mockResponse (typu org.springframework.mock.web.MockHttpServletResponse), mockSession (typu org.springframework.mock.web.MockHttpSession), mockParams oraz mockFlash.

Wskazanie początkowego (domyślnego) kontrolera przy wejściu do aplikacji to zmiana pliku grails-app/conf/UrlMappings.groovy, w którym deklarujemy statyczny atrybut mappings, np. (listing 4-45):
 class UrlMappings {
static mappings = {
"/"(controller: "store")
}
}
Wskazujemy jedynie początkowy kontroler, a nie konkretną akcję (jej wyznaczenie to reguły opisane w Rozdział 4. "Understanding Controllers" z "The Definitive Guide to Grails, Second Edition" - zazwyczaj index).

W zasadzie oczywiste (ale mnie początkowo zaskoczyła ta oczywistość) - pusta akcja w kontrolerze sprowadza się do delegowanie wykonania do widoku w grails-app/views/<kontroler>/<akcja>.gsp.

Kolejne strony to materiał o pisaniu testów jednostkowych z imitacjami obiektów w Grails. Warto pamiętać, że "you can never write too many tests!" (strona 101). Z Grails pisanie testów staje się trywialne. Radek Holewa wyraził również podobny entuzjazm w swoim komentarzu do wpisu Inni klienci aplikacji w Grails - rozdział 13 z "Beginning Groovy and Grails":

Co do samego Groovy'ego to używam go obecnie jako język w którym piszę testy jednostkowe w projektach w których biorę udział. Sprawdza się tam idealnie!

Na koniec wzmianka o klasach polecenia, gdzie wypełnienie danymi obiektu grailsowego bez konieczności zapisu do bazy danych to właśnie zadanie dla niego. Równie oczywista, jak poprzednia oczywistość, a wciąż mnie zaskakuje.

Natknąłem się również na konstrukcje znane mi z wcześniejszego programowania w C/C++, tj. (Listing 4-57):
 class LoginCommand {
String login
private u

User getUser() {
if (!u && login) {
...
}
}
...
}
Znaczy się, że null to w Groovy równoznaczne z false! Myślałem, że mam to już za sobą, a tu powrót do korzeni.

Możemy korzystać z pomocniczej metody MockUtils.prepareForConstraintsTests(), która akceptuje klasę polecenia lub klasę dziedzinową do testowania warunków danych (definiowanych przez static constraints).

p.s. Nie zapomnieliśmy o konkursie Bloger Roku 2008? Wystarczy wejść na stronę do głosowania na Notatnik, gdzie podajemy nasz adres email i zatwierdzamy. Wiadomość z adresem potwierdzającym pojawia się w naszej skrzynce, klikamy w niego potwierdzając nasz wybór. I tyle. Dziękuję!