Mockito – ja testataan ihan mitä vain isolaatiossa

Olen joskus aiemmin kirjaillut juttuja siitä miten esim. EJB komponentteja voidaan yksikkötestata – ja käytellyt välineenä EasyMockia. Testaus isolaatiossa, eristyksissä muusta on yksi tavoiteltava asia koska näin tehdyt testit voidaan ajaa salamannopeasti ja jatkuvasti, saaden palautetta muutoksista koko ajan.

Mockito-JUnit

Sanoinko testit? Köhköh, tarkoitin tiestysti speksit. Isolaatiotestit mahdollistavat TDD ja BDD disipliinien soveltamisen haltuessaan, tai itseni suosima Spec-While-Writing tapa joka on oikeastaan ihan samaa kuin em mutta rennommin rantein. Fanaattiset isolaatiotestithän eivät nimenomaan ole testeinä kovinkaan hyviä usein, mutta spekseinä ne ovat aivan verrattomia, ja monimutkaiselle logiikalle lähes ilmaiseksi sivuvaikutuksena syntyvät testitkään eivät ole ihan huono asia.

Sanoinko ilmaiseksi? Hups. Kyllähän testien kirjoittamiseen aikaa kuluu, etenkin jos niitä säveltää etupainotteisesti ennen koodipyrähdyksiä. Etenkin isolaation saavuttaminen on aika ajoin hankalaa. Viime aikoina olen löytänyt uutta apua tälle saralle: Kuten otsikko jo spoilasi, Mockito framework on varsin kiva, etenkin Spring Bootin kanssa. Vastaavaan tapaan Javascript-puolella käyttelen nykyään Jasmine mockeja, mutta tämä ei ole artikkeli niistä vaan Mockitosta. Tässä siis vähän maistiaisia siitä miltä maistuu yksikkötestaus mockitolla ja mitä hyöytyä siitä saa.

Mockito testi voi olla tavallinen JUnit tai TestNG testi, eli aloitellaan tähän tapaan:

public class UserResourceTest {

  @Test
  public void changePasswordShouldSucceedWithValidParameters() 
    throws Exception {
      fail("Not implemented yet");
  }

}

Testatessa on tietysti mukavaa olla se mitä testataan. Perinteisesti sen voi vaikka instansioida nimellä sut – system under test, Mockitolla homma hoituu näin:

@InjectMocks
private UserResource sut;

Jotta tuo tekee jotain on tietysti syytä ajaa tämä Mockiton kera. Lisätäänpä siis testiluokan alkuun:

@RunWith(MockitoJUnitRunner.class)

Nyt tapahtuu jo jotain. Tässä testattavassa luokassa on kuitenkin paljon riippuvuuksia, jäsenmuuttujissa on yhtä ja toista jota Spring injektoi ajon aikana, mutta jotka aiheuttavat nullpointer exceptioneitä ellei niitä ole alustettu. Olen joskus injektoinut näitä riippuvuuksia reflectionin avulla, joskus setXXX-metodeita tekemällä. Mockitolla homma sujuu kokonaisuudessaan näin:

@RunWith(MockitoJUnitRunner.class)
public class UserResourceTest {

    @Mock
    private Utils utils;

    @Mock
    private UserDetailsService userDetailsServiceMock;

