大多同事都已经养成用junit写单元测试的习惯,但junit在测试spring 时,存在一些不足!
1. Spring 容器多次初始化问题
根据 JUnit 测试用例的调用流程,每执行一个测试方法都会重新创建一个测试用例实例并调用其 setUp() 方法。由于在一般情况下,我们都在 setUp() 方法中初始化 Spring 容器,这意味着测试用例中有多少个测试方法,Spring 容器就会被重复初始化多少次
2. 用硬编码方式手工获取 Bean
在测试用例中,我们需要通过 ApplicationContext.getBean("bean id") 的方法从 Spirng 容器中获取需要测试的目标 Bean,并且还要进行造型操作。
3. 数据库现场容易遭受破坏
测试方法可能会对数据库记录进行更改操作,破坏数据库现场。虽然是针对开发数据库进行测试工作的,但如果数据操作的影响是持久的,将会形成积累效应并影响到测试用例的再次执行。举个例子,假设在某个测试方法中往数据库插入一条 ID 为 1 的 t_user 记录,第一次运行不会有问题,第二次运行时,就会因为主键冲突而导致测试用例执行失败。所以测试用例应该既能够完成测试固件业务功能正确性的检查,又能够容易地在测试完成后恢复现场.
4. 不容易在同一事务下访问数据库以检验业务操作的正确性
当测试固件操作数据库时,为了检测数据操作的正确性,需要通过一种方便途径在测试方法相同的事务环境下访问数据库,以检查测试固件数据操作的执行效果。如果直接使用 JUnit 进行测试,我们很难完成这项操作。
Spring 测试框架是专门为测试基于 Spring 框架应用程序而设计的,它能够让测试用例非常方便地和 Spring 框架结合起来.
下面我们看下如何使用:
1. 加jar包:
如果你使用的是spring2.5.6,请把\spring-framework-2.5.6\dist\modules\spring-test.jar copy到你的web项目的lib下。
把Juint4.4以上的jar包copy进web-info下的lib中。一定要注意是4.4以上的版本才支持。
2. 写测试基类,要从AbstractTransactionalJUnit4SpringContextTests继承
package cn.xxt.jsr250.action; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; @ContextConfiguration(locations={"/applicationContext.xml","/cn/xxt/jsr250/action/beans.xml"}) public class AbstractBaseTransactionalSpringContextTests extends AbstractTransactionalJUnit4SpringContextTests { }
备注:如果要引入多个配置文件,如同代码中所写,如果只有一个
//@ContextConfiguration(locations="/applicationContext.xml")
3. 写具体的测试类,继承测试基类
package cn.xxt.jsr250.action; import javax.annotation.Resource; import org.junit.Test; import cn.xxt.jsr250.domain.ExamUser; import cn.xxt.jsr250.service.ExamUserService; /** * spring 单元测试 * @author zhaoguoli * @version 1.0 2011-10-9下午07:07:33 create */ public class ExamUserSaveActionTest extends AbstractBaseTransactionalSpringContextTests { /** * 备注:此处可以使用Resource 和 Autowired 注解 */ @Resource private ExamUserService examUserService; /** * junit 4.0 以上版本的单元测试 */ @Test public void execute(){ ExamUser examUser = new ExamUser(); examUser.setName("123456"); examUser.setAge(0); examUserService.saveExamUser(examUser); } }
备注: 运行后查看日志
INFO 2011-10-09 19:10:23,906 Loading XML bean definitions from class path resource [applicationContext.xml] [org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:323)] INFO 2011-10-09 19:10:24,312 Loading XML bean definitions from class path resource [cn/xxt/jsr250/action/beans.xml] [org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:323)] INFO 2011-10-09 19:10:24,375 Refreshing org.springframework.context.support.GenericApplicationContext@1820dda: display name [org.springframework.context.support.GenericApplicationContext@1820dda]; startup date [Sun Oct 09 19:10:24 CST 2011]; root of context hierarchy [org.springframework.context.support.AbstractApplicationContext.prepareRefresh(AbstractApplicationContext.java:411)] INFO 2011-10-09 19:10:24,375 Bean factory for application context [org.springframework.context.support.GenericApplicationContext@1820dda]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1431340 [org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:426)] INFO 2011-10-09 19:10:24,484 Loading properties file from class path resource [frameworkconfig/jdbc/MySQL.properties] [org.springframework.core.io.support.PropertiesLoaderSupport.loadProperties(PropertiesLoaderSupport.java:178)] INFO 2011-10-09 19:10:24,578 Loaded JDBC driver: com.mysql.jdbc.Driver [org.springframework.jdbc.datasource.DriverManagerDataSource.setDriverClassName(DriverManagerDataSource.java:155)] INFO 2011-10-09 19:10:25,406 Began transaction (1): transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@16f144c]; rollback [true] [org.springframework.test.context.transaction.TransactionalTestExecutionListener.startNewTransaction(TransactionalTestExecutionListener.java:259)] INFO 2011-10-09 19:10:25,484 Rolled back transaction after test execution for test context [[TestContext@c2b2f6 testClass = ExamUserSaveActionTest, locations = array<String>['classpath:/applicationContext.xml', 'classpath:/cn/xxt/jsr250/action/beans.xml'], testInstance = cn.xxt.jsr250.action.ExamUserSaveActionTest@737371, testMethod = execute@ExamUserSaveActionTest, testException = [null]]] [org.springframework.test.context.transaction.TransactionalTestExecutionListener.endTransaction(TransactionalTestExecutionListener.java:279)] INFO 2011-10-09 19:10:25,500 Closing org.springframework.context.support.GenericApplicationContext@1820dda: display name [org.springframework.context.support.GenericApplicationContext@1820dda]; startup date [Sun Oct 09 19:10:24 CST 2011]; root of context hierarchy [org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:815)]
倒数第二行 执行了rolled back
且记:此处跟 数据库本身的事务隔离级别有关系,如mysql 设置的 innodb,会回滚,如果是myisam 不会回滚。同事可以看下数据库到底有没有增加记录。
总结:其实比较简单,五步区
(1)创建一个扩展自AbstractTransactionalJUnit4SpringContextTests的类,该基类是Spring2.5为方便在JUnit4环境进行事务测试的类,它还提供了一个simpleJdbcTemplate属性让你可以方便地操控数据库表,便于对测试数据进行有效的操作;
(2)用@ContextLocation注解指定你要加载的Spring配置信息所在的位置;(默认的加载文件信息请参阅Spring Documentation);
(3)用@Autowired或@Resource注解注入你的Service接口,@Autowrired是指按类型将Spring Bean注入;而@Resource则按名称将Spring Bean注入。
(4)用@Before准备待测试的数据,如果我们的数据库表结构没有任何数据,则可以在这里预先插入记录,以便进行单元测试,当整个测试完成后,这些数据都不会被保留在数据库中。
(5)在需要进行测试的方法上使用JUnit4.4提供的@Test注解进行标示;