TDD na przykładzie Grails – klasy domenowe i ich reguły

Nie tak dawno temu zawędrowałem na spotkanie WJUGa, na którym poruszono temat DDD (Domain Driven Development). Okazało się, że Grailsy są pięknym przykładem tego podejścia w praktyce. Jednakże na razie skupimy się na tym, co do tej pory omijałem w moich postach dotyczących frameworka – testach. Pisałem o nich wcześniej w kontekście JUnit, teraz zobaczmy jak zostało to zrobione w Grails.

Framework rozróżnia dwa typy testów – testy jednostkowe oraz testy integracyjne. Póki co zajmiemy się tymi pierwszymi. Zacznijmy od klas domenowych, gdyż to na ich przykładzie najlepiej pokazać jak powinien przebiegać proces budowania aplikacji w oparciu o testy. Jako podstawa posłuży nam kolejna funkcjonalność w aplikacji HowToJava – zadawanie pytań. Stworzymy zatem dwie klasy dziedzinowe – jedna będzie reprezentowała założony temat (wątek pytania, tak samo działający jak post na forum), a także konkretny wpis w ramach tego pytania/tematu.

Tworzymy zatem klasę dziedzinową HtjTopic. Wykonanie polecenia:

grails create domain-class

Powoduje utworzenie nie tylko klasy domenowej, ale także stosownych dla niej testów. Utworzona klasa testowa wygląda tak:

import grails.test.*

class HtjTopicTests extends GrailsUnitTestCase {
protected void setUp() {
super.setUp()
}

protected void tearDown() {
super.tearDown()
}

void testSomething() {

}
}

Mamy jak widać metodę wykonywaną na rozpoczęciu testu, a także jedną wywoływaną na koniec. Oczywiście w środku mamy też metodę będącą sednem testu. Przed jego wykonaniem zastanówmy się jednakże czego potrzebujemy w naszej klasie domenowej. Z całą pewnością takie pytanie zadane w serwisie musi mieć autora, tytuł z całą pewnością również by się przydał, data dodania myślę też by się przydała. Ostatecznie po szybkim procesie myślowym powstała taka oto lista:

  • tytuł będący łańcuchem o określonej długości
  • data utworzenia wpisu
  • autor (będzie to zalogowany użytkownik)

Dodamy też później kategorię, do której przynależy nasze pytanie, jednakże by nie komplikować kodu póki co, pozostaniemy przy naszej wersji. Generalna zasada TDD głosi, aby nie pisać nowego kodu, jeżeli testy nie kończą się sukcesem. Na razie nie mamy ani testów, ani kodu, zatem problem jest czysto akademicki. Zasadniczo jedyną rzeczą, którą można przetestować w wyraźny sposób póki co są ograniczenia i to, czy nasza klasa ich przestrzega. Stworzymy sobie obiekt imitujący zachowanie prawdziwego obiektu (tzw. mock) i na nim będziemy testować. Można by zastanowić się po co testować ograniczenia wpisywane do klasy, które mają na celu właśnie kontrolę i testowanie, czy poprawne wartości zostały wpisane? Autorzy frameworka odpowiadają na to pytanie prosto – bo tak :), ale też by uniknąć literówek, które (rzekomo) są plagą w tym przypadku. Grailsy z definicji ułatwiają życie i w związku z czym oferują gotowy szkielet do testowania. Nasza metoda testująca powinna wyglądać na razie tak:

void testConstraints() {
def existingTopic = new HtjTopic()
mockForConstraintsTests(HtjTopic, [ existingTopic ])

// Testujemy constraint notNull
def topic = new HtjTopic()
assertFalse topic.validate()
assertEquals "nullable", topic.errors["question"]

// Testujemy constraint blank
topic = new HtjTopic( question: "" )
assertFalse topic.validate()
assertEquals "blank", topic.errors["question"]

// Testujemy constraint minSize
topic = new HtjTopic( question: "test")
assertFalse topic.validate()
assertEquals "size", topic.errors["question"]

// Testujemy constraint maxSize
topic = new HtjTopic( question: "test test test")
assertFalse topic.validate()
assertEquals "size", topic.errors["question"]

// Na koniec dla pewnosci, ze poprawne dane powoduja przejscie testow
topic = new HtjTopic( question: "Jak pisać" )
assertTrue topic.validate()
}

Jej zrozumienie nie powinno nastręczać problemów. Dziwić mogą tylko dwie pierwsze linijki. Wywołanie metody mockForConstraintsTests w takim kształcie ‘uzbraja’ egzemplarze klasy domenowej w metodę validate(), co pozwala na sprawdzenie poprawności constraints (Uwaga: skróciłem długość tematu by ładniej to wyglądało w kodzie na blogu. Normalnie oczywiście powinien być dłuższy). Testy (uruchamiane w NetBeans kombinacją klawiszy Alt-F6) pokażą po pierwszym wywołaniu błędy bo i pewnie nie ma jeszcze klasy, której to obiekty chcemy utworzyć. Oto kod klasy HtjTopic:

class HtjTopic {

String question

static constraints = {
question( blank: false, size: 5..10, nullable: false )
}
}

To oczywiście na razie tyle, aby łatwiej było pokazać ideę testów. Wyniki testów możemy ładnie sobie obejrzeć w przeglądarce (katalog /tests w folderze projektu) – każdy błąd lub niepowodzenie testu zostanie odnotowane. Filozofia pisania testów jest taka, aby nie napychać jednej metody chamską wręcz ilością linii kodu. Należy to rozbić, np. ograniczając metody do konkretych pól, albo też do walidacji metodami poszczególnych ograniczneń (np. tylko wartości NULL).

Dodam jeszcze tylko, że testowanie odbywa się w spejcalnym środowisku testowym, które posiada oddzielny wpis konfiguracyjny w pliku DataSource.groovy. Oto jak powinna wyglądać konfiguracja, aby opierała się na HSQLu.

test {
dataSource {
pooled = true
driverClassName = "org.hsqldb.jdbcDriver"
username = "sa"
password = ""
dbCreate = "create-drop"
url = "jdbc:hsqldb:mem:testDb"
}
}

To tyle na dziś. W następnym wpisie zajmiemy się dodatkowymi metodami klas dziedzinowych oraz zerkniemy na inne artefakty, które można podpiąć pod testy.

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