- 浏览: 188501 次
- 性别:
- 来自: 武汉
文章分类
- 全部博客 (99)
- IT生活 (9)
- Oracle (3)
- java模式 (0)
- j2se (9)
- uml (0)
- javabean (3)
- 网络 (3)
- java 并发 (2)
- java xml (4)
- POI (1)
- jbpm (2)
- apache httpclient (1)
- tomcat (5)
- ssh2 错误 (2)
- mina_xsocket (0)
- struts2 (1)
- easymock (2)
- tomcat远程调试和加入JMS (1)
- eclipse_C/C++ (1)
- 数据结构 (1)
- spring (3)
- 工作记录 (6)
- 学习英语 (1)
- flex4_flexViewer_arcgis (4)
- esb (0)
- java多线程 (3)
- jvm_并发编程 (1)
- 流程 (1)
- 测试 (1)
- terracotta集群 (3)
- jvm_调优_gc_监控命令 (11)
- IT _视频 (1)
- log4jUtil (2)
- jrebel使用 (1)
- lucene-solr (3)
- linux_centos (4)
- hadoop (3)
- 集群部署 (1)
最新评论
-
叮咚可乐名:
Java并发编程和高并发解决方案视频课程网盘地址:https: ...
java处理高并发高负载类网站的优化方法 . -
q8txy8q:
非常不错,thanks.
推荐一些JVM原理,JVM调优,JVM内存模型,JAVA并发 电子书1 -
maimode:
好东西,非常实用
java操作XML文件 dom 篇 -
junJZ_2008:
嗯,就应该这样,常言道:好记性,不如乱笔头~!现在不知道是年纪 ...
第一次写博客
Spring Mock Web简介
Spring针对J2EE的常用Web接口提供了Mock,这些组件被发布于spring-mock.jar,介绍如下:
q MockHttpServletRequest:HttpServletRequest接口的mock实现。
q MockHttpServletResponse:HttpServletResponse接口的mock实现。
q MockHttpSession:HttpSession接口的mock实现。
q DelegatingServletInputStream:ServletInputStream接口的委托mock实现。
q DelegatingServletOutputStream: ServletOutputStream接口的委托mock实现,在需要拦截和分析写向一个输出流的内容时,可以使用它。
说明:在提供关于Controller(控制器)的测试时,以上这些对象是最常用的。
此外,Spring还提供了一些其它组件的mock实现:
q MockExpressionEvaluator:基于JSTL的定制标签库的mock实现
q MockFilterConfig:FilterConfig接口的mock实现。
q MockPageContext:JSP PageContext接口的mock实现,用于测试预编译的JSP。
q MockRequestDispatcher:RequestDispatcher接口的mock实现,它主要结合其它Mock来使用。
q MockServletConfig:ServletConfig接口的mock实现,在测试某类(如Struts框架)所提供的Web组件时,要求设置由MockServletContext所实现的ServletConfig和ServletContext接口
以上这些Mock位于“org.springframework.mock.web”包下,下一节中将看到Spring Mock所提供的其它功能。
扩展JUnit框架的测试基类
除了上节中介绍的Web Mock,Spring Mock包还提供了一些扩展自JUnit框架的测试基类,这些基类简化了对依赖注射和事务管理的单元测试,介绍如下:
q AbstractDependencyInjectionSpringContextTests:依赖于Spring Context组件的测试基类。
q AbstractTransactionalSpringContextTests:与事务相关的测试基类。
说明:AbstractTransactionalSpringContextTests定制的行为方式是对事务进行正常回滚。在实际使用时,需要重载onSetUpInTransaction()和onTearDownInTransaction()方法,以手工开始并提交事务。
q AbstractTransactionalDataSourceSpringContextTests:使用了Spring的JdbcTemplate来辅助测试,它是AbstractTransactionalSpringContextTests的子类。
以上这些Mock位于“org.springframework.test”包下。
Web组件的单元测试:搭建测试环境
EasyMock是一个Mock对象的类库。本书使用的EasyMock版本是2.2,可在http://sourceforge.net/projects/easymock下载到。
Mock 对象能够模拟领域对象的部分行为,并且能够检验运行结果是否和预期的一致。领域类将通过与Mock 对象的交互,来获得一个独立的测试环境。EasyMock可以动态地生成Mock 对象而不需要编写它们,因为也不会产生多余代码。
说明:默认情况下,EasyMock只支持为接口生成Mock。如果还需要为类生成Mock,可在上述网址下载EasyMock的扩展包以完成此功能。本书使用的EasyMock扩展包是2.2.1版本,它只能在Java 5.0以上的版本中运行。
一、组件单元测试:搭建测试环境
为了搭建一个真实的测试环境,接下来将使用EashMock框架来模拟宠物店的业务核心接口PetStoreFacade,并模拟实现其子类PetStoreImpl的行为,其中涉及和一些相关领域对象、DAO的交互。
创建测试骨架,如代码1所示。
代码1 PetStoreFacadeTest.java
package chapter.easymock;
import static org.easymock.EasyMock.createMock;①
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.verify;
import static org.easymock.EasyMock.replay;
import org.easymock.classextension.EasyMock;②
import junit.framework.TestCase;
import org.springframework.samples.jpetstore.dao.AccountDao;
import org.springframework.samples.jpetstore.domain.Account;
import org.springframework.samples.jpetstore.domain.logic.PetStoreFacade;
import org.springframework.samples.jpetstore.domain.logic.PetStoreImpl;
public class PetStoreFacadeTest extends TestCase {
private PetStoreFacade petStoreFacade;
private PetStoreImpl petStoreImpl;
private AccountDao mockAccountDao;
private static final String USERNAME = "Spirit.J";
private static final String PASSWORD = "1111";
public void setUp() {
petStoreFacade = createMock(PetStoreFacade.class);③
petStoreImpl = EasyMock.createMock(PetStoreImpl.class);④
mockAccountDao = createMock(AccountDao.class);
}
}
说明如下:
(1)①处使用了Java5.0的一种新特性:静态引入。它允许对静态方法的直接调用,如③处所示。
(2)②处比较特别,由于下文示例中需要Mock具体类而非接口,所以必须使用EashMock扩展包所提供的EasyMock类,它的使用方法如④处所示。请注意它和接口Mock的区别。
说明:通过EasyMock的createMock()方法,几乎可以模仿任何接口或类。在早期的EasyMock中,createMock()是MockControl类上的静态方法,而现在MockControl类已经废弃了。
二、组件单元测试:模拟业务接口和领域对象的交互
一般情况下,业务接口需要和领域对象进行交互,使用EasyMock可以轻松模拟出类似这样的实现。添加测试方法如下:
public void testFacadeWithDomainObject() throws Exception {
Account account = new Account();
account.setUsername(USERNAME);
account.setPassword(PASSWORD);
petStoreFacade.insertAccount(account);①
expectLastCall().once();②
replay(petStoreFacade); ③
petStoreFacade.insertAccount(account);④
verify(petStoreFacade);⑤
}
说明如下:
(1)Account是最简单的领域对象,它只具有getter/setter方法,创建它以备用。
(2)在①处,记录了对象的预期行为;②处下达了一个期望:希望该行为只被执行一次;③处通过调用静态引入的replay()方法,激活了这个PetStoreFacade模拟对象。
说明:通常要得到一个Mock对象,最简步骤为:为想要模拟的接口创建一个Mock对象(如代码4中的setUp()方法),记录预期的行为(如上述代码的①处),然后将Mock对象切换到replay状态(如上述代码的③处)。
(3)激活后,petStoreFacade就真正成为了PetStoreFacade接口的Mock对象句柄了,如果不在②处对它的预期行为加以修饰,那么在④的后续调用是不合法的。
(4)在调用replay()方法之前的状态,EashMock称之为“record状态”。该状态下,Mock对象不具备行为(即模拟接口的实现),它仅仅记录方法的调用。在调用replay()后,它才以Mock对象预期的行为进行工作,检查预期的方法调用是否真的完成。
(5)经过Mock的创建和激活,④处模拟了一次真实的业务接口调用。在⑤处出现的verify()也并不神秘,它只是用来验证PetStoreFacade接口上的insertAccount()方法是否真的被调用了。
有了以上的测试后,可以发现,PetStoreFacade接口上的insertAccount()方法是不具返回值的。现在来看对返回值的测试,添加如下测试方法:
public void testFacadeReturnValue() throws Exception {
Account expectAccount = new Account();
expectAccount.setUsername(USERNAME);
expectAccount.setPassword(PASSWORD);
petStoreFacade.getAccount(USERNAME);
expectLastCall().andReturn(expectAccount);①
//expect(petStoreFacade.getAccount(USERNAME)).andReturn(expectAccount);②
replay(petStoreFacade);
Account returnAccount = petStoreFacade.getAccount(USERNAME);
assertNotNull("返回值测试", returnAccount);
}
①和②处的写法是等价的,它们用以描述getAccount()方法的预期行为,即返回一个Account对象。
三、组件单元测试:模拟具体类和DAO的交互
在真实的应用场景中,DAO会被注入具体的业务对象,以接受业务对象的持久委托。据此,添加测试方法如下:
public void testFacadeImplWithDao() throws Exception {
Account expectAccount = new Account();
expectAccount.setUsername(USERNAME);
expectAccount.setPassword(PASSWORD);
petStoreImpl.setAccountDao(mockAccountDao);
petStoreImpl.getAccount(USERNAME);
EasyMock.expectLastCall().andReturn(expectAccount);
EasyMock.replay(petStoreImpl);
Account returnAccount = petStoreImpl.getAccount(USERNAME);
assertNotNull("返回值测试", returnAccount);
}
可以看到,petStoreImpl是指向具体实现类(PetStoreImpl)的模拟对象句柄,通过调用它的setAccountDao()方法,注入了模拟的Dao对象。
以下的测试步骤和上文类似,其中省略了verify()方法。最后,运行以上三个测试,效果如图2所示。
图2 运用EasyMock进行组件单元测试效果
本节将结合使用Spring Mock和EasyMock,对宠物店的Web组件SignonController,以及业务核心接口PetStoreFacade进行单元测试。
四、 Web组件单元测试:模拟控制器和业务接口、领域对象的交互
为了正确给出SignonController的单元测试脚本,首先给出SignonController、PetStoreFacade及PetStoreImpl的源码,如代码所示。
代码 SignonController.java
package org.springframework.samples.jpetstore.web.spring;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.support.PagedListHolder;
import org.springframework.samples.jpetstore.domain.Account;
import org.springframework.samples.jpetstore.domain.logic.PetStoreFacade;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class SignonController implements Controller {
private PetStoreFacade petStore;
public void setPetStore(PetStoreFacade petStore) {
this.petStore = petStore;
}
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response)
throws Exception {
String username = request.getParameter("username");
String password = request.getParameter("password");
Account account = this.petStore.getAccount(username, password);①
if (account == null) {
return new ModelAndView("Error", "message",
"Invalid username or password. Signon failed.");
}
else {
UserSession userSession = new UserSession(account);
PagedListHolder myList =
new PagedListHolder(this.petStore.getProductListByCategory(
account.getFavouriteCategoryId()));②
myList.setPageSize(4);
userSession.setMyList(myList);
request.getSession().setAttribute("userSession", userSession);
String forwardAction = request.getParameter("forwardAction");
if (forwardAction != null) {
response.sendRedirect(forwardAction);③
return null;
}
else {
return new ModelAndView("index");
}
}
}
}
代码10 PetStoreFacade.java
package org.springframework.samples.jpetstore.domain.logic;
import java.util.List;
import org.springframework.samples.jpetstore.domain.Account;
...
public interface PetStoreFacade {
...
Account getAccount(String username);
...
List getProductListByCategory(String categoryId);
...
}
代码11 PetStoreImpl.java
package org.springframework.samples.jpetstore.domain.logic;
import java.util.List;
import org.springframework.samples.jpetstore.dao.AccountDao;
...
import org.springframework.samples.jpetstore.dao.ProductDao;
import org.springframework.samples.jpetstore.domain.Account;
...
public class PetStoreImpl implements PetStoreFacade... {
...
private AccountDao accountDao;
...
private ProductDao productDao;
...
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
...
public void setProductDao(ProductDao productDao) {
this.productDao = productDao;
}
...
public Account getAccount(String username, String password) {
return this.accountDao.getAccount(username, password);
}
...
public List getProductListByCategory(String categoryId) {
return this.productDao.getProductListByCategory(categoryId);
}
...
}
代码9的①和②处分别是SignonController和业务接口以及领域对象发生交互的入口。可以发现,业务接口方法调用的返回值应该被作为测试的集中点。
据此添加测试案例,如代码12所示。
代码12 SignonControllerTest.java
package chapter.springandeasymock;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expectLastCall;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
import org.easymock.classextension.EasyMock;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.samples.jpetstore.dao.AccountDao;
import org.springframework.samples.jpetstore.dao.ProductDao;
import org.springframework.samples.jpetstore.domain.Account;
import org.springframework.samples.jpetstore.domain.logic.PetStoreImpl;
import org.springframework.samples.jpetstore.domain.logic.PetStoreFacade;
import org.springframework.samples.jpetstore.web.spring.SignonController;
import org.springframework.web.servlet.ModelAndView;
public class SignonControllerTest extends TestCase {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private AccountDao mockAccountDao;
private ProductDao mockProductDao;
private PetStoreImpl petStore;
private SignonController controller;
private static final String USERNAME = "Spirit.J";
private static final String PASSWORD = "1111";
protected void setUp() throws Exception {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
mockAccountDao = createMock(AccountDao.class);
mockProductDao = createMock(ProductDao.class);
petStore = EasyMock.createMock(PetStoreImpl.class);
controller = new SignonController();
}
/**
* 取得Account为空测试
*/
public void testAccountNull() throws Exception {
mockPetStoreImplAndControllerSetting(true);
ModelAndView modelView =
controller.handleRequest(request, response);
assertEquals("Account为空测试", "Error", modelView.getViewName());
assertEquals("错误消息测试",
"Invalid username or password. Signon failed.",
modelView.getModel().get("message"));
}
/**
* 正确进入Index页面测试
*/
public void testAccountNotNullAndGoIndex() throws Exception {
mockPetStoreImplAndControllerSetting(false);
ModelAndView modelView =
controller.handleRequest(request, response);
assertEquals("正确进入Index页面测试", "index", modelView.getViewName());
}
/**
* 事先录入PetStoreImpl的一些预期行为,并向Controller注射被激活的模拟对象
*/
private void mockPetStoreImplAndControllerSetting(
boolean isAccountNull) {
//预期的领域对象初始化
String favouriteCategoryId = "9999";
Account expectAccount = new Account();
List expectProductList = new ArrayList();
expectAccount.setUsername(USERNAME);
expectAccount.setPassword(PASSWORD);
expectAccount.setFavouriteCategoryId(favouriteCategoryId);
//注射MockDao
((PetStoreImpl)petStore).setAccountDao(mockAccountDao);
((PetStoreImpl)petStore).setProductDao(mockProductDao);
if (isAccountNull) expectAccount = null;
//录入业务接口的预期行为并描述其返回值
petStore.getAccount(USERNAME, PASSWORD);
expectLastCall().andReturn(expectAccount);
petStore.getProductListByCategory(favouriteCategoryId);
expectLastCall().andReturn(expectProductList);
//激活Mock
EasyMock.replay(petStore);
//向controller注射Mock
controller.setPetStore(petStore);
//请求参数初始化
request.setParameter("username", USERNAME);
request.setParameter("password", PASSWORD);
}
}
五、Web组件单元测试:重定向测试
观察代码9 SignonController的③处,为了测试重定向行为,简单地向上述测试案例中,添加如下测试方法:
/**
* 重定向测试
*/
public void testRedirect() throws Exception {
mockPetStoreImplAndControllerSetting(false);
request.setParameter("forwardAction", "RedirectView");
ModelAndView modelView =
controller.handleRequest(request, response);
assertEquals("重定向URL测试", "RedirectView",
response.getRedirectedUrl());
assertNull("重定向返回值测试", modelView);
}
本文章主要围绕Spring和单元测试,讲解了模仿对象的概念以及如何使用Spring Mock和EasyMock来进行单元测试。其中分别涉及了Web组件、业务组件、领域对象以及事务性组件的单元测试技巧。可以看到,模仿对象在单元测试中有着非常大的价值。
下面将使用Spring Mock对宠物店的Web控制器ViewCartController进行单元测试,目的是展示Spring Mock的基本使用技巧。
为了正确搭建测试环境,给出ViewCartController的源代码以及相关的配置文件,如代码1~2所示。
代码1 ViewCartController.java
package org.springframework.samples.jpetstore.web.spring;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.samples.jpetstore.domain.Cart;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.util.WebUtils;
public class ViewCartController implements Controller {
private String successView;
public void setSuccessView(String successView) {
this.successView = successView;
}
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response)
throws Exception {
UserSession userSession =
(UserSession) WebUtils.getSessionAttribute(request, "userSession");
Cart cart =
(Cart) WebUtils.getOrCreateSessionAttribute(request.getSession(),
"sessionCart", Cart.class);
String page = request.getParameter("page");
if (userSession != null) {
if ("next".equals(page)) {
userSession.getMyList().nextPage();
}
else if ("previous".equals(page)) {
userSession.getMyList().previousPage();
}
}
...
return new ModelAndView(this.successView, "cart", cart);
}
}
代码2 petstore-servlet.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
...
<bean name="/shop/viewCart.do"
class="org.springframework.samples.jpetstore.web.spring.ViewCartController">
<property name="successView" value="Cart"/>
</bean>
...
</beans>
petstore-servlet.xml中的代码片断是关于ViewCartController的配置,为了真实的模拟单元测试环境,笔者在ch18/springmock目录下新建了一个petstore-servlet.xml,并复制了以上代码片段作为测试上下文。
说明:虽然本书经常将测试依赖于外部配置,但在真实环境下,使用配置文件作为测试上下文并不总是一个好的选择,因为需要维护日益增多的测试套件。
给出测试案例骨架,如代码3所示。
代码3 ViewCartControllerTest.java
package chapter18.springmock;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import junit.framework.TestCase;
import org.springframework.beans.support.PagedListHolder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.samples.jpetstore.domain.Account;
import org.springframework.samples.jpetstore.domain.Product;
import org.springframework.samples.jpetstore.web.spring.UserSession;
import org.springframework.samples.jpetstore.web.spring.ViewCartController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils;
public class ViewCartControllerTest extends TestCase {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private MockHttpSession session;
private ViewCartController viewCartController;
protected void setUp() throws Exception {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
session = (MockHttpSession)request.getSession();
ApplicationContext context = new ClassPathXmlApplicationContext(
"ch18/springmock/petstore-servlet.xml");
viewCartController =
(ViewCartController)context.getBean("/shop/viewCart.do");
}
}
如代码3使用了MockHttpServletRequest、MockHttpServletResponse、MockHttpSession这三个组件,分别模拟真实Servlet环境下的HttpServletRequest、HttpServletRespons和HttpSession。可以发现,现在可以脱离Servlet环境,对Web组件进行单元测试了。
下文还将逐步对这些对象设定一些模拟值,以测试ViewCartController的一些预期行为。
Web组件的单元测试:视图转发
首先制定的测试案例是:ViewCartController是否依据输入参数进行了正确的视图转发。可以知道,在Spring MVC应用中,正确的视图信息被包含于ModelAndView对象。据此,添加测试方法如下:
public void testViewCartSuccessView() throws Exception {
request.setMethod("POST");
//显式调用Controller组件的handleRequest()方法
ModelAndView modelView =
viewCartController.handleRequest(request, response);
//取petstore-servlet.xml中/shop/viewCart.do的successView属性作为预期值
String expectSuccessView = "Cart";
assertEquals("正确的视图转发测试", expectSuccessView,
modelView.getViewName());
}
Web组件的单元测试:会话状态
接着制定的测试案例是:ViewCartController是否创建了正确的会话状态。观察代码1,可以发现,其中使用了Spring提供的一个Web工具类,它从HttpSession中取出或创建给定的属性值。另外,ViewCartController中还使用了一些领域对象。
注意:本测试方法中,领域对象的作用只是起了一个占位符的作用,简单地构造它们即可。
添加测试方法如下:
/**
* Session(会话)状态测试
*/
public void testSessionWellAndCartPassedByModelKeyAutoCreated()
throws Exception {
//简单地构造领域对象
UserSession userSession = new UserSession(new Account());
//向模拟HttpSession设值
session.setAttribute("userSession", userSession);
//通过WebUtils取得预期UserSession对象
UserSession expectUserSession =
(UserSession)WebUtils.getSessionAttribute(request, "userSession");
ModelAndView modelView =
viewCartController.handleRequest(request, response);
//测试HttpSession是否通过给定属性正确传递了UserSession对象
assertSame("Session状态测试", userSession, expectUserSession);
//测试Cart对象是否被自动创建于Session并且通过modelView的key值正确传递
Cart expectCart = (Cart)modelView.getModel().get("cart");
assertTrue("Session中Cart对象的自动创建测试",expectCart instanceof Cart);
}
Web组件的单元测试:简单逻辑
现在制定业务逻辑的测试案例,说明如下:
(1)是否依据给定的分页导向标识(如next、previous…),触发了正确的处理脚本
(2)是否依据给定的分页标识(如pageSize),进行了正确的分页处理
根据以上两点,添加测试方法如下:
public void testMyListOfUserSessionPagable() {
UserSession userSession = new UserSession( new Account());
userSession.setMyList(mockProductList(9, 4));
session.setAttribute("userSession", userSession);
UserSession expectUserSession =
(UserSession)WebUtils.getSessionAttribute(request, "userSession");
//下一页测试
request.setParameter("page", "next");
String pageTurnedTo = request.getParameter("page");
if (pageTurnedTo.equals("next")) {
//首次翻至下页
expectUserSession.getMyList().nextPage();
//取得当前页的数据列表
List expectPagedList = expectUserSession.getMyList().getPageList();
//从0开始计数并以每页4条记录起算
//以上数据列表中预期的第一条数据ID是4
int productID = 4;
for (Iterator iter = expectPagedList.iterator(); iter.hasNext();) {
Product product = (Product)iter.next();
System.out.println(product);
assertEquals(product.getProductId(), String.valueOf(productID++));
}
}
//前一页测试
request.setParameter("page", "previous");
pageTurnedTo = request.getParameter("page");
int productID = 0;
if (pageTurnedTo.equals("previous")) {
expectUserSession.getMyList().previousPage();
List expectPagedList = expectUserSession.getMyList().getPageList();
for (Iterator iter = expectPagedList.iterator(); iter.hasNext();) {
Product product = (Product)iter.next();
System.out.println(product);
assertEquals(product.getProductId(), String.valueOf(productID++));
}
}
}
/**
* 生成模拟数据
*
* @param dataListSize: 模拟的数据记录条数
* @param pageSize: 每页显示的记录条数
*/
private PagedListHolder mockProductList(int dataListSize, int pageSize) {
List productList = new ArrayList();
for(int i = 0; i < dataListSize; i++) {
Product product = new Product();
product.setProductId(String.valueOf(i));
product.setName("产品-"+i);
productList.add(product);
}
//使用Spring的分页工具类PagedListHolder包装目标数据列表
PagedListHolder pagedList = new PagedListHolder(productList);
pagedList.setPageSize(pageSize);
return pagedList;
}
最后运行以上所有测试,效果如图1:
图1 运用Spring Mock进行Web组件单元测试效果
控制台显示:
产品-4
产品-5
产品-6
产品-7
产品-0
产品-1
产品-2
产品-3
事务性单元测试:使用Spring Mock事务基类搭建测试环境
如上文所述,Spring Mock提供了一些便利的事务测试基类,使用它们可以方便地对业务组件进行事务性的单元测试。接下来将使用其中的AbstractTransactionalDataSourceSpringContextTests进行测试。
说明:AbstractTransactionalDataSourceSpringContextTests是AbstractTransactionalSpringContextTests的子类,它不但具有自动装配测试组件的功能,并且可以直接使用Spring的JdbcTemplate来辅助测试。
为了配置一个最简的事务测试环境,给出如下配置文件和测试案例,如代码4~7所示。
代码4 applicationContext-tx-minimum.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<bean id="baseTransactionProxy"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
abstract="true">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="petStore" parent="baseTransactionProxy">①
<property name="target">
<bean class="org.springframework.samples.jpetstore.domain.logic.PetStoreImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
</property>
</bean>
</beans>
代码5 dataAccessContext-local-minimum.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="sqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation"
value="classpath:ch18/springmock/sql-map-config-minimum.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapAccountDao">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
</beans>
代码6 sql-map-config-minimum.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<sqlMap resource="org/springframework/samples/jpetstore/dao/ibatis/maps/Account.xml"/>
</sqlMapConfig>
代码7 PetStoreFacadeTransactionTest.java
package chapter18.springmock;
import org.springframework.samples.jpetstore.domain.Account;
import org.springframework.samples.jpetstore.domain.logic.PetStoreFacade;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
public class PetStoreFacadeTransactionTest
extends AbstractTransactionalDataSourceSpringContextTests {
private static final String USERNAME = "Spirit.J";
private static final String TEST_SQL =
"SELECT COUNT(*) FROM ACCOUNT WHERE USERID='"+USERNAME+"'";
private PetStoreFacade petStore;
public void setPetStore(PetStoreFacade petStore) {①
this.petStore = petStore;
}
protected String[] getConfigLocations() {②
String path = "classpath:ch18/springmock/";
return new String[]{path+"applicationContext-tx-minimum.xml",
path+"dataAccessContext-local-minimum.xml"};
}
public void testPetStoreTransactionWorking() {
Account account = new Account();
account.setUsername(USERNAME);
account.setPassword("1111");
account.setEmail("fvyaba@126.com");
account.setFirstName("Spirit.");
account.setLastName("J");
account.setAddress1("inlet");
account.setCity("Shanghai");
account.setState("ok");
account.setZip("1111");
account.setCountry("China");
account.setPhone("1111");
account.setLanguagePreference("CN");
petStore.insertAccount(account);
int result =
jdbcTemplate.queryForInt(TEST_SQL);
assertEquals("事务进行中测试", 1, result);
}
protected void onTearDownAfterTransaction() throws Exception {
int result =
jdbcTemplate.queryForInt(TEST_SQL);
assertEquals("事务性单元测试", 0, result);
}
}
说明如下:
(1)代码7②处的getConfigLocations()是必须实现的基类抽象方法,它用以载入测试的上下文配置。
(2)所谓的自动装配,就如代码4和7的①处,只要发现上下文配置中,有与测试组件属性相匹配的Bean id或者name,就会自动进行注射。
(3)代码7可以直接使用jdbcTemplate,这是父类提供的贴心帮助
(4)默认的,AbstractTransactionalSpringContextTests将在这个测试方法结束后,实现自动回滚。所以,在事务结束后,如代码7的 onTearDownAfterTransaction()方法中,预期插入表中的记录数应该是0。
最后,为了正确运行测试,请安装Postgres数据库,启动服务并导入jpetstore-postgres-schema.sql。
相关推荐
EasyMock 简介 EasyMock 是一套用于通过简单的方法对于给定的接口生成 Mock 对象的类库,旨在解决单元测试中的 Mock 对象构建问题。以下是 EasyMock 的详细介绍: 单元测试与 Mock 方法 单元测试是对应用中的某一...
一、Easymock简介 Easymock是一个强大的工具,它提供了对Java接口的模拟支持,让你能在测试环境中模拟对象的行为,返回预设的值。通过模拟,你可以避免在测试时依赖实际的外部系统或服务,提高测试的效率和覆盖率。 ...
### EasyMock简介 EasyMock 是一种静态类型的模拟框架,它通过创建虚拟对象来模拟与被测试类交互的依赖。这样可以避免在测试中实际调用外部服务或数据库,提高测试的效率和可控制性。EasyMock 支持多种模拟行为,...
### EasyMock简介 EasyMock是一个动态模拟框架,允许开发者创建对象的虚拟版本,即mock对象。这些mock对象可以在测试中代替真实的依赖对象,从而隔离被测试代码,专注于单个组件的功能验证。通过这种方式,我们可以...
一、EasyMock简介 EasyMock是一款开源的Java模拟框架,它的核心功能是生成模拟对象,这些对象可以根据开发者的需求返回预设的值,或者对调用的方法进行行为模拟。这种方式使得测试更加灵活,避免了因依赖外部资源...
EasyMock简介(抽象类接口做测试); EasyMock来进行测试; EasyMock如何打桩; EasyMock实践指南; EasyMock使用技巧; EasyMock使用简明手册; EasyMock使用说明; EasyMock使用手记; 用Mock object进行隔离测试;
#### 六、EasyMock简介 EasyMock是一个流行的Java库,用于创建和管理模拟对象。在单元测试中,EasyMock可以帮助我们创建模拟的对象来替代真实的依赖,从而使得测试更加聚焦于被测对象本身的行为。EasyMock的使用可以...
**Junit简介** JUnit是Java编程语言中最流行的单元测试框架之一,它提供了编写和运行测试用例的简单API。Junit支持注解、异常断言、测试套件和参数化测试等功能,使得编写和维护测试代码变得方便快捷。例如,`@Test`...
- **README**:可能提供项目简介、安装和使用说明。 ### Easymock与其它模拟框架对比 Easymock与其他模拟框架如Mockito相比,各有优势。Mockito更注重代码的可读性和简洁性,提供了更丰富的API,而Easymock则更适合...
【个人简介】 姓名未提供,来自四川绵阳,拥有本科学历,专业为软件工程,具备1.5年的工作经验,专长在于JAVA开发。在技术方面,精通JAVA EE技术栈,包括Spring、SpringMvc、MyBatis等,并且熟悉前端技术如EasyUI、...
简介vue-admin-webapp是一个后台管理spa页面,它基于vue和element-ui采用了最新的前端技术栈,实现了登录权限验证,动态路由生成,并使用easy-mock来模拟请求数据,实现了典型的业务模型案例,可以帮助快速搭建后台...
### JUnit4简介 JUnit4是JUnit系列的一个版本,它引入了许多新的特性和改进,使得编写测试更加方便。例如,JUnit4引入了注解来简化测试方法的标记,如`@Test`、`@Before`、`@After`等。 #### JUnit4新特性 - **...
Unitils构建在DBUnit与EasyMock项目之上并与JUnit和TestNG相结合,支持数据库测试,支持利用mock对象进行测试并提供与Spring和Hibernate相集成。Unitils设计成以一种高度可配置和松散偶合的框架来添加这些服务到单元...