    @InjectMocks
    private UserResource sut;

Eli, kun alan kirjoittelemaan omaa testiäni, kaikki kolme edellämainittua on instansioitu Mockiton toimesta, mock objekteille on mock toteutukset, jotka esim. palauttavat null/eivät tee mitään. Ja mikä parasta, mock objektit on injektoitu sut objektin sisään, privaatteihin jäsenmuuttuja-kenttiin. Ensin yritetään samalla tyypillä, sitten samalla nimellä.

Mockiton oma dokumentaatio InjectMocks-metodista sanoo näin:

Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection in order and as described below. If any of the following strategy fail, then Mockito won’t report failure; i.e. you will have to provide dependencies yourself.

Eli tarkkana tämän kanssa.

Mitä muuta hienoa? No Mockitossa on verrattain helppoa opettaa mockit palauttamaan sopivia arvoja, esim:

UserEntity currentUser = new UserEntity();
UserAuthentication userAuthentication = new UserAuthentication(currentUser);
when(utils.getAuthentication()).thenReturn(userAuthentication);

Ja sitten vain paukuttamaan omaa suttia, joka taas kutsuu tuota utils.getAuthentication() metodia. Jos haluat tarkistaa, kutsuiko, sen voi tarkistaa verify-kutsulla:

verify(utils).getAuthentication();

Eli, Mockito vaikuttaisi hyvältä. Pääidea on tuoda testauksen hintaa alas, ja näin saada siitä yhtä luontevaa kuin hengittäminen. Tai luontevampaa.

 

Mainokset

Koodin laadun hifistelyä – Maven, AntRun, SonarQube

Hetken SonarQubea käytettyäni olen suuri fani. Kun koodaustiimi saa jatkuvasti metriikkaa tekemisistään, on helpompi tehdä asiat alusta asti oikein ja välttää teknistä velkaa. Olen jo aiemmin ollut ihastunut staattisiin lähdekoodianalyysiohjelmistohin, mutta Sonar on – puutteistaankin huolimatta – paljon laajempi ja osittain helppokäyttöisempi paketti.

Tässä pari uutta havaintoa matkan varrelta:

Sonarista saa näppärästi web service rajapinnan kautta metriikkaa ulos siitä miten homma etenee – näistä voi vuorostaan jopa päräytellä näppäriä graafeja tai radiaattoreita.

Esim: http://munsonarserveri:9000/api/timemachine?resource=1&metrics=coverage

Pajauttaa aikakoneesta koodin kattavuusmetriikkaa tämännäköiseen json muotoon:

[{"cols":[{"metric":"coverage"}],"cells":[{"d":"2014-06-04T15:42:12+0300","v":[0.0]},{"d":"2014-06-05T09:21:24+0300","v":[11.9]},{"d":"2014-06-09T10:22:23+0300","v":[12.8]},{"d":"2014-06-10T08:03:49+0300","v":[12.8]},{"d":"2014-06-11T07:36:49+0300","v":[14.1]},{"d":"2014-06-11T13:59:50+0300","v":[18.3]}]}]

Tuossapa tietysti sampleja joissa aikaleima, ja testikattavuuden prosentuaalinen määrä – toivon mukaan nouseva trendi.

Samoin irtoaa esim. tech debt mittari, LOC mittari, jne, joilla pystyy seuraamaan mitä tapahtuu. Nämä vaikka Android kännykän näytölle pyörimään tai robotti motkottamaan jos ylityksiä tapahtuu.

Toinen huomio: Javascript projektissa ei alkuun code coverage mittailut toimineet, vaikka lcov kattavuustiedostoja generoimmekin. Keksin pari heikkoutta Maven projektihässäkässä Sonarin suhteen:

– Sonarista on jokin aika sitten poistettu käytöstä parametrit joilla voi kertoa mistä junit testiraportit löytyvät. Monet tutorialit viittaavat vielä niihin, mutta ne eivät siis toimi.

– Grunt rakentelee kyllä iloisesti lcov-standardin mukaisia raportteja Karma -testeistä. Mutta ne menevät ikävästi kansioon jonka nimessä on selain ja käyttöjärjestelmä – jotka voivat vaihdella, ja joissa on välilyöntejä. Toinen ongelma oli lcov tiedoston sisältö – siellä oli suhteellisia polkuja jotka eivät olleet suhteessa Maven projektiin, vaan testikonfiguraatioiden paikkaan.

Joten muutama korjaus joita tein:

Ensinksi AntRun plug-in kopsaamaan lcov tiedosto fiksumpaan ja vakioidumpaan paikkaan – ja regexp replace fiksaamaan nuo polut:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.7</version>
  <executions>
    <execution>
      <id>copy-lcov-file-to-target</id>
      <phase>test</phase>
      <goals>
        <goal>run</goal>
      </goals>
      <configuration>
        <skip>${skipTests}</skip>
       <target>
         <copy todir="target" flatten="true">
           <fileset dir="target">
             <include name="surefire/PhantomJS*/lcov.info" />
           </fileset>
         </copy>
         <replaceregexp byline="true"
 file="${basedir}/target/lcov.info"
 match="SF:./app/"
 replace="SF:src/main/frontend/app/" />
       </target>
      </configuration>
    </execution>
  </executions>
</plugin>

Sitten vain sonaria varten yksi property asetus Maven pom.xml tiedostoon:

<properties>
    <sonar.javascript.lcov.reportPath>target/lcov.info</sonar.javascript.lcov.reportPath>
</properties>

Ja näin alkaa tippumaan testikattavuudesta raportteja.

Protractor alkoi myös tiputtelemaan JUnit yhteensopivia raportteja kun lisäili sen konfiguraatiotiedostoon tällaista:

