集成测试是在单元测试之上,通常是将一个或多个已进行过单元测试的组件组合起来完成的,即集成测试中一般不会出现Mock对象,都是实实在在的真实实现。
对于单元测试,如前边在进行数据访问层单元测试时,通过Mock HibernateTemplate对象然后将其注入到相应的DAO实现,此时单元测试只测试某层的某个功能是否正确,对其他层如何提供服务采用Mock方式提供。
对于集成测试,如要进行数据访问层集成测试时,需要实实在在的HibernateTemplate对象然后将其注入到相应的DAO实现,此时集成测试将不仅测试该层功能是否正确,还将测试服务提供者提供的服务是否正确执行。
使用Spring的一个好处是能非常简单的进行集成测试,无需依赖web服务器或应用服务器即可完成测试。Spring通过提供一套TestContext框架来简化集成测试,使用TestContext测试框架能获得许多好处,如Spring IoC容器缓存、事务管理、依赖注入、Spring测试支持类等等。
Spring TestContext框架提供了一些通用的集成测试支持,主要提供如下支持:
对于每一个测试用例(测试类)应该只有一个上下文,而不是每个测试方法都创建新的上下文,这样有助于减少启动容器的开销,提供测试效率。可通过如下方式指定要加载的上下文:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration( locations={"classpath:applicationContext-resources-test.xml", "classpath:cn/javass/point/dao/applicationContext-hibernate.xml"}) public class GoodsHibernateDaoIntegrationTest { }
二、 Test Fixture (测试固件)的依赖注入:
Test Fixture可以指运行测试时需要的任何东西,一般通过@Before定义的初始化Fixture方法准备这些资源,而通过@After定义的销毁Fixture方法销毁或还原这些资源。
Test Fixture的依赖注入就是使用Spring IoC容器的注入功能准备和销毁这些资源。可通过如下方式注入Test Fixture:
java代码:
@Autowired private IGoodsDao goodsDao; @Autowired private ApplicationContext ctx;
即可以通过Spring提供的注解实现Bean的依赖注入来完成Test Fixture的依赖注入。
开启测试类的事务管理支持,即使用Spring 容器的事务管理功能,从而可以独立于应用服务器完成事务相关功能的测试。为了使测试中的事务管理起作用需要通过如下方式开启测试类事务的支持:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration( locations={"classpath:applicationContext-resources-test.xml", "classpath:cn/javass/point/dao/applicationContext-hibernate.xml"}) @TransactionConfiguration( transactionManager = "txManager", defaultRollback=true) public class GoodsHibernateDaoIntegrationTest { }
Spring提供如下事务相关注解来支持事务管理:
Spring还通过提供如下注解来简化事务测试:
四、常用注解支持:Spring框架提供如下注解来简化集成测试:
五、TestContext 框架支持类: 提供对测试框架的支持,如Junit、TestNG测试框架,用于集成Spring TestContext和测试框架来简化测试,TestContext框架提供如下支持类:
AbstractJUnit38SpringContextTests : 我们的测试类继承该类后将获取到Test Fixture的依赖注入好处。
AbstractTransactionalJUnit38SpringContextTests : 我们的测试类继承该类后除了能得到Test Fixture的依赖注入好处,还额外获取到事务管理支持。
AbstractJUnit4SpringContextTests : 我们的测试类继承该类后将获取到Test Fixture的依赖注入好处。
AbstractTransactionalJUnit4SpringContextTests : 我们的测试类继承该类后除了能得到Test Fixture的依赖注入好处,还额外获取到事务管理支持。
@RunWith(SpringJUnit4ClassRunner.class) : 使用该注解注解到测试类上表示将集成Spring TestContext和Junit 4.5+测试框架。
@TestExecutionListeners : 该注解用于指定TestContext框架的监听器用于与TestContext框架管理器发布的测试执行事件进行交互,TestContext框架提供如下三个默认的监听器:DependencyInjectionTestExecutionListener、DirtiesContextTestExecutionListener、TransactionalTestExecutionListener分别完成对Test Fixture的依赖注入、@DirtiesContext支持和事务管理支持,即在默认情况下将自动注册这三个监听器,另外还可以使用如下方式指定监听器:
@RunWith(SpringJUnit4ClassRunner.class) @TestExecutionListeners({}) public class GoodsHibernateDaoIntegrationTest { }
如上配置将通过定制的Junit4.5+运行器运行,但不会完成Test Fixture的依赖注入、事务管理等等,如果只需要Test Fixture的依赖注入,可以使用@TestExecutionListeners({DependencyInjectionTestExecutionListener.class})指定。
AbstractTestNGSpringContextTests : 我们的测试类继承该类后将获取到Test Fixture的依赖注入好处。
AbstractTransactionalTestNGSpringContextTests : 我们的测试类继承该类后除了能得到Test Fixture的依赖注入好处,还额外获取到事务管理支持。
到此Spring TestContext测试框架减少完毕了,接下来让我们学习一下如何进行集成测试吧。
对于集成测试环境各种配置应该和开发环境或实际生产环境配置相分离,即集成测试时应该使用单独搭建一套独立的测试环境,不应使用开发环境或实际生产环境的配置,从而保证测试环境、开发、生产环境相分离。
1、拷贝一份Spring资源配置文件applicationContext-resources.xml,并命名为applicationContext-resources-test.xml表示用于集成测试使用,并修改如下内容:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:resources-test.properties</value> </list> </property> </bean>
2 、拷贝一份替换配置元数据的资源文件(resources/resources.properties ),并命名为resources-test.properties 表示用于集成测试使用,并修改为以下内容:
java代码:
db.driver.class=org.hsqldb.jdbcDriver db.url=jdbc:hsqldb:mem:point_shop db.username=sa db.password= #Hibernate属性 hibernate.dialect=org.hibernate.dialect.HSQLDialect hibernate.hbm2ddl.auto=create-drop hibernate.show_sql=false hibernate.format_sql=true
到此我们测试环境修改完毕,在进行集成测试时一定要保证测试环境、开发环境、实际生产环境相分离,即对于不同的环境使用不同的配置文件。
数据访问层集成测试,同单元测试一样目的不仅测试该层定义的接口实现方法的行为是否正确,而且还要测试是否正确与数据库交互,是否发送并执行了正确的SQL,SQL执行成功后是否正确的组装了业务逻辑层需要的数据。
数据访问层集成测试不再通过Mock对象与数据库交互的API来完成测试,而是使用实实在在存在的与数据库交互的对象来完成测试。
接下来让我们学习一下如何进行数据访问层集成测试:
1 、在test 文件夹下创建如下测试类:
java代码:
package cn.javass.point.dao.hibernate; //省略import @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration( locations={"classpath:applicationContext-resources-test.xml", "classpath:cn/javass/point/dao/applicationContext-hibernate.xml"}) @TransactionConfiguration(transactionManager = "txManager", defaultRollback=false) public class GoodsHibernateDaoIntegrationTest { @Autowired private ApplicationContext ctx; @Autowired private IGoodsCodeDao goodsCodeDao; }
2 、测试支持写完后,接下来测试一下分页查询所有已发布的商品是否满足需求:
@Transactional @Rollback @Test public void testListAllPublishedSuccess() { GoodsModel goods = new GoodsModel(); goods.setDeleted(false); goods.setDescription(""); goods.setName("测试商品"); goods.setPublished(true); goodsDao.save(goods); Assert.assertTrue(goodsDao.listAllPublished(1).size() == 1); Assert.assertTrue(goodsDao.listAllPublished(2).size() == 0); }
数据访问层的集成测试也是非常简单,与数据访问层的单元测试类似,也应该只对复杂的数据访问层代码进行测试。
业务逻辑层集成测试,目的同样是测试该层的业务逻辑是否正确,对于数据访问层实现通过Spring IoC容器完成装配,即使用真实的数据访问层实现来获取相应的底层数据。
接下来让我们学习一下如何进行业务逻辑层集成测试:
1 、在test 文件夹下创建如下测试类:
java代码:
@ContextConfiguration( locations={"classpath:applicationContext-resources-test.xml", "classpath:cn/javass/point/dao/applicationContext-hibernate.xml", "classpath:cn/javass/point/service/applicationContext-service.xml"}) @TransactionConfiguration(transactionManager = "txManager", defaultRollback=false) public class GoodsCodeServiceImplIntegrationTest extends AbstractJUnit4SpringContextTests { @Autowired private IGoodsCodeService goodsCodeService; @Autowired private IGoodsService goodsService; }
2 、测试支持写完后,接下来测试一下购买商品Code 码是否满足需求:
2.1 、测试购买失败的场景:
@Transactional @Rollback @ExpectedException(NotCodeException.class) @Test public void testBuyFail() { goodsCodeService.buy("test", 1); }
由于我们数据库中没有相应商品的Code码,因此将抛出NotCodeException异常。
2.2 、测试购买成功的场景:
@Transactional @Rollback @Test public void testBuySuccess() { //1.添加商品 GoodsModel goods = new GoodsModel(); goods.setDeleted(false); goods.setDescription(""); goods.setName("测试商品"); goods.setPublished(true); goodsService.save(goods); //2.添加商品Code码 GoodsCodeModel goodsCode = new GoodsCodeModel(); goodsCode.setGoods(goods); goodsCode.setCode("test"); goodsCodeService.save(goodsCode); //3.测试购买商品Code码 GoodsCodeModel resultGoodsCode = goodsCodeService.buy("test", 1); Assert.assertEquals(goodsCode.getId(), resultGoodsCode.getId()); }
由于我们添加了指定商品的Code码因此购买将成功,如果失败说明业务写错了,应该重写。
业务逻辑层的集成测试也是非常简单,与业务逻辑层的单元测试类似,也应该只对复杂的业务逻辑层代码进行测试。
对于表现层集成测试,同样类似于单元测试,但对于业务逻辑层都将使用真实的实现,而不再是通过Mock对象来测试,这也是集成测试和单元测试的区别。
接下来让我们学习一下如何进行表现层Action集成测试:
1 、准备Struts 提供的junit 插件, 到struts- 2.2.1 .1.zip 中拷贝如下jar 包到类路径:
lib/struts2-junit-plugin-2.2.1.1.jar
2 、测试支持类: Struts2提供StrutsSpringTestCase测试支持类,我们所有的Action测试类都需要继承该类;
3、准备Spring 配置文件: 由于我们的测试类继承StrutsSpringTestCase且将通过覆盖该类的getContextLocations方法来指定Spring配置文件,但由于getContextLocations方法只能返回一个配置文件,因此我们需要新建一个用于导入其他Spring配置文件的配置文件applicationContext-test.xml,具体内容如下:
<import resource="classpath:applicationContext-resources-test.xml"/> <import resource="classpath:cn/javass/point/dao/applicationContext-hibernate.xml"/> <import resource="classpath:cn/javass/point/service/applicationContext-service.xml"/> <import resource="classpath:cn/javass/point/web/pointShop-admin-servlet.xml"/> <import resource="classpath:cn/javass/point/web/pointShop-front-servlet.xml"/>
4 、在test 文件夹下创建如下测试类:
java代码:
package cn.javass.point.web.front; //省略import @RunWith(SpringJUnit4ClassRunner.class) @TestExecutionListeners({}) public class GoodsActionIntegrationTest extends StrutsSpringTestCase { @Override protected String getContextLocations() { return "classpath:applicationContext-test.xml"; } @Before public void setUp() throws Exception { //1 指定Struts2配置文件 //该方式等价于通过web.xml中的<init-param>方式指定参数 Map<String, String> dispatcherInitParams = new HashMap<String, String>(); ReflectionTestUtils.setField(this, "dispatcherInitParams", dispatcherInitParams); //1.1 指定Struts配置文件位置 dispatcherInitParams.put("config", "struts-default.xml,struts-plugin.xml,struts.xml"); super.setUp(); } @After public void tearDown() throws Exception { super.tearDown(); } }
5 、测试支持写完后,接下来测试一下前台购买商品Code 码是否满足需求:
5.1 、测试购买失败的场景:
@Test public void testBuyFail() throws UnsupportedEncodingException, ServletException { //2 前台购买商品失败 //2.1 首先重置hhtp相关对象,并准备准备请求参数 initServletMockObjects(); request.setParameter("goodsId", String.valueOf(Integer.MIN_VALUE)); //2.2 调用前台GoodsAction的buy方法完成购买相应商品的Code码 executeAction("/goods/buy.action"); GoodsAction frontGoodsAction = (GoodsAction) ActionContext.getContext().getActionInvocation().getAction(); //2.3 验证前台GoodsAction的buy方法有错误 Assert.assertTrue(frontGoodsAction.getActionErrors().size() > 0); }
5.2 、测试购买成功的场景:
java代码:
@Test public void testBuySuccess() throws UnsupportedEncodingException, ServletException { //3 后台新增商品 //3.1 准备请求参数 request.setParameter("goods.name", "测试商品"); request.setParameter("goods.description", "测试商品描述"); request.setParameter("goods.originalPoint", "1"); request.setParameter("goods.nowPoint", "2"); request.setParameter("goods.published", "true"); //3.2 调用后台GoodsAction的add方法完成新增 executeAction("/admin/goods/add.action"); //2.3 获取GoodsAction的goods属性 GoodsModel goods = (GoodsModel) findValueAfterExecute("goods"); //4 后台新增商品Code码 //4.1 首先重置hhtp相关对象,并准备准备请求参数 initServletMockObjects(); request.setParameter("goodsId", String.valueOf(goods.getId())); request.setParameter("codes", "a/rb"); //4.2 调用后台GoodsCodeAction的add方法完成新增商品Code码 executeAction("/admin/goodsCode/add.action"); //5 前台购买商品成功 //5.1 首先重置hhtp相关对象,并准备准备请求参数 initServletMockObjects(); request.setParameter("goodsId", String.valueOf(goods.getId())); //5.2 调用前台GoodsAction的buy方法完成购买相应商品的Code码 executeAction("/goods/buy.action"); GoodsAction frontGoodsAction = (GoodsAction) ActionContext.getContext().getActionInvocation().getAction(); //5.3 验证前台GoodsAction的buy方法没有错误 Assert.assertTrue(frontGoodsAction.getActionErrors().size() == 0); }
表现层Action集成测试介绍就到此为止,如何深入StrutsSpringTestCase来完成集成测试已超出本书范围,如果读者对这部分感兴趣可以到Struts2官网学习最新的测试技巧。