[spring源码学习]单元测试演化
1、使用main方法
最早的测试方法一般是在类中增加main方法,然后在main方法中增加对每个方法的测试代码,如果要测其中一个,就屏蔽掉其他的测试代码,执行后,根据log的打印来判断测试是否成功
2、使用junit
junit的出现,使得针对每个方法的单独测试成为可能,在junit中一般使用4.0后,基于注解,在@befor中初始化数据,在@after中恢复测试现场,然后@test注解标注每个测试案例,在测试案例中使用assert断言来判断测试是否成功,使用junit的好处是:
1)、可以针对每个测试案例编写测试代码
2)、可以仅运行指定测试案例或者批量运行所有测试案例
3)、可以使用断言判断测试结果
4)、必要时候可以提供测试报告
但同时,在使用junit之后,仍然有许多问题需要解决
1)对于百分之50以上的应用都是基于spring进行构建,在初始化@befor中我们需要加载spring的bean,意味着多个方法同时测试时候,spring容器被多次初始化
2)需要硬编码手工获取bean
3)数据库现场会被破坏,如果在测试方法中执行了数据库改写语句,我们不得不在随后的代码中还原现场,否则数据会被破坏
4)不方便对数据库操作结果进行验证
3、使用spring-test框架
使用junit之后残留的问题,如果我们整个框架使用spring,可以使用spring提供的测试框架spring-test进行解决,以下是一个spring-test的基本案例
@RunWith(SpringJUnit4ClassRunner.class) //指定测试用例的运行器 这里是指定了Junit4 @ContextConfiguration("classpath:applicationContext.xml") @TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true) @Transactional public class TestUserDao { @Autowired private BaseDao<User, Long> userDao = null; @Test @Rollback(true)//上面已经设置defaultRollback=true。这里其实可以不用写了 public void testModifyUser() { User user = userDao.findById(2L); System.out.println(user.getId()); user.setDisplayName("系统管理员4"); userDao.saveOrUpdate(user); Assert.assertEquals(userDao.findById(2L).getDisplayName(), "系统管理员4"); } }
我们可以看到一些spring-test的基本操作:
1)使用RunWith指定使用spring-test来进行单元测试
2)ContextConfiguration来确定从配置文件或者java文件读取bean,如果是配置文件:@ContextConfiguration("classpath:applicationContext.xml"),如果是从java类开始:@ContextConfiguration(classes = BaseSpringTest.class)
3)对于从java类开始,我们需要从包扫描,获取所有注解,需要配置@ComponentScan("包名")
4)在类上TransactionConfiguration配置测试的默认配置,如是否需要使用事物,回滚等
5)基本类的注解与之前spirng的开发一致
6)对于单个案例的测试和回滚,默认使用@Rollback和@Transactional进行标记
4、使用mock框架
在测试过程中,不可避免的遇到比如我们要测试一个serivce,但是测试过程中会调用到dao层,而dao层需要访问数据库,或者需要引用到网络访问层,所有测试会影响到数据库或者依赖于外部网络环境,所以在测试过程中要求,对系统环境要求很高,这个时候,我们可以使用Mockito框架,进行模拟dao层或者网络访问层进行测试,如下实例:
public class SimpleTest { @Test public void simpleTest(){ //创建mock对象,参数可以是类,也可以是接口 List<String> list = mock(List.class); //设置方法的预期返回值 when(list.get(0)).thenReturn("helloworld"); String result = list.get(0); //验证方法调用(是否调用了get(0)) verify(list).get(0); //junit测试 Assert.assertEquals("helloworld", result); } }
我们使用mock方法模拟了一个list,并将他的get(0)方法设定返回为"helloworld",如果有调用此方法时候,就会返回指定的调用值
spring-test与mock结合的测试案例写法
public class MockSpringTest extends BaseSpringTest { @Autowired private OrderBefore orderBefore; @InjectMocks private OrderCreate orderCreate = mock(OrderCreate.class); @Mock private OrderHelper orderHelper; @Autowired private OrderStart orderStart; @Before public void initMocks() throws Exception { MockitoAnnotations.initMocks(this); //对注解中的mock进行初始化 ReflectionTestUtils.setField(AopTargetUtils.getTarget(orderStart), "orderCreate", orderCreate); //AopTargetUtils为自定义的类,将属性注入到bean的方法内 doReturn(11).when(orderCreate).getAmt(); //定义方法默认返回 doReturn("success").when(orderHelper).resolve(); //定义方法默认返回 doCallRealMethod().when(orderCreate).create(); //定义方法调用真实方法 } @Test public void create() { System.out.println("start mock..."); //orderStart中注入了orderStart,orderStart中注入了orderCreate, //如果按照正常写法,会调用orderCreate的getAmt方法,但是我们之前使用了setFiled将方法将orderStart的属性替换为了mock类,所以可以看到执行结果为11 orderBefore.before(); } }