 onPrepare: function() {
   require('jasmine-reporters');
   jasmine.getEnv().addReporter(
     new jasmine.JUnitXmlReporter('../../../target/surefire', true, true,'e2e-TEST-'));
 },

Siinäpä nämä tällä kertaa. Sonarin aikakone – time machine – antaa mukavaa metriikkaa siitä miten tarkkailija muuntaa tarkkailtavaa – quality is in the eye of the beholder.

timemachine

 

 

 

Testauksesta ja Tomcatistä

No niin, tämä viikko sujuu sitten kiireen merkeissä joten ei ole paljoa uutta kirjoiteltavaa.. Mutta muutama asia on silti mielessä.

 

Olen miettinyt paljon yksikkötestauksen käyttöä ohjelmistokehityksessä Test Driven tapaan. Äärimmäisen harva sovelluskehitystiimi voi kerskua sillä että tekee TDD tapaan 100% kattavuudella eli yksikkötestaa tietokantakerroksen, liiketoimintakerroksen ja web käyttöliittymäkerroksen eristetyin testein joita on nopea ajaa, ja jotka kehitetään Test First periaattein, siten että testit toimivat suunnitteluvälineenä. Ei ole mitään estettä miksei tätä voisi teknisesti tehdä, mutta se on vain työlästä kun se tehdään oikein. Sensijaan integraatiotestit jotka testaavat moduulia ympäristössään ja ovat hitaampia ajaa, ovat myös halvempia tehdä ajankäytöllisesti, ja niitä voi rakentaa samoilla yksikkötestausvälineillä (JUnit ja Mockito ovat kovia tässä).

Oli miten oli, TDD ajatuksessa on kuitenkin jotain filosofisesti hyvin puhuttavaa. Testattava koodi on avointa ja tasarakenteista koodia, ja miten voisi paremmin tehdä funktion kuin ensin miettimäll ämistä tietää että se toimii? Ja mikä parasta, nimeämällä testit fiksusti testikoodi on melkeinpä luettavaa speksiä. Mockitoa koodatessakin käytännössä kirjoitetaan speksejä englanniksi – mutta näitä speksejä voi nappia painamalla tarkistaa milloin vain haluaa.

Ja tästä päästiinkin viikon linkkivinkkiin, eli http://www.dzone.com/links/r/10_steps_to_more_readable_tests.html

Toinen huomio: Tomcat 7 on saavuttanut enemmän tai vähemmän vakaan tason ja päässyt ulos betasta. Tomcat taitaa olla edelleenkin suosituimpia alustoja joissa java web sovelluksia ajetaan (huomioiden että JBOSS ajaa myös Tomcatiä), ja kun uusin versio on jälleen uusi Java EE 6 alusta, se herättää heti mielenkiinnon. Yksi muutos on Servlet 3.0 tuki – hyvästit XML:lle jos niin haluat. Tervetuloa asynkroniset kutsut. Lopulta Tomcat 7:ssä on myös korjattu aiempia muistivuoto-oireita, eli se tarjoaa kiinnostavan alustan testata myös vanhoille sovelluksille. EJB Lite ei valitettavasti onnistu vieläkään – mutta sitä varten on JBOSS 6…

 

EJB 3 Yksikkötestauksesta 3/3

Artikkelisarjan kahdessa ensimmäisessä osassa käsiteltiin EJB 3 ja JUnit yhteiskäyttöä. Todettiin että on pääosin kolme strategiaa millä EJB komponentteja voi testata:

1) Testataan niitä EJB säiliössä. Testikoodi toimii tavallaan clientin roolissa ja kutsuu ejb fasadin palveluita. Säiliö toimittaa tietokantaresurssit, transaktiot ja muut. Tämä ei ole kovin aitoa yksikkötestausta tai varsinkaan TDD:tä mutta voi olla hyödyllistä testausta silti.

