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>

Mainokset

Vastaa

Täytä tietosi alle tai klikkaa kuvaketta kirjautuaksesi sisään:

WordPress.com-logo

Olet kommentoimassa WordPress.com -tilin nimissä. Log Out / Muuta )

Twitter-kuva

Olet kommentoimassa Twitter -tilin nimissä. Log Out / Muuta )

Facebook-kuva

Olet kommentoimassa Facebook -tilin nimissä. Log Out / Muuta )

Google+ photo

Olet kommentoimassa Google+ -tilin nimissä. Log Out / Muuta )

Muodostetaan yhteyttä palveluun %s