我想知道对servlet进行单元测试的最佳方法是什么。
测试内部方法不是问题,只要它们不引用servlet上下文,而是测试doGet / doPost方法以及引用上下文或使用会话参数的内部方法呢?
有没有办法简单地使用JUnit等经典工具,或者最好使用TestNG?我是否需要嵌入tomcat服务器或类似的东西?
答案 0 :(得分:44)
大多数时候我通过“集成测试”而不是纯粹的单元测试来测试Servlet和JSP。 JUnit / TestNG有大量附加功能,包括:
这是一个简单的Order Processing Servlet的JWebUnit测试,它处理来自'orderEntry.html'形式的输入。它需要客户ID,客户名称和一个或多个订单商品:
public class OrdersPageTest {
private static final String WEBSITE_URL = "http://localhost:8080/demo1";
@Before
public void start() {
webTester = new WebTester();
webTester.setTestingEngineKey(TestingEngineRegistry.TESTING_ENGINE_HTMLUNIT);
webTester.getTestContext().setBaseUrl(WEBSITE_URL);
}
@Test
public void sanity() throws Exception {
webTester.beginAt("/orderEntry.html");
webTester.assertTitleEquals("Order Entry Form");
}
@Test
public void idIsRequired() throws Exception {
webTester.beginAt("/orderEntry.html");
webTester.submit();
webTester.assertTextPresent("ID Missing!");
}
@Test
public void nameIsRequired() throws Exception {
webTester.beginAt("/orderEntry.html");
webTester.setTextField("id","AB12");
webTester.submit();
webTester.assertTextPresent("Name Missing!");
}
@Test
public void validOrderSucceeds() throws Exception {
webTester.beginAt("/orderEntry.html");
webTester.setTextField("id","AB12");
webTester.setTextField("name","Joe Bloggs");
//fill in order line one
webTester.setTextField("lineOneItemNumber", "AA");
webTester.setTextField("lineOneQuantity", "12");
webTester.setTextField("lineOneUnitPrice", "3.4");
//fill in order line two
webTester.setTextField("lineTwoItemNumber", "BB");
webTester.setTextField("lineTwoQuantity", "14");
webTester.setTextField("lineTwoUnitPrice", "5.6");
webTester.submit();
webTester.assertTextPresent("Total: 119.20");
}
private WebTester webTester;
}
答案 1 :(得分:12)
尝试HttpUnit,尽管您可能最终编写的自动化测试比(单个测试的)单个类的“单元测试”更多是“集成测试”(模块)。
答案 2 :(得分:10)
我查看了发布的答案,并认为我会发布一个更完整的解决方案,实际演示如何使用嵌入式GlassFish及其Apache Maven插件进行测试。
我在我的博客Using GlassFish 3.1.1 Embedded with JUnit 4.x and HtmlUnit 2.x上编写了完整的流程,并将完整的项目下载到Bitbucket:image-servlet
在我看到这个问题之前,我正在查看JSP / JSF标记的图像servlet上的另一篇文章。因此,我将其他帖子中使用的解决方案与此帖子的完整单元测试版本结合起来。
Apache Maven有一个定义良好的生命周期,包括test
。我将使用另一个名为integration-test
的生命周期来实现我的解决方案。
integration-test
integration-test
生命周期内执行。将此插件添加为<build>
。
<plugin>
<groupId>org.glassfish</groupId>
<artifactId>maven-embedded-glassfish-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<!-- This sets the path to use the war file we have built in the target directory -->
<app>target/${project.build.finalName}</app>
<port>8080</port>
<!-- This sets the context root, e.g. http://localhost:8080/test/ -->
<contextRoot>test</contextRoot>
<!-- This deletes the temporary files during GlassFish shutdown. -->
<autoDelete>true</autoDelete>
</configuration>
<executions>
<execution>
<id>start</id>
<!-- We implement the integration testing by setting up our GlassFish instance to start and deploy our application. -->
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
<goal>deploy</goal>
</goals>
</execution>
<execution>
<id>stop</id>
<!-- After integration testing we undeploy the application and shutdown GlassFish gracefully. -->
<phase>post-integration-test</phase>
<goals>
<goal>undeploy</goal>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
在<build>
。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<!-- We are skipping the default test lifecycle and will test later during integration-test -->
<configuration>
<skip>true</skip>
</configuration>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<!-- During the integration test we will execute surefire:test -->
<goal>test</goal>
</goals>
<configuration>
<!-- This enables the tests which were disabled previously. -->
<skip>false</skip>
</configuration>
</execution>
</executions>
</plugin>
添加集成测试,如下例所示。
@Test
public void badRequest() throws IOException {
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
webClient.getOptions().setPrintContentOnFailingStatusCode(false);
final HtmlPage page = webClient.getPage("http://localhost:8080/test/images/");
final WebResponse response = page.getWebResponse();
assertEquals(400, response.getStatusCode());
assertEquals("An image name is required.", response.getStatusMessage());
webClient.getOptions().setThrowExceptionOnFailingStatusCode(true);
webClient.getOptions().setPrintContentOnFailingStatusCode(true);
webClient.closeAllWindows();
}
我在我的博客Using GlassFish 3.1.1 Embedded with JUnit 4.x and HtmlUnit 2.x上编写了完整的流程,并将完整的项目下载到Bitbucket:image-servlet
如果您有任何疑问,请发表评论。我认为这是一个完整的示例,您可以将其用作规划servlet的任何测试的基础。
答案 3 :(得分:6)
您是否在单元测试中手动调用doPost和doGet方法?如果是这样,您可以覆盖HttpServletRequest方法以提供模拟对象。
myServlet.doGet(new HttpServletRequestWrapper() {
public HttpSession getSession() {
return mockSession;
}
...
}
HttpServletRequestWrapper是一个方便的Java类。我建议你在单元测试中创建一个实用工具方法来创建模拟http请求:
public void testSomething() {
myServlet.doGet(createMockRequest(), createMockResponse());
}
protected HttpServletRequest createMockRequest() {
HttpServletRequest request = new HttpServletRequestWrapper() {
//overrided methods
}
}
最好将模拟创建方法放在基类servlet超类中,并使所有servlet单元测试扩展它。
答案 4 :(得分:6)
Mockrunner(http://mockrunner.sourceforge.net/index.html)可以做到这一点。它提供了一个可用于测试Servlet的模拟J2EE容器。它还可以用于单元测试其他服务器端代码,如EJB,JDBC,JMS,Struts。我自己只使用了JDBC和EJB功能。
答案 5 :(得分:3)
这个servlet doPost()方法的JUnit测试实现仅依赖于Mockito库来模拟$(document).ready(function() {
var owl = $("#owl-demo");
owl.owlCarousel({
navigation : false,
singleItem : true,
autoPlay: 3000,
transitionStyle : "fadeUp"
});
});
,HttpRequest
,HttpResponse
,HttpSession
的实例和ServletResponse
。将参数键和JavaBean实例替换为与从中调用doPost()的关联JSP文件中引用的值相对应的参数键和JavaBean实例。
Mockito Maven依赖:
RequestDispatcher
JUnit测试:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
</dependency>
答案 6 :(得分:0)
2018年2月更新:OpenBrace Limited has closed down,不再支持其ObMimic产品。
另一个解决方案是使用我的ObMimic库,它专门用于servlet的单元测试。它提供了所有Servlet API类的完整普通Java实现,您可以根据需要配置和检查这些类。
您确实可以使用它直接从JUnit或TestNG测试中调用doGet / doPost方法,并测试任何内部方法,即使它们引用ServletContext或使用会话参数(或任何其他Servlet API功能)。
这不需要外部或嵌入式容器,并不限制您使用更广泛的基于HTTP的“集成”测试,并且与通用模拟不同,它具有“烘焙”的完整Servlet API行为,因此您的测试可以是基于“状态”而不是基于“交互”的(例如,您的测试不必依赖于代码所做的Servlet API调用的精确序列,也不必依赖于您自己对Servlet API如何响应的期望每次电话会议。)
我对How to test my servlet using JUnit的回答中有一个简单的例子。有关完整详情和免费下载,请访问ObMimic网站。
答案 7 :(得分:0)
此问题的解决方案提出了Mockito How to test my servlet using JUnit 这将任务限制为简单的单元测试,而无需设置任何类似于服务器的环境。