2) Testataan EJB 3 komponentteja ilman säiliötä, tavallisina java-luokkina. Tämä on helppoa triviaaleilla EJB komponenteilla mutta koska niiden logiikka usein kaipaa taustalleen resursseja kuten JNDI, Tietokantapalvelimet, Connection Poolit, transaktiopalvelimet, sanomapalvelimet, JCA connectorit, jne, niitä voi olla vaikeaa testata eristyksessä. Ratkaisuna on pystyttää testaukseen dedikoitu muistitietokanta tms resurssi, joka antaa mahdollisuuden testata businesslogiikan ja pysyvyysratkaisun yhteistoimintaa. Tämä vaatii sen, että kytkös kantaan tai sanomapalvelimeen on helppo vaihtaa, esim. Dependency Injection patternilla. Tämäkään ei ole aivan puhdasta yksikkötestausta mutta voi olla höydyllistä testausta ja on ilman muuta tehtävissä.

3) Puhtaan Test Driven lähestymistavan puitteissa haluamme keskittyä testaamaan EJB:n varsinaista business logiikkaa, ei kannan toimintaa. Lisäksi testien pitäisi olla vakioituja olosuhteiltaan, ja salamannopeita suorittaa (jotta niitä voidaan ajaa toistuvasti ja jatkuvasti, kenen hyvänsä työasemassa). Tämä on mahdollista jos voimme korvata taustaresurssit sopivalla Mock frameworkillä. Tässä mallissa on varjopuolena että Mock objektit pitää ensin ’kouluttaa’ palauttamaan oikeat, odotetut arvot. Toisaalta tämä koulutusvaihe samalla speksaa mitä odotamme sovelluksen tekevän, eli palvelee dokumentoinnin tehtävää.

Tässä artikkelissa esittelen suositun Mock frameworkin Mockiton käyttöä. Testauksen kohteena on jo sarjan 2. osassa esitelty BankFacade SessionBean, joka näyttää siis tältä:

@Stateless
public class BankFacadeBean implements BankFacadeLocal {

@PersistenceContext
private EntityManager em;

public EntityManager getEm() {
return em;
}

public void setEm(EntityManager em) {
this.em = em;
}

public void updateAccount(Account a) {
em.merge(a);
}

public void createAccount(Account a) {
em.persist(a);
}

public Account getAccount(Long id) {
return em.find(Account.class, id);
}

}

Yksinkertainen Bean, jolla tulee ajon aikana olemaan EntityManager injektoituna jäsenmuuttujaan. Meidän testimme kannalta sellaista ei kuitenkaan ole – joten mockaamme sellaisen. Tässä on Mockito frameworkiä käyttävän JUnit yksikkötestin luokkamääritys ja setUp-metodi:

… snip …

