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 = {Mamy możliwość bardziej precyzyjnego określenia, kiedy i która akcja ma być przechwycona z literałem mapowym, np. (Listing 4-38):
log.trace("Przechwyciłem wykonanie metody $actionName z parametrami $params")
}
class AlbumController {Parametr action określa akcję do wykonania, a drugi - only lub except - wskazują, której akcji kontrolera dotyczy.
private trackCountry = {
...
}
def beforeInterceptor = [action:trackCountry, only:"show"]
}
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 ->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.
log.trace("Wykonano akcję $actionName, która zwróciła model $model")
}
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() {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.
mockDomain(Album, [new Album(title: "Tytuł"),
new Album(title: "Inny tytuł")])
def model = controller.list()
assertEquals 2, model.albumList.size()
}
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() {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.
controller.index()
assertEquals "Welcome...", controller.response.contentAsString
}
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 = 1Uż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.
controller.show()
assertEquals "show", renderArgs.view
assertEquals 1, renderArgs.model.album.id
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 {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).
static mappings = {
"/"(controller: "store")
}
}
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 {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.
String login
private u
User getUser() {
if (!u && login) {
...
}
}
...
}
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ę!
Brak komentarzy:
Prześlij komentarz