Powrócili po przerwie wakacyjnej moi Anglicy więc możemy wrócić do jedynego słusznego języka na Koziolekweb.

Ok dzisiejszy temat zajęć to użycie mieszanki statycznych importów i adnotacji @RunWith w ramach tworzenia testów JUnit.

Cel

W JUnit nie mamy do dyspozycji adnotacji @BeforeSuite znanej z TestNG. Mały problem. Oczywiście jest możliwość tworzenia zestawów testów w taki sposób:

Listing 1. Zestaw testów w JUnit4

import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({
   BasicEMTest.class
})
public class DBTestSuite {}

po co komu ta klasa nie wie nikt. Szczególnie, że JUnit potrafi przetworzyć wszystkie klasy z testami przed rozpoczęciem testów. Zapewne dało by się i wyłuskać i wywołać metody na modłę TestNG. No, ale skoro się nie da to się nie da. Pomyślmy przez chwilę czy nie można by bylo wykorzystać tego rozwiązania w jakiś bardziej rozsądny.

import static przypomnienie

Statyczny import został wprowadzony w javie 1.5 i działa na mniej więcej takiej zasadzie, że zaimportowany statyczny składnik z danej klasy może zostać użyty jak własny składnik klasy importującej. Przypomina to trochę operator .$ z groovy, ale jest prymitywniejsze.
Statyczne imoprty były przedstawiane jako super narzędzie redukujące długi kod. Niestety twórcy swoje, a programiści swoje. W dodatku w najpopularniejszych IDE nie ma możliwości wykonania refaktoryzacji „organize imoprts” z jednoczesnym przeniesieniem kodu statycznego do sekcji import.

Wykonanie przed…

…klasą i metodą

Czasami w trakcie testów potrzebujemy uruchomić jakiś kod przygotowujący środowisko testowe. Kod taki często jest dość rozwlekły względnie jego wykonanie jest bardzo długie. Zazwyczaj też musi być wykonany wielokrotnie przed każdym testem lub klasą testową. By rozwiązać ten problem mamy do dyspozycji adnotację @BeforeClass, która oznacza metodę do wywołania przed wszystkimi testami i adnotację @Before, która oznacza metodę wywoływaną przed każdym testem.
Oczywiście wszyscy wiemy, że konfiguracja środowiska testowego (oraz przywrócenie po testach) przed testami powinna być dostosowana do pewnej zdroworozsądkowej granicy. Dlatego też zanim przystąpimy do radosnego pisania warto sporządzić plan i odpowiednio przygotować sobie rozpiskę co gdzie i kiedy tworzymy i usuwamy. Jaka część środowiska powinna być odtwarzana przed każdym testem, a jaka przed każdą klasą.
Nie będę opisywał tu zasad działania wspomnianych adnotacji. Przejdziemy do czegoś ciekawszego.

… przed zestawem

Co jeżeli jakaś grupa testów wymaga konfiguracji środowiska na dużą skalę. Jednocześnie konfiguracja może okazać się długa i żmudna? Najprościej jest użyć TestNG z jego @BeforeSuite, no ale my chcemy używać wyłącznie JUnita (argument managera: po co wprowadzać nową nieznaną technologię; bywa powalający). W tym miejscu do głosu dochodzi kod z listingu 1. 99% programistów zapytanych po co tworzona jest klasa odpowie, że po to by zaśmiecać kod. Klasa zestawu może być jednak pomocna w przypadku gdy chcemy skonfigurować środowisko w zakresie, który nie podlega testowaniu, ale jest konieczny do uruchomienia testów. Najprostszym przykładem jest tu konfiguracja EntityManagera przy testowaniu usług. Same usługi nie podlegają testowaniu w zakresie CRUD, ale wymagają tych operacji do prawidłowego działania. EntityManager może też zostać wypełniony danymi potrzebnymi do testów za pomocą np. DBUnit. Później nie potrzebujemy usuwać tych danych, aż do końca testów. Jednocześnie wielokrotne tworzenie od nowa środowiska znacząco wydłuża czas testów.
Rozwiązaniem jest użycie adnotacji @BeforeClass w klasie zestawu. Przykładową klasę prezentuje poniżej:

Listing 2. Zestaw testów w JUnit4 z konfiguracją dla całego zestawu

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({
   BasicEMTest.class,
   UserServiceTest.class
})
public class DBTestSuite {

	public static EntityManager entityManager;

	@BeforeClass
	public static void setUp(){
		EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
		entityManager = factory.createEntityManager();
	}

	@AfterClass
	public static void tearDown(){
		entityManager.close();
	}
}

Całość wygląda bardzo fajnie. Jest fajna i generalnie sprawuje się bardzo dobrze.

Statyczne importy się przydają

Jeżeli chcemy wykorzystać naszą klasę zestawu do konfiguracji to dlaczego nie pójść dalej. Można w niej umieścić obiekty, które przydają się w całym zestawie, a nie bedziemy ich tworzyć za każdym razem osobno. Poniższy listing przedstawia klasę, która wykorzystuje statyczny import by zmniejszyć ilość kodu:

Listing 3. praktyczne zastosowanie import static

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static pl.com.kir.elixir.security.DBTestSuite.entityManager;

import org.junit.BeforeClass;
import org.junit.Test;

public class UserServiceTest {

	@BeforeClass
	public static void checkSuite(){
		assertNotNull(entityManager);
	}
	
	@Test
	public void createTest() {
		assertTrue(true);
	}
}

Niech mi ktoś powie, że ten kod nie jest piękny. Ważna rzecz. Jeżeli wykorzystujemy mechanizm zestawów i „wywołań przed”, należy koniecznie sprawdzić czy interesujące nas elementy nie są przez przypadek null. Służy do tego metoda checkSuite z listingu 3.

Podsumowanie i słowo o Mavenie

Zaprezentowane przeze mnie rozwiązanie jest przykładem tworzenia samodokumentującego się kodu. Testy poza nazewnictwem są tu zorganizowane w zestawy, które pozwalają na kompleksowe testowanie jakiejś funkcjonalności. Tego typu rozwiązania nie są przeznaczone do tworzenia testów jednostkowych w środowisku izolowanym. Nie są też przeznaczone do szybkiego testowania pojedynczych metod w czasie pracy. Celem takiej konfiguracji jest przede wszystkim przyspieszenie procesu konfiguracji i co za tym idzie wykonania testów na serwerach CI. Przyspieszają też „kontrolne” kompilacje na komputerze programisty.
Jeżeli chcemy uruchomić taki zestaw za pomocą Mavena musimy odpowiednio skonfigurować styczkę surefire.

Listing 4. Konfiguracja maven-surefire-plugin

<plugin><groupid>org.apache.maven.plugins</groupid><artifactid>maven-surefire-plugin</artifactid><version>2.4</version><configuration><forkmode>pertest</forkmode><argline>-Xmx1024m -Xms512m</argline><testfailureignore>true</testfailureignore><excludes><exclude>**/*Test.java</exclude></excludes><includes><include>**/*Suite.java</include></includes></configuration></plugin>

Konfigurację tą umieszczamy w osobnym profilu lub przenosimy testy i zestawy do osobnego folderu w strukturze projektu i odpowiednio konfigurujemy excludes i includes.