真正意义上的spring环境中的单元测试方案spring-test与mokito完美结合
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
- *
- * @author hujf 2011-3-2 下午02:04:08
- */
- @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<String>() {
- @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<Person> personList = new ArrayList<Person>();
- // 初始化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<Person> 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;
- List<MockObjectMap> objs = new ArrayList<MockObjectMap>();
- autowireMockBean(clz, bean, objs);
- List<Object> stubObjs = getStubInstance(clz, bean);
- autowireMockBeanForSpring(stubObjs, objs);
- }
- private void autowireMockBeanForSpring(List<Object> stubObjs, List<MockObjectMap> objs)
- throws IllegalArgumentException,
- IllegalAccessException,
- InvocationTargetException {
- for (Object object : stubObjs) {
- Class claz = object.getClass();
- do {
- for (Method method : claz.getDeclaredMethods()) {
- if (method.getName().startsWith(SETTER)) {
- for (MockObjectMap mockObjectMap : objs) {
- Object obj = method.getGenericParameterTypes()[0];
- if (obj instanceof java.lang.reflect.Type
- && mockObjectMap.getType().getName().equalsIgnoreCase(((Class) obj).getName())) {
- method.invoke(object, mockObjectMap.getObj());
- continue;
- }
- }
- }
- }
- claz = claz.getSuperclass();
- } while (!claz.equals(Object.class));
- }
- }
- private void autowireMockBean(Class clz, Object bean, List<MockObjectMap> objs) throws IllegalArgumentException,
- IllegalAccessException {
- for (Field field : clz.getFields()) {
- Annotation[] mockAnnotations = field.getAnnotations();
- for (Annotation annotation : mockAnnotations) {
- if (annotation instanceof org.mockito.Mock) {
- MockObjectMap mockObjectMap = new MockObjectMap();
- objs.add(mockObjectMap);
- mockObjectMap.setType(field.getType());
- mockObjectMap.setObj(mock(field.getType()));
- field.setAccessible(true);
- field.set(bean, mockObjectMap.getObj());
- continue;
- }
- }
- }
- }
- /**
- * 取得测试类中所有的mock对象的类型
- *
- * @param clazz
- * @return
- */
- private Class[] getMockClass(Class claz) {
- List<Class> clasList = new ArrayList<Class>();
- Field[] fields = claz.getDeclaredFields();
- for (Field field : fields) {
- Annotation[] mockAnnotations = field.getAnnotations();
- for (Annotation annotation : mockAnnotations) {
- if (annotation instanceof org.mockito.Mock) {
- clasList.add(field.getType());
- continue;
- }
- }
- }
- return clasList.toArray(new Class[0]);
- }
- /**
- * 取得测试类中测试桩类
- *
- * @param clazz
- * @return
- * @throws InvocationTargetException
- * @throws IllegalAccessException
- * @throws IllegalArgumentException
- */
- private List<Object> getStubInstance(Class clazz, Object bean) throws IllegalArgumentException,
- IllegalAccessException, InvocationTargetException {
- List<Object> objList = new ArrayList<Object>();
- Field[] fields = clazz.getDeclaredFields();// 测试类中所有的域名
- Method[] methods = clazz.getDeclaredMethods();
- for (Field field : fields) {
- Annotation[] mockAnnotations = field.getAnnotations();
- for (Annotation annotation : mockAnnotations) {
- if (annotation instanceof Autowired) {
- for (Method method : methods) {
- String name = field.getName();
- if (method.getName().startsWith(GETTER) && method.getName().substring(3).equalsIgnoreCase(name)) {
- objList.add(method.invoke(bean, null)); // 将所有的测试桩类放在objList
- }
- }
- }
- }
- }
- return objList;
- }
- private class MockObjectMap {
- private Object obj;
- private Class<?> type;
- public Object getObj() {
- return obj;
- }
- public void setObj(Object obj) {
- this.obj = obj;
- }
- public Class<?> getType() {
- return type;
- }
- public void setType(Class<?> type) {
- this.type = type;
- }
- }
- }
总结:这种方式可以真正的用spring来实现TDD面向接口的测试方案,对依赖的类做到完全屏蔽,对目前测试类和mock类设置期望输出简单实现
真正意义上的spring环境中的单元测试方案spring-test与mokito完美结合的更多相关文章
- 如何在spring环境中做单元测试
在测试类的上方加入以下注解 @RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:spring.xm ...
- Apollo 5 教你怎么把自己的配置放到 Spring 环境中
目录: 前言 处理方案 简单例子 前言 有的时候,你可能需要在 Spring 环境中放入一些配置,但这些配置无法写死在配置文件中,只能运行时放入.那么,这个时候该怎么办呢? Apollo 就是搞配置的 ...
- spring事务中隔离级别和spring的事务传播机制
Transaction 也就是所谓的事务了,通俗理解就是一件事情.从小,父母就教育我们,做事情要有始有终,不能半途而废. 事务也是这样,不能做一般就不做了,要么做完,要 么就不做.也就是说,事务必须是 ...
- Spring Boot中普通类获取Spring容器中的Bean
我们知道如果我们要在一个类使用spring提供的bean对象,我们需要把这个类注入到spring容器中,交给spring容器进行管理,但是在实际当中,我们往往会碰到在一个普通的Java类中,自己动手n ...
- 对象无法注册到Spring容器中,手动从spring容器中拿到我们需要的对象
当前对象没有注册到spring容器中,此时无法new object() 的方式创建对象,否则所有@Autowired 注入的对象都为null; 处理方式: 手动创建一个类@Component注册到S ...
- 非spring环境中配置文件工具
http://commons.apache.org/proper/commons-configuration/ 注意:属性的值使用","分割,会造成读取属性值不正确的问题.建议使用 ...
- DB2数据源在Spring环境中的配置
简单记录一下,以备不时之需. DB2的java驱动包可以在这里下载:http://pan.baidu.com/s/1gOoEJ DB2数据源的配置如下,粗体字部分是需要根据实际情况修改的: <b ...
- Nginx+PHP负载均衡集群环境中Session共享方案 - 运维笔记
在网站使用nginx+php做负载均衡情况下,同一个IP访问同一个页面会被分配到不同的服务器上,如果session不同步的话,就会出现很多问题,比如说最常见的登录状态. 下面罗列几种nginx负载均衡 ...
- spring环境搭建需要的插件-------Spring Tool Suite™ Downloads
下载地址http://spring.io/tools/sts/all 上面的是集成了eclipse的,所以文件比较大,下面的是单独的插件,下载之后打开eclipse,help->installN ...
随机推荐
- Discuz 模板目录
-------------------------------------------------------------------------------------------------- t ...
- How to configure Veritas NetBackup (tm) to write Unified and Legacy log files to a different directory
Problem DOCUMENTATION: How to configure Veritas NetBackup (tm) to write Unified and Legacy log files ...
- day6-3面向对象高阶
面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 是一个模板,模板中包装了多个“函数”供使用(可以讲多函数中公用的变量封装到对象中) 对象,根据模板创建的实例(即:对象),实 ...
- Installing Erlang
Installing Erlang Pre-built binaries for most common platforms. Source code releases from the main E ...
- linux 多个python版本的切换
源码安装新的python版本,我的安装路径: /usr/self/Python3.5.2 修改软链接到你所安装的python版本中: 默认python命令是在/usr/bin/目录下 1 sudo m ...
- activeamq启动失败
启动activeamq,启动时控制台显示: INFO: Using java '/usr/bin/java'INFO: Starting - inspect logfiles specified in ...
- 关于@property()的那些属性及ARC简介
@property()常用的属性有:nonatomic,atomic,assign,retain,strong,weak,copy. 其中atomic和nonatomic用来决定编译器生成的gette ...
- ps 使用说明
ps基本介绍 linux 版本 centos 1511 x64 汇报当前所有进程的快照.report a snapshot of the current processes. 能够显示F, S, U ...
- 使用NuGet Package Project快速制作NuGet包
今天在visual studio gallery发现了一个插件NuGet Package Project,通过它可以在Visual Studio中建立Nuget Package工程,直接生成Nuget ...
- vc++ 如何添加右键弹出菜单
一.创建新工程 二.编辑菜单资源 1.添加菜单 按"Ctrl+R",双击"Menu"图标 2.于菜单编辑器内编辑菜单 四.添加代码(红色部分) void CCM ...