java - 在服务器上运行单元测试(JAX-RS)

标签 java unit-testing tomcat junit jax-rs

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

除了RYO(自己动手)以外,还有其他标准,灵活的方法吗?我发现的事情似乎与开发人员机器上的集成测试(即Jersey测试框架)更多有关。甚至RYO也让我感到困惑...我如何从源代码包中调用测试包中的代码?

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

最佳答案

我想分享我在发布此问题后所学到的知识,并在StackExchange上提出了我的第一个答案。
单元vs集成vs功能测试连续统
在这个问题上有很多纠正,争论和拖拉,所以我想清除它。这真的非常简单。说您有一些服务。当您调用它时,我将简单地说明一系列事件:
(已收到请求)-(已调用功能1)-(已调用功能2)-(已调用功能3)-(已发送响应)
单元测试分别隔离地测试每个功能(或类或单元),馈入输入并检查输出。集成测试需要几个单元(例如功能2到功能3链),并且也要进行ol'in-in-out。功能测试贯穿从请求到响应的整个链。我将它留给读者来猜测在每个级别上进行测试的优缺点。无论如何,所有这些测试都可以在服务器中运行,并且有很好的理由可以在其中运行它们。
容器内/服务器内测试的类型

  • 测试中的容器 Spring和其他依赖项注入(inject)框架的功能使您可以为每个测试设置仅填充最低限度的最小类(包括所有模拟)的容器。这非常方便,因为它消除了手动布线的需要,并更好地接近了生产环境。这仅允许进行单元和集成测试。
  • 优点:
    a)传统的单元测试(具有集中测试和隔离测试的优势)更加方便
    b)由于您正在测试 Autowiring 逻辑,因此离生产环境更近
    e)与IDE测试运行器集成
    f)快速
  • 的缺点:
    a)环境可能与生产环境大不相同
    b)不能代替功能测试

  • 测试中的服务器普通的测试运行器运行几乎普通的单元测试,这些单元测试将启动嵌入式服务器或容器并对其进行调用。一些框架(例如Jersey测试框架)仅允许进行功能测试,但是大多数框架(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)它是在构建期间完成的(并将与Continuous Integration工具和其他工具集成)
    b)无需安装或设置任何内容(Maven将自动下载,运行服务器等)
  • 其他缺点:
    a)环境可能非常不同(Maven使用Jetty,并且在您的计算机上运行)
    b)无法在生产
  • 中重新运行

  • 战中测试测试将与您的代码一起永久编译。无论何时何地发生WAR,您都可以启动测试。在开发服务器上,在过渡期间甚至在生产中。这就是我最初的问题。
  • 其他优点:
    a)完全正确的环境。
    b)每当
  • 都运行测试
  • 其他缺点:
    a)需要设置服务器



  • 还有一点要说明。 Netbeans将Maven测试的大部分优势赋予了WAR测试。它包括一个嵌入式服务器,并在构建后自动启动并部署到该服务器。它甚至可以打开Firefox ...只需将其设置为指向您的/test资源即可。就像以Maven方式进行操作一样,但效果更好。
    无论如何,我将向您展示如何在同一个Maven项目中同时进行Maven测试和WAR测试。
    使用Spring进行测试的容器:
    Spring是一个庞大的容器框架。它的依赖项注入(inject)机制与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注入(inject)自己的ServletContextAwareProcessor。但是,当必须动态设置解压缩的WAR文件的路径时,必须使用MockServletContextAwareProcessor,因为WebAppConfiguration仅允许您在编译时静态设置路径。运行服务器中的测试时使用此类(请参见下文),我注入(inject)了真实的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)。您可以使用不同的名称命名,但这就是故障安全默认情况下期望。 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)获利。真的,就是这样!只需运行“mvn install”或在IDE中进行构建,即可构建代码,常规的* Test.java测试将运行, jetty 服务器将启动,* 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将被实际的虚拟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”。

    关于java - 在服务器上运行单元测试(JAX-RS),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8878190/

    相关文章:

    java - 如何使用 Dom4j 测试元素的 CDATA 值?

    unit-testing - 在单元测试中隐藏调用堆栈

    ios - 单元测试和 iOS : viewDidLoad triggered twice

    android - 验证 Mockito 中的列表参数列表

    java - 与tomcat 7建立数据库连接

    java - 相同的java代码不能在不同的机器上运行

    java - 为什么在http请求中编码?

    java - 在java中缩放多页TIFF图像

    Java 是否可以获取 jButton 的变量名称?

    jsp - j_security_check 给出带有正确凭据的 403 页面