在服务器上运行unit testing(JAX-RS)

我正在编写一个JAX-RS(Jersey + Maven)应用程序,它可以执行一些棘手的操作(例如调用WAR中嵌入的本机可执行文件)。 我需要在服务器上运行[部分]unit testing(JUnit4)(运行Tomcat 7.0.22的Amazon Elastic Beanstalk)来检查一切是否正常。

除了RYO(滚动你自己的)之外,还有一种标准的,灵活的方式吗? 我发现的东西似乎更多地与开发者机器上的集成测试(即Jersey测试框架)有关。 甚至RYO让我感到困惑……我怎样才能从源包中调用测试包中的代码?

基本上,我想创建一个我可以调用的/ test资源,它将以漂亮的格式从服务器返回我的unit testing结果。 如果我可以做/ test / {category}更好

我想在发布这个问题之后分享我所学到的内容,并在StackExchange上发布我的第一个答案(我通过谷歌无数次到达的网站寻找无穷无尽问题的解决方案)

单元与集成与function测试连续体

在这个问题上有很多纠正,争论和拖钓,所以我想清除它。 这一切都非常简单。 说你有一些服务。 当你调用它时,我会简单地说明一系列事件:

(收到请求) – (调用函数1) – (调用函数2) – (调用函数3) – (发送响应)

unit testing单独测试每个function(或类或单元),输入输入并检查输出。 集成测试需要几个单元(例如function2function3链),并且还可以进行输入和输出。 function测试贯穿整个链,从请求到响应。 我将把它留给读者来猜测在每个级别的测试中的一些优点和缺点。 无论如何,所有这些测试都可以在服务器中运行,并且有很好的理由来运行它们。

容器内/服务器内测试的类型

  • 容器中的测试 Spring和其他dependency injection框架的一个特性允许您设置一个容器,该容器只填充每个testss的最小类(加上所有模拟)。 这非常方便,因为它不需要手动布线,更好地接近生产环境。 这只允许进行单元和集成测试。
    • 优点:a)传统的unit testing(具有集中和隔离测试的优点)使得更方便b)更接近生产环境,因为您正在测试自动assembly逻辑e)与IDE测试运行器集成f)快速
    • 缺点:a)环境可能与生产相当不同b)不能取代function测试的需要
  • 服务器测试一个普通的测试运行器运行几乎普通的unit testing,启动嵌入式服务器或容器,并调用它。 一些框架(如Jersey测试框架)只允许function测试,但大多数(Arquillian,jeeunit)让你做所有类型。 对于其中一些框架,就好像测试在服务器上运行,并且可以进行任何类型的调用。
    • 优点(除了你可以访问所有容器和服务器服务的事实):a)你有自包含的测试,不需要安装或设置任何东西b)测试是隔离的,因为创建了一个新的服务器/容器对于每个测试或测试套件。 b)与IDE测试运行器集成
    • 缺点:a)环境可能与生产相当不同(例如,Jetty不是Tomcat或Glassfish)b)启动/停止服务器会降低测试速度c)框架很糟糕。 Jeeunit是一个微小的项目,甚至还没有在Windows上进行测试,Arquillian很大但很新,文档记录很差,我也无法让它工作。
  • 在服务器中进行测试在这里,测试实际上是使用代码编译并运行的。
    • 优点:a)您有简单的旧测试,无需了解或使用任何类型的框架
    • 缺点:a)测试之间没有隔离(不一定是问题,甚至是缺点,但可能需要采取预防措施)b)没有与IDE测试运行器集成(至少在Netbeans中)
    • 在构建期间使用Maven Maven启动服务器,加载特殊测试WAR,执行测试,并提供一个很好的Surefire报告。
      • 其他优点:a)它在构建期间完成(并将与持续集成工具和其他工具集成)b)无需安装或设置任何东西(Maven将自动下载,运行等服务器)
      • 其他缺点:a)环境可能相当不同(Maven使用Jetty,它在您的机器上运行)b)无法在生产中重新运行
    • in-WAR测试使用您的代码永久编译测试。 无论何时何地,只要您的WAR启动,您就可以启动测试。 在开发服务器上,在暂存期间,甚至在生产中。 这就是我原来的问题。
      • 其他优点:a)完全正确的环境。 b)每当运行测试
      • 其他缺点:a)需要设置服务器

还有一点要做。 Netbeans将Maven测试的大部分好处提供给WAR测试。 它包括一个嵌入式服务器,并在构建后自动启动和部署。 它甚至可以打开Firefox ……只需将其设置为指向您的/测试资源即可。 就像用Maven那样做,但更好。

无论如何,我将向您展示如何在同一个Maven项目中一起进行Maven测试和in-WAR测试。

使用Spring的容器测试:

