我正在编写一个JAX-RS(Jersey + Maven)应用程序,该应用程序执行一些棘手的事情(例如,调用WAR中嵌入的本机可执行文件)。我需要在服务器(运行Tomcat 7.0.22的Amazon Elastic Beanstalk)上运行[某些]单元测试(JUnit4),以检查一切正常。
除了RYO(自己动手)之外,还有其他标准,灵活的方法吗?我发现的事情似乎与开发人员机器上的集成测试(即Jersey测试框架)有关。甚至RYO也让我感到困惑…我如何从源代码包中调用测试包中的代码?
基本上,我想创建一个可以调用的/ test资源,该资源将以漂亮的格式从服务器返回我的单元测试结果。如果我能做/ test / {category}那就更好了
我想分享我在发布此问题后所学到的知识,并在StackExchange上提出了我的第一个答案(我通过Google到达了无数次以寻找无休止的问题的解决方案的网站)
在这个问题上有很多纠正,争论和拖拉,所以我想清除它。这真的非常简单。说您有服务。当您调用它时,我将简单地说明一系列事件:
(已收到请求)-(已调用功能1)-(已调用功能2)-(已调用功能3)-(已发送响应)
单元测试分别隔离地测试每个功能(或类或单元),馈入输入并检查输出。集成测试需要几个单元(例如功能2到功能3链),并且也要进行ol’in-in- out。功能测试贯穿从请求到响应的整个链。我将它留给读者来猜测每个级别的测试的优缺点。无论如何,所有这些测试都可以在服务器中运行,并且有很好的理由可以在其中运行它们。
还有一点要说明。Netbeans将Maven测试的大部分优势赋予了WAR测试。它包括一个嵌入式服务器,并在构建后自动启动并部署到该服务器。它甚至可以打开Firefox …只需将其设置为指向您的/ test资源即可。就像以Maven方式进行操作一样,但效果更好。
无论如何,我将向您展示如何在同一个Maven项目中一起进行Maven测试和WAR测试。
Spring是一个庞大的容器框架。它的依赖注入机制与Jax-RS交织在一起,产生了光荣的效果,但要付出大量学习时间。我不会解释Spring或Jax- RS的工作原理。我将直接跳转至说明中,希望读者可以将这些想法适应其他情况。
使容器进入JUnit 4测试的方法是使用Spring测试运行器,声明要在容器中注册的类,注册一些Jax- RS特定的帮助程序类,注册您的模拟,最后使用您的Jax-RS资源,就好像它是普通类一样:
@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(classes={ MyClass1.class, Myclass2.class, MyJaxRsResource.class, MockServletContextAwareProcessor.class, MyCTest.Config.class }) public class MyCTest { @Configuration static class Config { // Set up and register mocks here, and watch them be autowired! @Bean public DBService dbJobService() throws DBException { return mock(DBService.class); } } @Autowired MyJaxRsResource myResource; @Test public void test() { String response = myResource.get("hello"); } }
@WebAppConfiguration注入自己的ServletContextAwareProcessor。但是,MockServletContextAwareProcessor当必须动态设置解压缩的WAR文件的路径时,这是必需的,因为WebAppConfiguration仅允许您在编译时静态设置路径。运行服务器中的测试时使用此类(请参见下文),我注入了真正的ServletContext。我使用Spring的配置文件功能通过一个环境变量(不是很优雅)来抑制它。setServletContext仅由服务器测试运行程序调用。
@WebAppConfiguration
MockServletContextAwareProcessor
@Configuration public class MockServletContextAwareProcessor { public static void setServletContext(ServletContext sc) { servletContext = sc; } private static ServletContext getServletContext() { return servletContext; } private static ServletContext servletContext; @Configuration @Profile("server-test") static class ServerTestContext { static public @Bean ServletContextAwareProcessor scap() { ServletContext sc = getServletContext(); return new ServletContextAwareProcessor(sc); } } }
步骤1)在/ src / test文件夹中创建常规JUnit测试,但是将其命名为IT * .java或 IT.java或 ITCase.java(例如MyClassIT.java)。您可以使用不同的名称,但这就是故障安全默认情况下期望。IT代表集成测试,但是测试代码可以位于测试连续体的任何位置。例如,您可以实例化一个类并对其进行单元测试,或者可以启动HttpClient(或Jersey Client),将其指向自己(请注意下面的端口),并在功能上测试您的入口点。
public class CrossdomainPolicyResourceSTest extends BaseTestClass { static com.sun.jersey.api.client.Client client; @BeforeClass public static void startClient() { client = Client.create(); } @Test public void getPolicy() { String response = client .resource("http://localhost/crossdomain.xml") .get(String.class); assertTrue(response.startsWith("<?xml version=\"1.0\"?>")); } }
BaseTestClass 只是一个小帮助程序类,它打印测试类的名称并在执行时进行测试(对于服务器中测试很有用,请参见下文):
BaseTestClass
public abstract class BaseTestClass { @ClassRule public static TestClassName className = new TestClassName(); @Rule public TestName testName = new TestName(); @BeforeClass public static void printClassName() { System.out.println("--" + className.getClassName() + "--"); } @Before public void printMethodName() { System.out.print(" " + testName.getMethodName()); } @After public void printNewLine() { System.out.println(); } }
步骤2)将maven-failsafe-plugin和maven-jetty-plugin添加到pom.xml
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.11</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.26</version> <configuration> <!-- By default the artifactId is taken, override it with something simple --> <contextPath>/</contextPath> <scanIntervalSeconds>2</scanIntervalSeconds> <stopKey>foo</stopKey> <stopPort>9999</stopPort> <connectors> <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector"> <port>9095</port> <maxIdleTime>60000</maxIdleTime> </connector> </connectors> </configuration> <executions> <execution> <id>start-jetty</id> <phase>pre-integration-test</phase> <goals> <goal>run</goal> </goals> <configuration> <scanIntervalSeconds>0</scanIntervalSeconds> <daemon>true</daemon> </configuration> </execution> <execution> <id>stop-jetty</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin>
步骤3)获利。真的,就是这样!只需运行“ mvn install”或在IDE中进行构建,即可构建代码,常规的 Test.java测试将运行,码头服务器将启动, IT.java测试将运行,然后您将得到一份不错的报告。
(与上述说明一起使用或分开使用)
第1步:通过指示maven-war-plugin将测试类(src / test /目录)包含在WAR中,以将它们包括在其中:(从此处改编)
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.1.1</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> <webResources> <resource> <directory>${project.build.directory}/test-classes</directory> <targetPath>WEB-INF/classes</targetPath> </resource> <resource> <directory>${project.build.directory}/test-libs</directory> <targetPath>WEB-INF/lib</targetPath> </resource> </webResources> </configuration> </plugin>
注意:您可以通过创建附加执行及其配置集中的(和详细信息,留给读者)创建带有集成测试的单独WAR。
注意:理想情况下,以上内容将排除所有常规测试(并且仅复制* IT.java)。但是,我无法使用包含/排除项。
您还必须通过给予maven-dependency-plugin额外的执行以包含复制范围的目标(包括测试范围)来包括测试库。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.1</version> <executions> <execution> <id>copy-dependencies</id> <phase>prepare-package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <excludeScope>compile</excludeScope> <outputDirectory>${project.build.directory}/test-libs</outputDirectory> <overWriteReleases>true</overWriteReleases> <overWriteSnapshots>true</overWriteSnapshots> <overWriteIfNewer>true</overWriteIfNewer> </configuration> </execution> </executions> </plugin>
如果maven-dependency-plugin已经具有其他执行(例如,Netbeans为javaee-endorsed- api插入了一个),则不要删除它们。
步骤2)使用JUnitCore(JUnit4)以编程方式运行测试。
String runTests() { PrintStream sysOut = System.out; PrintStream sysErr = System.err; ByteArrayOutputStream stream = new ByteArrayOutputStream(); PrintStream out = new PrintStream(stream); try { System.setOut(out); System.setErr(out); TextListener listener = new TextListener(out); JUnitCore junit = new JUnitCore(); junit.addListener(listener); junit.run(MyClassIT.class, AnotherClassIT.class, ...etc...); } finally { System.setOut(sysOut); System.setErr(sysErr); out.close(); } return stream.toString(); }
步骤3)通过JAX-RS公开您的测试
@Path("/test") public class TestResource { @GET @Produces("text/plain") public String getTestResults() { return runTests(); } private String runTests() { ... } }
将此类与您的其他测试类(在src / test中)放在一起,以便它可以引用它们。
但是,如果您在注册所有资源的子类javax.ws.rs.core.Application子类化,则引用TestResource将会遇到问题(因为源代码无法引用测试代码)。要解决此问题,请在src / main / … [same package]下创建一个完全为空的虚拟TestResource类。由于在打包过程中虚拟TestResource将被真实的虚拟TestResource覆盖,因此此技巧可行。
public class ShoppingApplication extends Application { @Override public Set<Class<?>> getClasses() { return new HashSet<Class<?>>() {{ add(TestResource.class); }}; } @Override public Set<Object> getSingletons() { return new HashSet<Object>(); } } package ...same package as the real TestResource... public class TestResource { }
步骤4)设置您的IDE,以启动/部署您的应用程序,并在构建后自动将浏览器指向“ / test”。