poniedziałek, 7 kwietnia 2008

Testowanie ziaren EJB3 z użyciem TestNG

Po pierwszych potyczkach z JPA nadszedł czas na zdobycie pierwszych doświadczeń w pisaniu ziaren EJB3. Najpierw chciałem jednak wybadać sprawy związane z testowaniem tychże ziaren. W poprzednich wersjach EJB z testowaniem było dość ciężko, gdyż jak wiadomo ziarna, żeby działały musiały być osadzone w kontenerze EJB, które były dostępne tylko w serwerach aplikacji. Jak dla mnie, jest to zdecydowanie za skomplikowany i zbyt czasochłonny proces testowania. Niestety ziarna EJB3 również wymagają, żeby uruchamiać je w kontenerze EJB.

Wbudowywalny kontener EJB3

Od pewnego czasu JBoss udostępnia wbudowywalny kontener EJB3. Jest to kontener, który można uruchomić poza serwerem aplikacji. W chwili obecnej kontener ten ma co prawda pewne ograniczenia, ale póki udostępnia wszystkie wymagane przeze mnie usługi nie będę się tym zbytnio przejmował ;)
Niemniej koncepcja wbudowywalnego kontenera EJB3 umożliwia stworzenie, szybkiego i łatwego w użyciu środowiska testów jednostkowych dla ziaren EJB3.

Środowisko testowe EJB3

Przy tworzeniu środowiska testów jednostkowych dla ziaren wykorzystam wspomniany wbudowywalny kontener EJB3.
Do jego uruchomienia wymagane jest, aby w zasięgu classpath'a znajdowały się (oprócz implementacji tego kontenera i bibliotek zależnych) następujące pliki konfiguracyjne: embedded-jboss-beans.xml, ejb3-interceptors-aop.xml, jndi.properties, default.persistence.properties.
W celu uniknięcia umieszczenia tych plików we wszystkich podprojektach modułów EJB tworzących korporacyjną aplikację, jak i określania w każdym z nich zależności od jar'ów wbudowanego kontenera EJB3, postanowiłem stworzyć dedykowany projekt maven'a. Projekt środowiska testów jednostkowych EJB będzie zawierał wspomniane pliki konfiguracyjne, zdefiniowane zależności do jar'ów implementacji kontenera oraz bazową klasę testową, dla testów jednostkowych ziaren.
A oto implementacja bazowej klasy testów jednostkowych:
package pl.dwalczak.ejb3testenv;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.apache.log4j.Logger;
import org.jboss.ejb3.embedded.EJB3StandaloneBootstrap;
import org.jboss.ejb3.embedded.EJB3StandaloneDeployer;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;

abstract public class Ejb3Test {

 private static final Logger LOG = Logger.getLogger(Ejb3Test.class);
 private static final String LOCAL_POSTFIX = "/local";
 private InitialContext initialContext;
 private EJB3StandaloneDeployer deployer;

 @BeforeSuite
 public void startupEjb3() {
  EJB3StandaloneBootstrap.boot(null);
  EJB3StandaloneBootstrap.scanClasspath();

  deployer = EJB3StandaloneBootstrap.createDeployer();
  try {
   deployer.setKernel(EJB3StandaloneBootstrap.getKernel());
   deployer.getArchivesByResource().add("META-INF/ejb-jar.xml");
   deployer.create();
   deployer.start();
   initialContext = new InitialContext();
  } catch (NamingException e) {
   LOG.error("Can't initialize context.", e);
   throw new RuntimeException(e);
  } catch (Exception e) {
   LOG.error("Deployer error.", e);
   throw new RuntimeException(e);
  }
 }

 @AfterSuite
 public void shutdownEjb3() {
  if (null != initialContext) {
   try {
    initialContext.close();
   } catch (NamingException e) {
    LOG.error("Can't close context.", e);
   }
  }
  if (null != deployer) {
   try {
    deployer.stop();
    deployer.destroy();
   } catch (Exception e) {
    LOG.error("Can't close deployer.", e);
   }
  }
  EJB3StandaloneBootstrap.shutdown();
 }

 @SuppressWarnings(value = "unchecked")
 public  T lookup(String beanName, Class businessInterface) {
  T result = null;
  try {
   result = (T) initialContext.lookup(beanName + LOCAL_POSTFIX);
  } catch (NamingException e) {
   LOG.error("Can't lookup bean: " + beanName, e);
   throw new RuntimeException(e);
  }
  return result;
 }
}
Dzięki temu przystosowanie nowego modułu EJB w maven'ie będzie polegało na dodaniu zależności do tego projektu (przy scope ustawionym na test).

Trzeba tu dodać, że bardziej złożona aplikacja korporacyjna będzie raczej wymagała specjalnie dostosowanego projektu środowiska testowego. Niemniej myślę, że przygotowany przeze mnie projekt tegoż środowiska jest dobrym punktem startowym.

Testowanie ziaren EJB3 - przykład

W moim przykładzie mamy do czynienia z bardzo prostym ziarnem:
package pl.dwalczak.ejb3pg1;
import javax.ejb.Stateless;
import org.apache.log4j.Logger;

@Stateless
public class CalculatorBean implements Calculator {
 
 private static Logger LOG = Logger.getLogger(CalculatorBean.class);

 public int add(int x, int y) {
  LOG.debug("add(" + x + ", " + y + ")");
  return x + y;
 }

}
Interfejs biznesowy:
package pl.dwalczak.ejb3pg1;

public interface Calculator {

 int add(int x, int y);
}
Po dodaniu zależności do projektu środowiska testowego można napisać test jednostkowy do tego ziarna, który może wyglądać w sposób następujący:
package pl.dwalczak.ejb3pg1;
import org.testng.Assert;
import org.testng.annotations.Test;
import pl.dwalczak.ejb3testenv.Ejb3Test;

public class CalculatorTest extends Ejb3Test {
 
 @Test
 public void testAdd() {
  Calculator calculator = lookup("CalculatorBean", Calculator.class);
  int sum = calculator.add(2, 2);
  Assert.assertEquals(sum, 4);
 }

}
Jest jeszcze tylko jedna kwestia. Do modułu EJB trzeba dodać plik konfiguracyjny META-INF/ejb-jar.xml, nawet jeśli nie trzeba w nim nic konfigurować. Jest to po prostu niezbędne, aby wykorzystywany kontener zaczytał klasy ziaren EJB3, pomimo że te są skonfigurowane z użyciem adnotacji.

Zasoby

Pliki źródłowe przykładu
Pliki źródłowe ejb3-test-env
Embeddable EJB 3.0
JSR-000220 Enterprise JavaBeans 3.0
JavaTM Platform Enterprise Edition, v 5.0 API Specifications

Brak komentarzy: