Embedded Container for EJB test(原创)

EJB的测试一向是个比较头疼的问题。大多数情况下都是把EJB当作POJO来mock测试,并没有拿到容器中去测。
其实JBoss在很早就注意到这个问题,做了一个叫做Embedded JBoss的东东,在跑测试的时候能够直接把JBoss加载到测试的JVM中去运行。 这里是官方给出的介绍,其中很多东西都已经过期了(包括官方给出的例子,居然多不维护了。只有一个能正常运行,地址是( http://anonsvn.jboss.org/repos/jbossas/projects/embedded/examples/tags/6.0.0.Final/) )。
这个例子里面的pom比较重要。如下:

<?xml version="1.0" encoding="UTF-8"?> <!-- vi:ts=2:sw=2:expandtab: --> <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"> <!-- Parent --> <parent> <groupId>org.jboss</groupId> <artifactId>jboss-parent</artifactId> <version>5</version> </parent> <!-- Model Version --> <modelVersion>4.0.0</modelVersion> <!-- Artifact Configuration --> <groupId>org.jboss.jbossas.embedded.examples</groupId> <artifactId>jbossas-embedded-examples-slsb</artifactId> <version>6.0.0.Final</version> <!-- To match target version of AS --> <name>JBoss Embedded AS Examples - SLSB</name> <description>Example for EmbeddedAS for testing EJB 3.x Stateless Session Beans</description> <!-- Properties --> <properties> <version.org.jboss.jbossas>6.0.0.Final</version.org.jboss.jbossas> <JBOSS_HOME>${project.build.directory}/jboss-${version.org.jboss.jbossas}</JBOSS_HOME> <version.junit>4.7</version.junit> </properties> <build> <plugins> <!-- Compiler --> <plugin> <artifactId>maven-compiler-plugin</artifactId> <inherited>true</inherited> <configuration> <source>1.6</source> <target>1.6</target> <showDeprecation>false</showDeprecation> <showWarnings>true</showWarnings> <optimize>true</optimize> <compilerVersion>1.6</compilerVersion> <fork>true</fork> <argLine>-Xmx512M</argLine> <executable>${JAVA_HOME}/bin/javac</executable> </configuration> </plugin> <!-- Surefire --> <plugin> <artifactId>maven-surefire-plugin</artifactId> <configuration> <redirectTestOutputToFile>true</redirectTestOutputToFile> <trimStackTrace>false</trimStackTrace> <printSummary>true</printSummary> <includes> <include>**/*UnitTest.java</include> </includes> <forkMode>always</forkMode> </configuration> </plugin> </plugins> </build> <!-- Dependencies --> <dependencies> <!-- org.jboss.jbossas --> <dependency> <groupId>org.jboss.jbossas</groupId> <artifactId>jboss-as-depchain</artifactId> <version>${version.org.jboss.jbossas}</version> <type>pom</type> </dependency> <!-- JUnit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${version.junit}</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <!-- org.jboss.jbossas --> <dependency> <groupId>org.jboss.jbossas</groupId> <artifactId>jboss-as-depchain</artifactId> <version>${version.org.jboss.jbossas}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <profiles> <profile> <id>embedded</id> <activation> <activeByDefault>true</activeByDefault> </activation> <build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <executions> <execution> <id>integration-test</id> <phase>integration-test</phase> <goals> <goal>test</goal> </goals> <configuration> <additionalClasspathElements> <additionalClasspathElement>${JBOSS_HOME}/client/jbossws-native-client.jar</additionalClasspathElement> <!-- Because jbossweb.sar contains shared web.xml, which must be visible from same CL as TomcatDeployer.class.getClassLoader --> <additionalClasspathElement>${JBOSS_HOME}/server/default/deploy/jbossweb.sar</additionalClasspathElement> </additionalClasspathElements> <redirectTestOutputToFile>true</redirectTestOutputToFile> <trimStackTrace>false</trimStackTrace> <printSummary>true</printSummary> <includes> <include>**/*IntegrationTest.java</include> </includes> <forkMode>always</forkMode> <!-- MaxPermSize Required to bump the space for relective data like classes, methods, etc. EMB-41. Endorsed required for things like WS support (EMB-61) --> <argLine>-Xmx512m -XX:MaxPermSize=256m -Djava.net.preferIPv4Stack=true -Djava.util.logging.manager=org.jboss.logmanager.LogManager -Djava.endorsed.dirs=${JBOSS_HOME}/lib/endorsed -Djboss.home=${JBOSS_HOME} -Djboss.boot.server.log.dir=${JBOSS_HOME}</argLine> </configuration> </execution> </executions> </plugin> <!-- Get AS and put into "target" --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>unpack</id> <phase>pre-integration-test</phase> <!-- So run before testing --> <goals> <goal>unpack</goal> </goals> <configuration> <artifactItems> <artifactItem> <groupId>org.jboss.jbossas</groupId> <artifactId>jboss-as-distribution</artifactId> <version>${version.org.jboss.jbossas}</version> <type>zip</type> <overWrite>false</overWrite> <outputDirectory>${project.build.directory}</outputDirectory> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles> </project>

其中jboss-as-depchain插件可以自动从Jboss的Maven仓库下载对应版本的AS.zip包。
而maven-dependency-plugin经过配置可以把该as.zip包解压开来,最终maven-surefire-plugin用这个解开的Jboss作为embedded container来跑unit test.

后来,EJB3.1引入了这个概念,叫做Embeddable Container,做成了标准。而JBOSS也将之前的Embedded JBoss做进一步发展,并改名为Arquillian,加入到JBossAS7,作为其组件之一。
Arquillian支持三种调用container的方法:
a. remote 根据提供的jndi以及其他jboss参数找到远程已经启动的 application server来做container
b. managed 由Arquillian来管理 application server的生命周期
c. embedded 将application server加载到当前JVM里面去运行。

更具体的资料,可以参考Arquillian官网提供的docs。我另外找了一份中文版的,但是比较落后,仅能做参考(地址猛击 这里
其中有点很重要,就是
java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file
这个问题不仅仅在arquillian中有,embedded jboss,甚至下面提到的embeddable jboss都有这个问题。原因是maven中用到了javaee-api或javaee-web-api的dependency。参见:( https://community.jboss.org/wiki/WhatsTheCauseOfThisExceptionJavalangClassFormatErrorAbsentCode
解决方法有两种:
1. 不要用javaee-api,而用各个as自己提供的包。比如jboss的:

<dependency> <groupId>org.jboss.spec</groupId> <artifactId>jboss-javaee-6.0</artifactId> <version>1.0.0.Final</version> <type>pom</type> <scope>provided</scope> </dependency>

2. 在surefire plugin中把这个包过滤掉:

<plugin>

  <artifactId>maven-surefire-plugin</artifactId>

  <version>2.9</version>

  <configuration>

    <classpathDependencyExcludes>

    <!-- exclude code absent api -->

    <classpathDependencyExclude>javax:javaee-api</classpathDependencyExclude>

    <classpathDependencyExclude>javax:javaee-web-api</classpathDependencyExclude>

  </classpathDependencyExcludes>

</configuration>

</plugin>


JBoss在推Arquillian的同时,也支持ejb3.1新加的embeddable container.个人感觉ejb3.1的方式做起unit test 比 arquillian更简单。
只需要在开始 调用  EJBContainer container = EJBContainer.createEJBContainer();来创建容器,然后InitialContext ctx = new InitialContext();拿到context,后面就可以根据这个context进行lookup找到要测试类或者相关类的bean。远离mock,哈哈~
回到正题,jboss embeddable container可以在 这里找到一个介绍。里面给出了例子以及常见的错误。
对外暴露的包是jboss-ejb3-embedded-standalone,这是个EJB3.1 container的实现类。
原理是maven 的unit test插件,在运行unit test case的时候,EJBContainer.createEJBContianer()会去找实现类,也就是在 jboss-ejb3-embedded-standalone这个包里。
这里有三点值得注意:
1. 直到当前我拿到来用的版本( .0.0-alpha-4),都有一个bug,就是测试类的classpath中不能有非jar包!!
我就是因为在Maven的dependency中加入了一个sar包,结果报

Caused by: java.lang.NullPointerException

at org.jboss.ejb3.embedded.impl.base.scanner.ClassPathEjbJarScanner.isEjbJar(ClassPathEjbJarScanner.java:302)

at org.jboss.ejb3.embedded.impl.base.scanner.ClassPathEjbJarScanner.getEjbJars(ClassPathEjbJarScanner.java:174)

at org.jboss.ejb3.embedded.sub.JBossSubmersibleEJBContainer.createEJBContainer(JBossSubmersibleEJBContainer.java:117)

原因是在isEjbJar这个方法中,它定义了一个Closeable 对象叫handle,null初始化,如果不是jar或者目录就会返回,但是在最后有一个finally,里面没检查,直接调了handle.close() 。 我了个擦,悲剧就这样发生了。。。怎么都起不来。。。
我给JBoss Community提了个bug,地址是: https://issues.jboss.org/browse/EMB-93 不知道他们什么时候会解决。

2.  jboss-ejb3-embedded-standalone里面是直接去classpath里面找JBOSS_HOME或者jboss.home变量,然后启动该变量指向的jboss来启动。所以,如果你的环境变量里面没有JBOSS_HOME,那么必须在maven脚本里面plugin argline里面加上

-Djboss.home=${JBOSS_HOME} -Xmx512m -XX:MaxPermSize=256m -Djava.endorsed.dirs=${JBOSS_HOME}/lib/endorsed

并且定义

<JBOSS_HOME>C:\WorkShop\EclipseWS\IndigoWS\server\design\bcc-embedded-container\jboss-6.1.0.Final</JBOSS_HOME> </properties>

如果有就算了。
在我的项目里面,想尝试着不配置JBOSS,不安装直接用。所以我根据前面embedded jboss给的pom.xml做了个新的。具体如下:

a. 父pom.xml:

<?xml version="1.0" encoding="UTF-8"?> <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>com.test.sample</groupId> <artifactId>test-sample</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-eclipse-plugin</artifactId> <version>2.8</version> <configuration> <useProjectReferences>false</useProjectReferences> </configuration> </plugin> </plugins> <pluginManagement> <plugins> <plugin> <!-- This plugin's configuration is used in m2e to prevent errors on project import in Eclipse --> <groupId>org.eclipse.m2e</groupId> <artifactId>lifecycle-mapping</artifactId> <version>1.0.0</version> <configuration> <lifecycleMappingMetadata> <pluginExecutions> <pluginExecution> <pluginExecutionFilter> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <versionRange>[2.1,)</versionRange> <goals> <goal>unpack</goal> <goal>copy-dependencies</goal> </goals> </pluginExecutionFilter> <action> <ignore /> </action> </pluginExecution> </pluginExecutions> </lifecycleMappingMetadata> </configuration> </plugin> </plugins> </pluginManagement> </build> <modules> <module>embedded-container</module> <module>wolfc-jboss-beach-ejb3-embedded-ee9d31a</module> </modules> </project>

这里面的plugin是绕开eclipse m2e的插件在unpack的地方报错。

b. 自动下载jboss的pom.xml

<?xml version="1.0" encoding="UTF-8"?> <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> <packaging>pom</packaging> <groupId>ericsson.edison</groupId> <artifactId>edison-container-embedded</artifactId> <name>container-embedded</name> <version>0.0.1-SNAPSHOT</version> <description/> <repositories> <repository> <id>jboss-public-repository-group</id> <name>JBoss Public Maven Repository Group</name> <url>https://repository.jboss.org/nexus/content/groups/public-jboss/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <properties> <version.org.jboss.jbossas>6.1.0.Final</version.org.jboss.jbossas> </properties> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <!-- Get AS and put into "target" --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>unpack</id> <phase>pre-integration-test</phase> <!-- So run before testing --> <goals> <goal>unpack</goal> </goals> <configuration> <artifactItems> <artifactItem> <groupId>org.jboss.jbossas</groupId> <artifactId>jboss-as-distribution</artifactId> <version>${version.org.jboss.jbossas}</version> <type>zip</type> <overWrite>false</overWrite> <!--<outputDirectory>${project.build.directory}</outputDirectory>--> <outputDirectory>${basedir}</outputDirectory> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> <!-- <plugin> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>copyRelease</id> <phase>pre-integration-test</phase> <configuration> <tasks> <copy overwrite="true" todir="${basedir}/jboss-${version.org.jboss.jbossas}/bin"> <fileset dir="${basedir}/" includes="run.bat"/> </copy> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> --> </plugins> </build> <dependencies> <dependency> <groupId>org.jboss.jbossas</groupId> <artifactId>jboss-as-depchain</artifactId> <version>${version.org.jboss.jbossas}</version> <type>pom</type> </dependency> </dependencies> <dependencyManagement> <dependencies> <!-- org.jboss.jbossas --> <dependency> <groupId>org.jboss.jbossas</groupId> <artifactId>jboss-as-depchain</artifactId> <version>${version.org.jboss.jbossas}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>

这个脚本会自动从JBoss的仓库把定义版本(这里是6.1.0 Final)的JBoss拉下来,然后解压放到这个项目的jboss- ${version.org.jboss.jbossas}文件夹。注掉的部分是把当前文件夹下的run.bat拷到jboss的bin目录下替换掉原来的。

c. 真正跑test的pom.xml:

<?xml version="1.0" encoding="UTF-8"?> <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>org.jboss.beach.ejb3</groupId> <artifactId>test-embedded</artifactId> <name>test-embedded</name> <version>0.0.1-SNAPSHOT</version> <description/> <repositories> <repository> <id>jboss-public-repository-group</id> <name>JBoss Public Maven Repository Group</name> <url>https://repository.jboss.org/nexus/content/groups/public-jboss/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <properties> <version.org.jboss.jbossas>6.1.0.Final</version.org.jboss.jbossas> <JBOSS_HOME>../embedded-container/jboss-${version.org.jboss.jbossas}</JBOSS_HOME> </properties> <build> <plugins> <!-- <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.2.1</version> <configuration> <executable>env</executable> <environmentVariables> <JBOSS_HOME> C:/Sample/embedded-container/jboss-${version.org.jboss.jbossas} </JBOSS_HOME> </environmentVariables> </configuration> <executions> <execution> <id>resetJbossHome</id> <phase>integration-test</phase> <goals> <goal>exec</goal> </goals> </execution> </executions> </plugin> <plugin> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>copyRelease</id> <phase>pre-integration-test</phase> <configuration> <tasks> <exec executable="env"> <env key="JBOSS_HOME" value="C:/Edison/Docs/Learning/EmbeddableEJB/EmbeddedJBoss/Sample/embedded-container/jboss-${version.org.jboss.jbossas}"/> </exec> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> --> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>2.6</version> <configuration> <argLine>-Djboss.home=${JBOSS_HOME} -Xmx512m -XX:MaxPermSize=256m -Djava.endorsed.dirs=${JBOSS_HOME}/lib/endorsed</argLine> <classpathDependencyExcludes> <!-- exclude code absent api --> <classpathDependencyExclude>javax:javaee-api</classpathDependencyExclude> <classpathDependencyExclude>javax:javaee-web-api</classpathDependencyExclude> </classpathDependencyExcludes> </configuration> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>com.test.sample</groupId> <artifactId>test-sar</artifactId> <version>0.1</version> <type>sar</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.jboss.ejb3.embedded</groupId> <artifactId>jboss-ejb3-embedded-standalone</artifactId> <version>1.0.0-alpha-4</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>[4,)</version> <scope>test</scope> </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>6.0</version> <scope>provided</scope> </dependency> </dependencies> </project>

这里面比较重要的有几点:
a. repositories 
我这里显示的把jboss 的仓库加进来了。当然,你也可以在自己的maven的setting.xml里面加进去,怎么加,这里就不写了。
b. exec-maven-plugin 和 maven-antrun-plugin 被我注释掉了,这里我原来是想尝试着把JBOSS_HOME的环境变量值在maven里面去改变,结果以失败告终。我在stack overflow上还发了个QA,结果没答案,地址:( http://stackoverflow.com/questions/9190342/how-can-i-set-a-environment-variables-in-maven-per-run)
c. javaee-api 这里我是故意加的,以测试maven-failsafe-plugin里面的屏蔽是否成功。

到这里,新的测试ejb的方案已经测试的差不多了。我自己可以写小例子通过了。
不过悲剧的是,当我把方案集成到我们项目中去,各种问题……先是jboss起不来,发现是surefire跑test case时,尝试启动jboss,因为没有配参数,当然失败了。。。。把surefire删掉,结果maven自己会集成它。。。只能用includes和excludes配置,让surefire exclude掉测试类,而failsafe includes进去。。。
可是……jboss是起来了,却抛了一堆异常,搞得我头大。猜测是参数配置问题,但是我实在搞不动了。整整4天全部在搞这个……
疯掉了……NND……万恶的挨踢人啊,为什么不把文档和例子写好点呢? Damn~


一些不错的参考文章:

http://community.jboss.org/wiki/EJB31Embeddable
http://www.adam-bien.com/roller/abien/entry/embedding_ejb_3_1_container
http://www.hascode.com/2011/01/enterprise-java-bean-ejb-3-1-testing-using-maven-and-embedded-glassfish/

另外:Jboss 仓库的查询系统:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值