import static org.mockito.Mockito.*;

public class BankFacadeBeanMockTest extends TestCase {

private static Logger logger = Logger.getLogger(BankFacadeBeanMockTest.class.getName());
private EntityManager em;

public static final Account ACCOUNT = new Account();
public static final double BALANCE=10000.0;

public BankFacadeBeanMockTest(String testName) {
super(testName);
}
private BankFacadeBean instance;

@Override
protected void setUp() throws Exception {
super.setUp();

ACCOUNT.setBalance(BALANCE); // initialize account
ACCOUNT.setId(Long.valueOf(1));

// Set up a MOCK EntityManager here
em = mock(EntityManager.class);

// teach mock how to do its things

// make sure ACCOUNT is in EntityManager context
when(em.contains(ACCOUNT)).thenReturn(Boolean.TRUE);

// make sure we can find account
when(em.find(Account.class, Long.valueOf(1))).thenReturn(ACCOUNT);

instance = new BankFacadeBean();
instance.setEm(em);
}

.. snip …

Kävikö selväksi? Importoimme staattisen Mockito objektin kaikki metodit. Loimme testiä varten vakioidun tilin jossa tilinumero ja saldo pysyy samana (refaktoroinnille olisi kyllä jo käyttöä..). mock()-metodilla loimme EntityManager rajapinnasta mock objektin. Mock objektin kolmea metodia tullaan testeissä kutsumaan, joten varmistamme että ne palauttavat odotetut arvot:

1) contains() palauttaa true oikealle tilille – ja väittää näin objektin löytyvän kannasta

2) find() palauttaa luomamme tilin – aivan kuin se olisi haettu kannasta.

Lopuksi injektoimme mockatun EntityManager objektin pankki-fasadille, ja alamme testailemaan sitä. Tässä testirutiini:

public void testPersistence() {
System.out.println(”updateAccount”);

try {
// find existing account from db

// update account data

//a.setId(Long.valueOf(1000)); // don’t set id, since it’s autoid

instance.createAccount(ACCOUNT);
Long id = ACCOUNT.getId();
System.out.println(”Created with account id ”+ id);

// em should know Account a now
assertTrue(em.contains(ACCOUNT));
// Verify that our account exists and has proper balance
Account b = instance.getAccount(id);
assertEquals(BALANCE,b.getBalance());
} catch (Exception ex) {
ex.printStackTrace();
fail(”Exception during testPersistence”);
}
}

Mockito Unit Test

Mockito yksikkötestaus EJB 3.0 komponenteille on helppoa ja salamannopeaa

Testi ei siis vaadi EJB säiliötä, tai tietokantaa, tai muutakaan resurssia, testaamme Java-luokkaa pelkkänä POJO:na. Tällaista testiä voit ajaa salamannopeasti uudelleen, ja uudelleen, ja uudelleen aina muutoksia tehtäessä. Se ei mittaa tietokannan tai muiden taustajärjestelmien toimintaa, mutta varmentaa ja dokumentoi mitä odotamme niiden osien tekevän. Huomaa että tämä tekee refaktoroinnista varmempaa ja auttaa myös sovelluksen suunnittelussa.