Spring是一个庞大的容器框架。 它的dependency injection机制与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。 但是,当必须动态设置解压缩WAR文件的路径时, MockServletContextAwareProcessor是必需的,因为WebAppConfiguration只允许您在编译时静态设置路径。 在运行-sus-in-the-server(见下文)时使用这个类,我注入了真正的ServletContext。 我使用Spring的配置文件function通过环境变量来抑制它(这不是很优雅)。 setServletContext只是由服务器测试运行器调用。

 @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); } } } 

使用Maven的测试服务器:

步骤1)在/ src / test文件夹中创建常规JUnit测试,但将它们命名为IT * .java或* IT.java或* ITCase.java(例如,MyClassIT.java)您可以对它们进行不同的命名,但这就是故障安全预计默认情况下。 IT代表集成测试,但测试代码可以位于测试连续体的任何位置。 例如,您可以实例化一个类并对其进行unit testing,或者您可以启动HttpClient(或Jersey客户端),将其指向您自己(请注意下面的端口),并在function上测试您的入口点。

 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("")); } } 

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中

  org.apache.maven.plugins maven-failsafe-plugin 2.11    integration-test verify      org.mortbay.jetty maven-jetty-plugin 6.1.26   / 2 foo 9999   9095 60000      start-jetty pre-integration-test  run   0 true    stop-jetty post-integration-test  stop     

第3步)利润。 真的,就是这样! 只需运行’mvn install’或在IDE中点击构建,代码将构建,你的常规* Test.java测试将运行,jetty服务器将启动,* IT.java测试将运行,你会得到一个很好的报告。

将您的测试打包在WAR中以便在任何地方运行:

(与上述说明一起使用或分开使用)

步骤1)通过指示maven-war-plugin包含它们来获取WAR中嵌入的测试类(src / test /目录):(从此处改编)

  org.apache.maven.plugins maven-war-plugin 2.1.1  false   ${project.build.directory}/test-classes WEB-INF/classes   ${project.build.directory}/test-libs WEB-INF/lib     

注意:您可以通过创建额外的执行并在其配置集中创建具有集成测试的单独WAR(我留给读者的详细信息)

注意:理想情况下,上面会排除所有常规测试(并且只复制* IT.java)但是,我无法获得包含/排除工作。

您还必须通过为maven-dependency-plugin提供额外的执行来包含测试库,其目标是包含测试范围的复制依赖性

  org.apache.maven.plugins maven-dependency-plugin 2.1   copy-dependencies prepare-package  copy-dependencies   compile ${project.build.directory}/test-libs true true true     

如果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 / … [相同包]下创建一个完全空的虚拟TestResource类…这个技巧有效,因为虚拟TestResource在打包时会被真实的TestResource覆盖。

 public class ShoppingApplication extends Application { @Override public Set> getClasses() { return new HashSet>() {{ add(TestResource.class); }}; } @Override public Set getSingletons() { return new HashSet(); } } package ...same package as the real TestResource... public class TestResource { } 

步骤4)设置IDE以启动/部署您的应用程序,并在构建后自动打开浏览器指向“/ test”。

获胜关键字原来是“容器内测试”。 全新的卓越框架是Arquillian 。

奇怪的是,似乎没有别的东西。 StackOverflow上的其他人问道: “我没有看到任何这些项目被广泛使用,所以容器内测试有什么不好吗?” 但没有得到明确答复。

我想这只是unit testing和完整集成测试这两大领域之间的一个小区域,需要通过容器内测试来解决。 对我而言,我只需要进行一些测试来检查服务器资源是否可访问且function正常。 可能应该手工编写它们而不是花费所有这些时间来研究(然后学习)容器内测试。

使用Maven,Surefire可以为您提供测试结果的格式化报告。

http://maven.apache.org/plugins/maven-surefire-report-plugin/report-mojo.html

有许多方法可以使这些报告的内容可用,无论是发送给您还是发布到网页。 你有很多选择。

雅加达仙人掌似乎已经完成了我想要的。 它的主页说:“Cactus是一个简单的测试框架,用于unit testing服务器端的Java代码…它使用JUnit … Cactus实现了一个容器内策略…”一个URL,例如http:// localhost:8080 / test / ServletTestRunner?suite = TestSampleServlet将提供漂亮的HTML输出。

但是,由于缺乏活跃的开发,Apache基金会将其置于阁楼中。 这是否意味着我不应该考虑使用它? Attic页面说“鼓励Cactus用户切换到其他测试技术”而不解释它们是什么!

我不认为有一种标准的方法,但您可以使用Spring Remoting从您的开发人员计算机调查您感兴趣的服务器上的方法。 如果你使用接口并注入你正在测试的服务,你应该能够在本地运行两次相同的unit testing,一次只在服务器上运行,只需更改Spring配置即可。