結合テスト
Arquillian
アーキテクチャ
★結合テストを実行する前の注意点
1.$JBOSS_HOME/standalone/deployments フォルダに何もないこと
2.standalone.xmlに<deployments>を削除すること
3.実行例
clean install -Pci -pl services-impl pre-integration-test -DskipTests=true
★コンテナとの連携方法
・remote 既に起動しているコンテナに対してテストを行う
・managed 指定されたコンテナを利用してテストを行う(コンテナの起動と停止は自動的に)
・embedded 組込みコンテナを利用して全てがJVM上で行う
※remoteやmanaged 環境の再現性が高く、インテグレーションテスト向け
※embedded 環境の再現性が低く、単体テスト向け
profile設定(JBossASの場合)
<profile>
<id>arq-jbossas-managed</id>
<dependencies>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-arquillian-container-managed</artifactId>
<version>7.1.1.Final</version>
<scope>test</scope>
</dependency>
</dependencies>
</profile>
<profile>
<id>arq-jbossas-remote</id>
<dependencies>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-arquillian-container-remote</artifactId>
<version>7.1.1.Final</version>
<scope>test</scope>
</dependency>
</dependencies>
</profile>
remote実行
mvn -P arq-jbossas-remote test
※事前にJBoss ASを起動
managed実行
$JBOSS_HOME=/opt/jboss-eap-6.1 mvn -P arq-jbossas-managed test
embedded実行(GlassFishの場合)
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-glassfish-embedded-3.1</artifactId>
<version>1.0.0.CR3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.main.extras</groupId>
<artifactId>glassfish-embedded-web</artifactId>
<version>3.1.2.2</version>
<scope>test</scope>
</dependency>
★単体Frameworkとの連携
TestNGの場合
<dependency>
<groupId>org.jboss.arquillian.testng</groupId>
<artifactId>arquillian-testng-container</artifactId>
<version>1.0.0.CR1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.4</version>
<scope>test</scope>
</dependency>
JUnitの場合
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<version>1.0.0.CR1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
//JUnit
import org.junit.runner.RunWith;
import org.jboss.arquillian.junit.Arquillian;
//TestNG
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.arquillian.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archives;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.impl.base.asset.ByteArrayAsset;
//JUnit
@RunWith(Arquillian.class)
public class XxxIT {
//TestNG
public class XxxIT extends Arquillian {
★設定ファイル:arquillian.xml
<arquillian ...>
<defaultProtocol type="Servlet 3.0" />
<container qualifier="glassfish-embedded" default="true">
<configuration>
<property name="resourcesXml">
src/test/resources/glassfish-resources.xml
</property>
</configuration>
</container>
<container qualifier="jboss-local" default="false">
<configuration>
<property name="javaVmArguments">
-Djboss.socket.binding.port-offset=${jboss-as.port.offset}
-Xmx512m -XX:MaxPermSize=360m
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8787
</property>
<property name="managementPort">
${jboss-as.management.port}
</property>
<property name="outputToConsole">true</property>
</configuration>
</container>
<container qualifier="jboss-ci" default="false">
<configuration>
<property name="javaVmArguments">
-Djboss.socket.binding.port-offset=${jboss-as.port.offset}
-Xmx512m -XX:MaxPermSize=360m
</property>
<property name="jbossHome">${jboss-as.home}</property>
<property name="managementPort">
${jboss-as.management.port}
</property>
<property name="outputToConsole">true</property>
</configuration>
</container>
</arquillian>
※デバッグ手順:(青字を追加)
Step1 Debug As → TestNG Test / JUnit Test
Step2 Remote Java Application
glassfish-resources.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC
"-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN"
"http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
<jdbc-resource pool-name="H2Pool" jndi-name="jdbc/xxx" />
<jdbc-connection-pool name="H2Pool"
res-type="javax.sql.XADataSource"
datasource-classname="org.h2.jdbcx.JdbcDataSource">
<property name="url" value="jdbc:h2:mem:;DB_CLOSE_DELAY=-1" />
</jdbc-connection-pool>
</resources>
★Mode
In-container mode(By default)
@Stateless
public class UserServiceBean {
@PersistenceContext
private EntityManager em;
public User addUser(User user) {
em.persist(user);
return user;
}
// Transactionなし
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public User findById(Long id) {
return em.find(User.class, id);
}
}
public class UserServiceBeanIT extends Arquillian {
private static final Logger LOGGER
= Logger.getLogger(UserServiceBeanIT.class.getName());
@Inject
private UserServiceBean service;
@Deployment
public static JavaArchive createTestArchive() {
final JavaArchive jar
= ShrinkWrap.create(JavaArchive.class, "test.jar")
.addClasses(User.class, UserServiceBean.class)
.addAsManifestResource(
"META-INF/persistence.xml", "persistence.xml")
.addAsManifestResource(
EmptyAsset.INSTANCE,
ArchivePaths.create("beans.xml"));
LOGGER.info(jar.toString(Formatters.VERBOSE));
return jar;
}
@Test
public void shouldXxx() {
final User user = new User("Andy");
service.addUser(user);
assertNotNull(user.getId());
}
}
Client mode
@WebServlet("/user")
public class UserServlet extends HttpServlet {
@Inject
private UserServiceBean service;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
...
User user = service.addUser(new User("Andy");
...
}
...
}
public class UserServletIT extends Arquillian {
private static final Logger LOGGER
= Logger.getLogger(UserServletIT.class.getName());
@Deployment(testable = false)
public static WebArchive createTestArchive() {
final WebArchive war
= ShrinkWrap.create(WebArchive.class, "test.war")
.addClasses(
User.class, UserServiceBean.class, UserServlet.class)
.addAsResource("META-INF/persistence.xml")
.addAsWebInfResource(
EmptyAsset.INSTANCE,
ArchivePaths.create("beans.xml"));
LOGGER.info(war.toString(Formatters.VERBOSE));
return war;
}
@RunAsClient
@Test(dataProvider = Arquillian.ARQUILLIAN_DATA_PROVIDER)
public void shouldXxx(@ArquillianResource URL baseURL) throws IOException {
final URL url = new URL(baseURL, "user"); //<context_path>/user
final User user = new User(1L, "Andy");
StringBuilder sb = new StringBuilder();
BufferedReader reader
= new BufferedReader(new InputStreamReader(url.openStream()));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.close();
assertEquals(user.toString(), sb.toString());
}
}
Mixed mode
★HtmlUnit
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>2.8</version>
<scope>test</scope>
</dependency>
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.Filters;
import org.jboss.shrinkwrap.api.GenericArchive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.ClassLoaderAsset;
import org.jboss.shrinkwrap.api.importer.ExplodedImporter;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
import org.junit.runner.RunWith;
@RunWith(Arquillian.class)
public class ArquillianHtmlUnitTest {
@Deployment
public static WebArchive createTestArchive() {
Collection<String> dependencies = Arrays.asList(new String[]{
"javax.servlet:jstl",
"taglibs:standard",
"commons-lang:commons-lang"
});
File[] libs = Maven.resolver()
.loadPomFromFile("pom.xml").resolve(dependencies)
.withTransitivity().asFile();
WebArchive war = ShrinkWrap.create(WebArchive.class, "test.war")
// /WEB-INF/classes/*
.addClasses(MyManager.class, ActionServlet.class)
// /WEB-INF/classes/META-INF/*
.addAsResource(new ClassLoaderAsset("META-INF/ejb-jar.xml"),
"META-INF/ejb-jar.xml")
.addAsResource(new ClassLoaderAsset(
"META-INF/persistence.xml"),"META-INF/persistence.xml")
.addAsLibraries(libs); // /WEB-INF/lib/*
war.merge(ShrinkWrap.create(GenericArchive.class)
.as(ExplodedImporter.class)
.importDirectory("src/main/webapp")
.as(GenericArchive.class), "/", Filters.includeAll());
return war;
}
@ArquillianResource
private URL deploymentUrl; //http://localhost:xxx/test/
@Before
@After
public void clean() {
// DBのクリーン処理など
}
@Test
public void shouldXxx() throws Exception {
WebClient webClient = new WebClient();
//JSP例
HtmlPage page = webClient.getPage(deploymentUrl + "/xxx.jsp");
assertPresent(page);
//Servlet例
page = webClient.getPage(deploymentUrl + "/yyy");
assertPresent(page);
webClient.closeAllWindows();
}
private void assertPresent(HtmlPage page) {
String pageAsText = page.asText();
assertTrue(pageAsText.contains("..."));
}
}
★Testing EJB
方法1 Arquillian
@RunWith(Arquillian.class)
public class EjbIT {
@Deployment
public static JavaArchive createTestArchive() {
return Archives.create("test.jar", JavaArchive.class)
.addClasses(MyManager.class, MyManagerBean.class);
}
//あるいは
@Deployment
public static WebArchive createTestArchive() {
return ShrinkWrap.create(WebArchive.class, "test.war")
.addClasses(MyManager.class, MyManagerBean.class)
.addAsResource(new ClassLoaderAsset("META-INF/ejb-jar.xml"), "META-INF/ejb-jar.xml")
.addAsResource(new ClassLoaderAsset("META-INF/persistence.xml"), "META-INF/persistence.xml");
}
@EJB
private MyManager myManager;
@Test
public void shouldXxx() throws Exception {
assertEquals("...", myManager.method("xxx"));
}
}
方法2 EmbeddedEJB
import javax.ejb.embeddable.EJBContainer;
public class EjbIT {
private static EJBContainer ejbContainer;
@BeforeClass
public static void setUp() throws Exception {
ejbContainer = EJBContainer.createEJBContainer();
}
@AfterClass
public static void tearDown() {
if (ejbContainer != null) {
ejbContainer.close();
}
}
@Test
public void shouldXxx() throws Exception {
MyManagerBean bean =
(MyManagerBean) ejbContainer.getContext().lookup(
"java:global/<project>/MyManagerBean!<package>.MyManagerBean");
...
}
}
★Testing CDI
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
@RunWith(Arquillian.class)
public class CdiIT {
@Deployment
public static JavaArchive createTestArchive() {
return Archives.create("test.jar", JavaArchive.class)
.addClasses(MyManager.class, MyManagerBean.class)
.addManifestResource(new ByteArrayAsset(new byte[0])),
ArchivePaths.create("beans.xml"));
}
@Inject
private MyManager myManager;
@Inject
private BeanManager beanManager;
@Test
public void shouldXxx() throws Exception {
assertNotNull(beanManager);
assertEquals("...", myManager.method("xxx"));
}
}
★Testing JPA
@Stateless
public class UserRepositoryBean implements UserRepository {
@PersistenceContext
private EntityManager em;
public void addUser(User u) {
em.persist(u);
em.flush();
}
public List<User> findByName(String name) {
return em.createQuery("SELECT u FROM User u WHERE u.name = :name")
.setParameter("name", name)
.getResultList();
}
}
@RunWith(Arquillian.class)
public class UserRepositoryIT {
private static final String NAME = "Andy";
@Deployment
public static JavaArchive createTestArchive() {
return Archives.create("test.jar", JavaArchive.class)
.addClasses(User.class, UserRepository.class, UserRepositoryBean.class)
.addManifestResource(
"test-persistence.xml", ArchivePaths.create("persistence.xml"));
}
@EJB
private UserRepository userRepository;
@Test
public void shouldXxx() {
User u = new User(NAME);
userRepository.addUser(u);
List<User> users = userRepository.findByName(NAME);
assertNotNull(users);
assertTrue(users.size() == 1);
assertEquals(users.get(0).getName(), NAME);
}
}
★Testing JMS
import javax.annotation.Resource;
import javax.jms.*;
@RunWith(Arquillian.class)
public class JmsIT {
@Deployment
public static JavaArchive createTestArchive() {
return Archives.create("test.jar", JavaArchive.class)
.addClasses(MyMessageEcho.class, MyQueueRequestor.class);
}
@Resource(mappedName="/queue")
private Queue queue;
@Resource(mappedName="/ConnectionFactory")
private ConnectionFactory factory;
@Test
public void shouldXxx() throws Exception {
String messageBody = "xxx";
Connection connection = factory.createConnection();
Session session
= connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MyQueueRequestor requestor
= new MyQueueRequestor((QueueSession) session, queue);
connection.start();
Message request = session.createTextMessage(messageBody);
Message response = requestor.request(request, 5000);
assertEquals(messageBody, ((TextMessage) response).getText());
}
}
★Testing JAX-RS
@RequestScoped
@Path("calc")
public class Calc {
@Inject
private CalcBean calcBean;
@GET
@Path("add")
public int add(@QueryParam("x") int x, @QueryParam("y") int y) {
return calcBean.add(x, y);
}
}
@Stateless
@LocalBean
public class CalcBean {
public int add(int x, int y) {
return x + y;
}
}
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.sun.jersey.api.client.Client;
@RunWith(Arquillian.class)
public class CalcTest {
@Deployment
public static WebArchive createDeployment() {
Path webinf = Paths.get("src", "main", "webapp", "WEB-INF");
return ShrinkWrap
.create(WebArchive.class, "test.war")
.addClasses(Calc.class, CalcBean.class, CalcApplication.class)
.addAsWebInfResource(webinf.resolve("beans.xml").toFile());
}
@Inject
private Calc calc;
@Test
public void addUT() throws Exception {
int result = calc.add(1, 2);
assertThat(result, is(3));
}
@Test
@RunAsClient
public void addIT() throws Exception {
String response =
Client
.create()
.resource("http://localhost:8080/xxx/calc/add")
.queryParam("x", "1")
.queryParam("y", "2")
.get(String.class);
assertThat(response, is("3"));
}
}
★ShrinkWrap (Archives)
import static org.apache.ziplock.JarLocation.jarLocation;
import static org.jboss.shrinkwrap.resolver.api.maven.Maven.resolver;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.Filters;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
public class Deployments {
@Deployment
public static Archive<?> deployment() {
Archive<?> archive = ShrinkWrap
.create(EnterpriseArchive.class, "test.ear")
.addAsModule(createWar())
.addAsLibrary(createJar())
.addAsApplicationResource(
"META-INF/jboss-deployment-structure.xml",
"jboss-deployment-structure.xml");
System.out.println(archive.toString(true));
return archive;
}
private static Archive<?> createWar() {
Archive<?> archive = ShrinkWrap
.create(WebArchive.class, "test.war")
.addAsResource("META-INF/persistence.xml")
.addAsDirectory("WEB-INF/classes")
.addAsWebInfResource("META-INF/beans.xml", "beans.xml")
.addAsWebInfResource("META-INF/testPersistence.xml",
"classes/META-INF/persistence.xml")
.addAsWebInfResource(
new File("src/test/resources", "glassfish-web.xml"))
.addAsWebInfResource(
new File("src/main/webapp", "WEB-INF/beans.xml"))
.addAsLibraries(createJar())
.addAsLibraries(jarLocation(MyBean.class))
.addAsLibraries(
resolver().resolve("groupId:artifactId:version")
.withTransitivity().asFile());
System.out.println(archive.toString(true));
return archive;
}
private static Archive<?> createJar() {
Archive<?> archive = ShrinkWrap
.create(JavaArchive.class, "test.jar")
.addClass(MyBean.class)
.addClasses(MyBeanA.class, MyBeanB.class)
.addPackage("com.xxx")
.addPackage(MyBean.class.getPackage())
.addPackages(true, "com.xxx", "com.yyy")
.addAsResource("META-INF/beans.xml", "beans.xml")
.addAsResource(EmptyAsset.INSTANCE, "META-INF/beans.xml")
.addAsResource(
"META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsManifestResource(EmptyAsset.INSTANCE,
ArchivePaths.create("beans.xml"))
.addAsManifestResource(
new StringAsset(
"<decorators><class>com.test.MyDecorator<class></decorators>"),
"beans.xml");
System.out.println(archive.toString(true));
return archive;
}
private static void export(Archive<?> archive, String path) {
archive.as(ZipExporter.class).exportTo(new File(path), true);
}
private static void deleteJar(Archive<?> archive) {
String regexp = "^/WEB-INF/lib/XXX.*\\.jar$";
Map<ArchivePath, Node> content = archive.getContent(Filters.include(regexp));
for (ArchivePath ap : content.keySet()) {
archive.delete(ap.get());
}
archive.delete("/WEB-INF/lib/XXX.jar");
}
}
★ShrinkWrap Resolvers (Maven, Gradle)
<dependency>
<groupId>org.jboss.shrinkwrap.resolver</groupId>
<artifactId>shrinkwrap-resolver-depchain</artifactId>
<version>${shrinkwrap.resolvers.version}</version>
<scope>test</scope>
<type>pom</type>
</dependency>
@Deployment
public static Archive<?> createTestArchive() {
// Usage: GroupId:ArtifactId:[Packaging:Classifier:]Version
String[] deps = {
"org.apache.commons:commons-lang3:3.1",
"org.apache.commons:commons-lang3:jar:sources:3.1"
};
File[] libs = Maven.configureResolver()
.fromClassloaderResource("custom-settings.xml")
.resolve(deps)
.withMavenCentralRepo(false)
.withoutTransitivity()
.asFile();
File[] libs = Maven.resolver()
.resolve(deps)
.withTransitivity()
.asFile();
File[] libs = Maven.resolver()
.loadPomFromFile("pom.xml")
.resolve("GroupId:ArtifactId")
.withTransitivity()
.asFile(); // as(File.class)
File[] libs = Maven.resolver()
.loadPomFromFile("pom.xml")
.importRuntimeDependencies()
.resolve()
.withTransitivity()
.asFile();
File[] libs = Maven.resolver()
.loadPomFromFile("pom.xml")
.importCompileAndRuntimeDependencies()
.resolve()
.withoutTransitivity()
.asFile();
File lib = Maven.resolver()
.resolve("GroupId:ArtifactId:Version")
.withoutTransitivity()
.asSingleFile(); // asSingle(File.class)
return ShrinkWrap.create(WebArchive.class, "test.war")
.addClasses(MyBean.class)
.addAsResource(
"META-INF/test-persistence.xml", "META-INF/persistence.xml")
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsLibraries(libs)
.addAsModule(lib, "test.jar")
.addAsWebInfResource("test-ds.xml");
}
WebArchive webArchive = ShrinkWrap.create(MavenImporter.class)
.loadPomFromFile("pom.xml")
.importBuildOutput()
.as(WebArchive.class);
WebArchive webArchive = ShrinkWrap.create(EmbeddedGradleImporter.class)
.forProjectDirectory(dir)
.importBuildOutput()
.as(WebArchive.class);