Jos tämä herätti kiinnostuksen, kannattaa seurata myös dirty-mockito projektia (http://code.google.com/p/dirty-mockito/wiki/JpaSupport), tässä maistiainen siitä:

public class WidgetDaoTest extends ActiveTest<WidgetDao> {

@Mock
private EntityManager em;

@Mock
private Query query;

private WidgetDao widgetDao;

@Test
public void testFindByName() {
Widget widget = new Widget();

when(em.createNamedQuery(”WidgetDao.findByName”)).thenReturn(query);
when(query.getResultList()).thenReturn(Collections.singletonList(widget));

assertSame(widget, widgetDao.findByName(”Bob”));

verify(query).setParameter(”name”, ”Bob”);
}

}

Jos olet utelias, Maven pom.xml tiedosto tälle projektille näyttää tältä:

<project xmlns=”http://maven.apache.org/POM/4.0.0” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance
xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd”>
<modelVersion>4.0.0</modelVersion>
<groupId>fi.tieturi</groupId>
<artifactId>EJB3UnitTestDemo</artifactId>
<packaging>ejb</packaging>
<version>1.0-SNAPSHOT</version>
<name>EJB3MockUnitTestDemo JEE5 EJB (ejb)</name>
<url>http://maven.apache.org</url&gt;
<dependencies>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>

<!– NOTE: This was needed as fix for Absent Code error that you get –>
<!– Put this dependency BEFORE javaee apis so you have persistence implementation available –>
<dependency>
<groupId>net.java.dev.glassfish</groupId>
<artifactId>glassfish-persistence-api</artifactId>
<version>b32g</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javaee</groupId>
<artifactId>javaee-api</artifactId>
<version>5</version>
<scope>provided</scope>
</dependency>

<!– for Mockito mock framework –>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.8.2</version>
<scope>test</scope>
</dependency>

</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ejb-plugin</artifactId>
<version>2.1</version>
<configuration>
<ejbVersion>3.0</ejbVersion>
</configuration>
</plugin>

</plugins>
<finalName>EJB3UnitTestDemo</finalName>
</build>
</project>

EJB 3 Yksikkötestauksesta 2/3

Edellisessä artikkelisarjan osassa tarkasteltiin Javan uutta EJB 3 business komponenttia ja todettiin että sitä pystyy nyt testaamaan suoraan JUnitilla, ilman EJB säiliötä. Ongelmana on kuitenkin se, että aidon elämän EJB komponentit eivät vain laske lukuja yhteen vaan integroivat dataa esim. tietokannoista, sanomapalveluista, jne. Toisin sanoen niiden realistinen testaus edellyttää erilaisten tietolähteiden olemassaoloa – muuten testiajo tuottaa vain NullPointerException:eitä.

Otetaanpa esimerkiksi yksinkertainen EJB Session Bean joka käyttää EntityManager rajapintaa tietokantaoperaatioihin:

@Stateless
public class BankFacadeBean implements BankFacadeLocal {

@PersistenceContext
private EntityManager em;

public EntityManager getEm() {
return em;
}

public void setEm(EntityManager em) {
this.em = em;
}

public void updateAccount(Account a) {
em.merge(a);
}

public void createAccount(Account a) {
em.persist(a);
}

public Account getAccount(Long id) {
return em.find(Account.class, id);
}

}

Tässä on kaksi tapaa lähestyä: Täysin puhdasoppinen tapa on Mock objektien käyttö, mutta se vaatii tietysti mock objektien virittelyn toimivaksi. Tässä on etuna nopeus ja puhdas TDD lähestymistapa. Toinen vaihtoehto on pystyttää testikanta, erillään aidosta, ja tehdä esim. kantaoperaatiot sinne. Tässä on etuna kokonaislogiikan testaus, mutta lähestymistapa ei ole kovin puhdas eikä nopea, ja kannan pystyttäminenkin vaatii ekstratyötä testiympäristössä. Toisaalta se mittaa muutakin kuin logiikkaa, ja antaa realistisen kuvan siitä miten business metodit toimivat kokonaisuudessaan.

Oman EntityManager ratkaisun tekeminen on helppoa, sillä voit tehdä oman persistence unit määrityksen testimoduuliin, ja ladata sen käyttäen Standard Edition mallia – täysin ilman JNDI:tä tai EJB säiliötä. Voit injektoida sen beanillesi (tarvitset get/set mekanismin tai muun tavan injektoida) – ja pystyt testaamaan logiikkaa. Samaa menetelmää voi soveltaa muillekin resursseille, mutta tarvitset tietysti edelleen taustalle vastaavat ajurit, kantapalvelimen, sanomapalvelimen, jne… Muista kuitenkin että jos EJB säiliö ei olekaan välissä, myöskään container-managed transaction, security, jne piirteet eivät toimi. Joten joudut hallinnoimaan myös transaktiot käsin.

Jotta testauksesta saa edes kohteellisen nopeaa, on kätevintä pystyttää muistinvarainen testitietokanta vain testauksen tarpeisiin. Tämä onnistuu helposti käyttämällä esim. JDK 6:sta alkaen sisäänrakennettua Derby-kantaa. Tässä esimerkki testikoodista joka testaa EJB 3.0 session facade komponenttia joka vuorostaan käyttää JPA rajapinnan operaatioita tilitietojen tallennukseen ja lataukseen. Ensiksi setUp ja tearDown metodit:

… snip …

public class BankFacadeBeanTest extends TestCase {

private static Logger logger = Logger.getLogger(BankFacadeBeanTest.class.getName());
private EntityManagerFactory emFactory;
private EntityManager em;

public static final double BALANCE=10000.0;

public BankFacadeBeanTest(String testName) {
super(testName);
}
private BankFacadeBean instance;

@Override
protected void setUp() throws Exception {

super.setUp();
try {
logger.info(”Starting in-memory database for unit tests”);
Class.forName(”org.apache.derby.jdbc.EmbeddedDriver”);
DriverManager.getConnection(”jdbc:derby:memory:unit-testing-jpa;create=true”).close();
} catch (Exception ex) {
ex.printStackTrace();
fail(”Exception during database startup.”);
}

try {
logger.info(”Building JPA EntityManager for unit tests”);
emFactory = Persistence.createEntityManagerFactory(”testPU”);
em = emFactory.createEntityManager();
} catch (Exception ex) {
ex.printStackTrace();
fail(”Exception during JPA EntityManager instanciation.”);
}

instance = new BankFacadeBean();
instance.setEm(em);
}

@Override
protected void tearDown() throws Exception {
super.tearDown();
logger.info(”Shuting down Hibernate JPA layer.”);
if (em != null) {
em.close();
}
if (emFactory != null) {
emFactory.close();
}
logger.info(”Stopping in-memory database.”);
try {
DriverManager.getConnection(”jdbc:derby:memory:unit-testing-jpa;shutdown=true”).close();
} catch (SQLNonTransientConnectionException ex) {
if (ex.getErrorCode() != 45000) {
throw ex;
}
// Shutdown success
}
VFMemoryStorageFactory.purgeDatabase(new File(”unit-testing-jpa”).getCanonicalPath());

}

Yksinkertaista? Eli ennen testiä pystytetään muistinvarainen tietokanta johon voi luoda vapaasti mitä rakenteita haluaa Entity Objecteja varten. Testin jälkeen tearDown metodi käy poistamassa tilapäiskannan ja palataan alkutilaan.

Itse testimetodista on vaikeaa tehdä kovin hienojyväistä, koska yksikkötestimetodit voidaan ajaa riippumatta toisistaan eri järjestyksissä. Voisi olla hyvä idea luoda vähän mallidataa setup metodissa kantaan, mutta yksi vaihtoehto on tehdä se aina testimetodissa. Oikeaoppisesti olisi syytä poistaa testidata mutta tearDown poistaa koko kannan joten siihen ei ole tarvetta. Tässä esimerkki BankFacade komponentin testauksesta:

/**
* Test of updateAccount method, of class BankFacadeBean.
*/
public void testPersistence() {
System.out.println(”updateAccount”);

try {
em.getTransaction().begin();

// find existing account from db
// update account data
Account a = new Account();
//a.setId(Long.valueOf(1000)); // don’t set id, since it’s autoid
a.setBalance(BALANCE);
instance.createAccount(a);
Long id = a.getId();
System.out.println(”Created with account id ”+ id);

// em should know Account a now
assertTrue(em.contains(a));
em.getTransaction().commit();
em.getTransaction().begin();
// Verify that our account exists and has proper balance
Account b = instance.getAccount(id);
assertEquals(BALANCE,b.getBalance());
em.getTransaction().commit();

} catch (Exception ex) {
em.getTransaction().rollback();
ex.printStackTrace();
fail(”Exception during testPersistence”);
}
}

Miten sitten pystytät testikannan? Tässä on JPA:n käyttämä persistence.xml joka kuvaa testikantayhteyden (Maven projekteissa sijoitetaan src/main/resources/META-INF kansioon):

<?xml version=”1.0″ encoding=”UTF-8″?>
<persistence version=”1.0″ xmlns=”http://java.sun.com/xml/ns/persistence”>
<persistence-unit name=”testPU”
transaction-type=”RESOURCE_LOCAL”>
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>bank.Account</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name=”hibernate.connection.url” value=”jdbc:derby:memory:unit-testing-jpa”/>
<property name=”hibernate.connection.driver_class” value=”org.apache.derby.jdbc.EmbeddedDriver”/>
<property name=”hibernate.dialect” value=”org.hibernate.dialect.DerbyDialect”/>
<property name=”hibernate.hbm2ddl.auto” value=”create”/>
<property name=”hibernate.connection.username” value=””/>
<property name=”hibernate.connection.password” value=””/>
</properties>
</persistence-unit>
</persistence>

Siinä se! Tässä artikkeliosassa siis näytettiin miten helposti voit testata EJB 3 komponentteja ilman aitoa EJB säiliötä JUnit yksikkötestien avulla. Voit simuloida kantaresurssia salamannopealla muistikannalla joka myös palautuu alkutilaan käytön jälkeen eli tarjoaa vakioidun testiympäristön. Tämä ei ole välttämättä aitoa Test Driven kehitystä eikä puhdasoppista yksikkötestausta mutta jos haluat nimenomaan testata kantaoperaatioiden toimintaa kokonaisuutena eikä vain logiikkaa, tämä lähestymistapa tarjoaa siihen keinot.

Seuraavassa artikkelisarjan osassa puhutaan Mock objektien käytöstä testauksessa, mikä mahdollistaa puhtaan logiikan testauksen ilman taustaresurssien pystyttämistä.

————————————————————————————————–

Tämä testi on rakennettu hyödyntäen Maven alustaa riippuvuuksien ja kirjastojen suhteen, tässä on pom.xml tiedosto projektille:
<project xmlns=”http://maven.apache.org/POM/4.0.0” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance
xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd”>
<modelVersion>4.0.0</modelVersion>
<groupId>fi.tieturi</groupId>
<artifactId>EJB3UnitTestDemo</artifactId>
<packaging>ejb</packaging>
<version>1.0-SNAPSHOT</version>
<name>EJB3UnitTestDemo JEE5 EJB</name>
<url>http://maven.apache.org</url&gt;
<dependencies>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.5.ga</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>ejb3-persistence</artifactId>
<version>1.0.1.GA</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.3.2.GA</version>
<scope>test</scope>
</dependency>
<!– this is needed for built-in java derby db –>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.5.3.0_1</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>10.5.3.0_1</version>
<scope>test</scope>
</dependency>

<!– NOTE: This was needed as fix for Absent Code error that you get –>
<!– Put this dependency BEFORE javaee apis so you have persistence implementation available –>
<dependency>
<groupId>net.java.dev.glassfish</groupId>
<artifactId>glassfish-persistence-api</artifactId>
<version>b32g</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>javaee</groupId>
<artifactId>javaee-api</artifactId>
<version>5</version>
<scope>provided</scope>
</dependency>

</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ejb-plugin</artifactId>
<version>2.1</version>
<configuration>
<ejbVersion>3.0</ejbVersion>
</configuration>
</plugin>

</plugins>
<finalName>EJB3UnitTestDemo</finalName>
</build>
</project>