spring环境中单元测试其实不是真正意义上的单元测试,真正意义上的单元测试应该是隔离所有的依赖,对当前业务实现业务逻辑测试;但是目前spring好像还没提供这样的解决方案,只能做依赖于环境的集成测试。比如:要测试A类,但是A类依赖B类和C类,这个时候我们必须保证B和C是完整的且是相对稳定的没太多bug的类.但是实际开发过程中,C类和B类可能是对数据库操作的Dao层或是对外接口层,这个时候我们在测试A类的时候业务B和C的环境或B或C都现在还没开发完成只是一个接口定义完成,这个时候就很难完成我们A类的测试了。
二.解决方案:
为了解决这个问题我们必须在测试的时候忽略B和C类,换句话说就是假象B和C都是可以运行或按我们预期返回结果的运行。我们利用mockito来掩饰我们测试类的所有的依赖。这样我们需要做到两点1.我们可以让B和C可以控制返回预期;2.B和C必须注入到spring中替换我们的测试类的依赖.
/** * 类SayServiceTests.java的实现描述:Mock demo * */ @ContextConfiguration(locations = { "/applicationContext.xml" }) @RunWith(SpringJUnit4ClassRunner.class) @TestExecutionListeners({ org.springframework.test.context.support.DependencyInjectionAsMockitoTestExecutionListener.class }) public class SayServiceTest { @Mock public SayDao sayDao; @Autowired public SayService sayService; // TODO 暂时用实现类 @Test public void testSay() { // 1.设置预期行为 void when(sayDao.sayTo(null)).thenReturn("3"); // 2.验证 assertTrue(sayService.sayTo(null).equals("3")); } public SayDao getSayDao() { return sayDao; } public void setSayDao(SayDao sayDao) { this.sayDao = sayDao; } public SayService getSayService() { return sayService; } public void setSayService(SayService sayService) { this.sayService = sayService; } @Test public void testSayTo() { System.out.println("testSayTo..."); // 1.设置预期行为 when(sayDao.sayTo(any(Person.class))).thenAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); // SayDao mock = (SayDao)invocation.getMock(); //Object mock = invocation.getMock(); if (null != args && args.length > 0) { Person person = (Person) args[0]; return person.getName(); } return null; } }); // 2.验证 Person person = new Person(); person.setId(11); person.setName("Leifeng"); String s = sayService.sayTo(person); System.out.println(s); assertSame("Leifeng", s); } @Test public void testSaySomething() { System.out.println("testSaySomething..."); // 1.设置预期行为 when(sayDao.saySomething(anyString(), any(Person.class), anyString())).thenAnswer(new Answer() { @Override public String answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); if (null != args && args.length > 0) { String hello = (String) args[0]; Person person = (Person) args[1]; String msg = (String) args[2]; return hello + "," + person.getName() + "(" + person.getId() + ")" + ":" + msg; // SayDao dao = new SayDaoImpl(); // return dao.saySomething(hello, person, msg); } return null; } }); // 2.验证 Person person = new Person(); person.setId(12); person.setName("Leifeng"); String s = sayService.saySomething("Welcome", person, "handsome guy!"); System.out.println(s); assertNotNull(s); } @Test public void testQueryPerson() { // 1.预置预期行为 List personList = new ArrayList (); // 初始化List《Person》 for (int i = 0; i < 10; i++) { Person person = new Person(); person.setId(i + 1); person.setName("name" + i); personList.add(person); } when(sayDao.queryPerson(any(Person.class))).thenReturn(personList); // 2.验证 Person query = new Person(); query.setId(13); query.setName("Leifeng"); List list = sayService.queryPerson(query); assertTrue(10 == list.size()); // 重要(根据具体业务设计) assertTrue(3 == list.get(3).getFlag()); } }
DependencyInjectionAsMockitoTestExecutionListener类是在spring-test中的DependencyInjectionTestExecutionListener基础上扩展的一个结合mock的测试监听器。我们在测试的时候可以用注解TestExecutionListeners指定这个监听器来实现单元测试
package org.springframework.test.context.support; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import static org.mockito.Mockito.mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.TestContext; /** * @author yanglin lv */ public class DependencyInjectionAsMockitoTestExecutionListener extends DependencyInjectionTestExecutionListener { private static String SETTER = "set"; private static String GETTER = "get"; @Override protected void injectDependencies(final TestContext testContext) throws Exception { super.injectDependencies(testContext); Object bean = testContext.getTestInstance(); Class[] mockClass = getMockClass(bean.getClass()); Method[] methods = bean.getClass().getDeclaredMethods(); Class clz = bean.getClass(); Object instance = null; Listobjs = new ArrayList (); autowireMockBean(clz, bean, objs); List
总结:这种方式可以真正的用spring来实现TDD面向接口的测试方案,对依赖的类做到完全屏蔽,对目前测试类和mock类设置期望输出简单实现