【问题标题】:Running unit tests on the server (JAX-RS)在服务器上运行单元测试 (JAX-RS)
【发布时间】:2012-01-16 09:48:36
【问题描述】:

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

除了 RYO(自己动手)之外,还有其他标准、灵活的方法吗?我发现的东西似乎更多地与开发人员机器上的集成测试有关(即 Jersey 测试框架)。连 RYO 都让我很困惑……我怎么能从 Source Packages 中调用 Test Packages 中的代码?

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

就更好了

【问题讨论】:

  • 什么是 RYO(对于不知道的人)?
  • 滚动你自己的(即,不使用 JUnit 框架)

标签: java unit-testing tomcat junit jax-rs


【解决方案1】:

我想分享我在发布此问题后学到的知识,并将我的第一个答案放在 StackExchange(我无数次通过谷歌访问该网站以寻找解决我无尽问题的解决方案)

单元 vs 集成 vs 功能测试连续体

关于这个问题有很多纠正、争论和拖钓,所以我想澄清一下。这一切真的很简单。假设你有一些服务。当您调用它时,会出现一系列事件,我将简单地说明为:

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

单元测试单独测试每个函数(或类或单元),输入输入并检查输出。集成测试需要几个单元(例如功能 2-功能 3 链),并且还进行 ol' in-and-out。功能测试贯穿整个链条,从请求到响应。我将让读者猜测在每个规模级别进行测试的一些优点和缺点。无论如何,所有这些测试都可以在服务器中运行,并且有充分的理由在那里运行它们。

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

  • Container-in-the-tests Spring 和其他依赖注入框架的一项功能允许您为每个测试设置一个仅填充最少的类(加上所有模拟)的容器。这非常方便,因为它消除了手动布线的需要,并且更好地接近了生产环境。这仅允许单元和集成测试。
    • 优点: a) 传统的单元测试(具有集中测试和隔离测试的优点)变得更加方便 b)更接近生产环境,因为您正在测试自动装配逻辑 e) 与 IDE 测试运行器集成 f) 快速
    • 缺点: a) 环境可能与生产环境有很大不同 b) 不能取代功能测试的需要
  • Server-in-the-tests 一个普通的测试运行器运行几乎普通的单元测试,它启动一个嵌入式服务器或容器,并对其进行调用。一些框架(如 Jersey Testing Framework)只允许功能测试,但大多数(Arquillian、jeeunit)允许你做所有类型的测试。使用其中一些框架,就好像测试在您的代码旁边运行在服务器上,并且可以进行任何类型的调用。
    • 优点(除了您可以访问所有容器和服务器服务的事实): a) 你有独立的测试,不需要安装或设置任何东西 b) 测试是隔离的,因为为每个测试或测试套件创建了一个新的服务器/容器。 b) 与 IDE 测试运行器集成
    • 缺点: a) 环境可能与生产环境有很大不同(例如,Jetty 不是 Tomcat 或 Glassfish) b)启动/停止服务器会减慢测试速度 c)框架很糟糕。 Jeeunit 是一个小项目,甚至还没有在 Windows 上进行过测试,Arquillian 很大但非常新,文档记录很差,我也无法让它工作。
  • Tests-in-the-server 在这里,测试实际上是与您的代码一起编译并与您的代码一起运行的。
    • 优点: 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 ......只需将其设置为指向您的 /test 资源。这就像用 Maven 方式做的,但更好。

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

使用 Spring 的容器测试:

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。但是,当必须动态设置解压 WAR 文件的路径时,MockServletContextAwareProcessor 是必需的,因为 WebAppConfiguration 只允许您在编译时静态设置路径。在运行服务器中的测试时使用这个类(见下文),我注入了真正的 ServletContext。我使用 Spring 的配置文件功能通过环境变量来抑制它(这不是很优雅)。 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) 您可以将它们命名为不同的名称,但这是 Failsafe 默认情况下所期望的。 IT 代表集成测试,但测试代码可以位于测试连续体的任何位置。例如,您可以实例化一个类并对其进行单元测试,或者您可以启动 HttpClient(或 Jersey 客户端),将其指向您自己(注意下面的端口),然后在功能上测试您的入口点。

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 只是一个小助手类,它打印测试类的名称并在执行时进行测试(对服务器中的测试很有用,见下文):

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) 利润。真的,就是这样!只需在 IDE 中运行 'mvn install' 或点击 build,代码将构建,您的常规 *Test.java 测试将运行,码头服务器将启动,*IT.java 测试将运行,您将获得一份不错的报告。

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

(与上述说明一起使用或单独使用)

第 1 步)通过指示 maven-war-plugin 将测试类(src/test/ 目录)嵌入到 WAR 中:(改编自 here

<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 将在打包过程中被真实的覆盖。

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”。

【讨论】:

  • “通过 JAX-RS 公开你的测试”:我明白了,你是说生产代码向公众公开测试吗?您认为这是安全风险吗?正如您在上面指出的那样,我猜它“污染”了代码。
  • 我正在使用 Spring 的配置文件功能,因此可以在生产中禁用测试端点(即,从容器中删除而不实例化)。
【解决方案2】:

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

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

我想这只是单元测试和完全集成测试这两个大领域之间的一小部分,需要容器内测试覆盖。对我来说,我也只需要少量测试来检查服务器资源是否可访问且正常运行。可能应该手写它们,而不是花所有时间研究(然后学习)容器内测试。

【讨论】:

  • 另一个容器内单元测试框架(不是集成测试)解决方案是 Pax-Exam (team.ops4j.org/wiki/display/paxexam/Pax+Exam)。我自己没有使用过它,但我使用的一些开源项目将它们的 OSGi 与该框架集成。
【解决方案3】:

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

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

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

【讨论】:

  • WAR文件在服务器上时不包含Maven项目,所以我认为这不能用于关于生成漂亮输出的部分问题。
  • 你有持续集成环境吗?
  • 如果我这样做会有帮助吗?我可以在 Amazon 中安装一些会调用 WAR 和 Tomcat 环境的东西吗? (我想利用现有的调试钩子。)这样,它会针对它运行单元测试吗?如果我必须为此进行 SSH,那没关系。 (虽然完全不像将它嵌入到 WAR 中那样优雅/灵活。)
  • 像 Jenkins 这样的东西可以运行 maven 目标并按计划为您组织测试输出。 jenkins-ci.org
【解决方案4】:

Jakarta Cactus 似乎已经完成了我正在寻找的事情。它的主页说:“Cactus 是一个简单的测试框架,用于对服务器端 Java 代码进行单元测试……它使用 JUnit……Cactus 实现了容器内策略……”http://localhost:8080/test/ServletTestRunner?suite=TestSampleServlet 这样的 URL 将提供一个漂亮的HTML 输出。

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

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 有趣的想法,如果相当重量级。
    猜你喜欢
    • 2023-03-10
    • 2013-01-01
    • 2012-02-12
    • 2016-11-01
    • 2018-05-07
    • 1970-01-01
    • 1970-01-01
    • 2015-07-11
    • 1970-01-01
    相关资源
    最近更新 更多