[本文出自天外归云的博客园]

概要简述

利用JUnit结合Mockito,再加上spingframework自带的一些方法,就可以组合起来对Spring MVC中的Controller层进行测试。

在设计测试用例前,我们要对待测Controller的代码逻辑进行逐层深入的走查。走查的目的是要明确Controller中主要逻辑分支,以便设计测试用例进行覆盖。一些主要通用的关注点有:

1. 请求request中所包含的参数值(Controller中从请求中获取的参数)

2. Controller中的try块中能够引起异常的方法调用

3. Controller中的if语句涉及的变量值

4. 一些ThreadLocal方法(在实际测试过程中需要对ThreadLocal对象做一些操作来模拟一些状态)

测试规范

创建后端测试分支:一定是以开发分支为基础创建,也为通过修改开发代码来调试测试代码创造方便。

创建测试类:测试类名B与待测类名A的关系为B=ATest

测试类上添加注释:@RunWith(MockitoJUnitRunner.class)

测试类中声明:private MockMvc mockMvc;

测试类中待注入mock的对象声明上添加注释:@InjectMocks

测试类中待mock的对象声明上添加注释:@Mock

测试方法上添加注释:@Test

常用引用(如果IDE不能自动下载对应maven仓库,则需手动修改pom.xml文件添加引用相应的maven仓库):

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.RunWith;
import org.junit.runner.notification.Failure;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

初始化方法标准范例:

@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(声明的controller).build();
}

测试方法标准结构范例:

@Test
public void testSth() throws Exception {
// 自定义填充
// Mock依赖
// 构造请求
// 执行请求与断言
}

对环境ThreadLocal的填充方法举例:

//填充为空
XXThreadLocalContainer.XXX_THREAD_LOCAL.set(null);
//填充为指定类型对象
A a= new A();
a.setXX("test");
XXThreadLocalContainer.XXX_THREAD_LOCAL.set(a);

对无返回service方法依赖注入的mock方法举例:

doThrow(Exception.class).when(someService).doSomeMethod(any(SomeClassA.class), any(SomeClassB.class), anyString(), anyString(), any(SomeEnum.class));

对有返回service方法依赖注入的mock方法举例:

// 构造mock方法返回的对象
A a = new A();
a.setSomePropertyA(someValueA);
a.setSomePropertyB(someValueB);
// 构造mock方法
doReturn(a).when(someService).doSomeMethod(anyString(), anyString());

利用RequestBuilder构造GET请求举例:

RequestBuilder request = MockMvcRequestBuilders.get(someUrl).requestAttr("someAttrNameA", someValueA).requestAttr("someAttrNameB", someValueB).requestAttr("someAttrNameC", someValueC);

执行请求与断言举例:

mockMvc.perform(request).andDo(print()).andExpect(jsonPath("someFieldName").value(String.valueOf(someFieldValue)));

自定义main函数执行测试:

public static void main(String[] args) {
Result result = JUnitCore.runClasses(MpResurrectionControllerTest.class);
for (Failure failure : result.getFailures()) {
System.out.println(String.format("FAILED : %s", failure.toString()));
}
System.out.println(String.format("TEST SUCCESS : %s", result.wasSuccessful()));
}

抽象复用

在实际的测试过程中,把复用的部分提取抽象,生成一个基类为测试类提供继承(MockTestBase.java),其中testAll方法利用反射通过类名动态生成类对象:

package com.xx.xxx;

import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.RunWith;
import org.junit.runner.notification.Failure;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.web.servlet.MockMvc; /**
* @Author: Tylan
* @CreateDate: 2018/7/2 11:07
* @UpdateDate: 2018/7/2 11:07
*/
@RunWith(MockitoJUnitRunner.class)
public abstract class MockTestBase { MockMvc mockMvc; @Rule
public ExpectedException thrown = ExpectedException.none(); @Before
public void setUp() {
MockitoAnnotations.initMocks(this);
} public static void testAll(String className) throws ClassNotFoundException {
Class obj = Class.forName(className);
Result result = JUnitCore.runClasses(obj);
for (Failure failure : result.getFailures()) {
System.out.println(String.format("FAILED : %s", failure.toString()));
}
System.out.println(String.format("TEST SUCCESS : %s", result.wasSuccessful()));
}
}

新的测试类就变成这样,重新写一个main函数调用基类testAll方法,利用反射传入当前运行的类名:

package com.xx.xxx;

import com.xx.xxx.ForTestController;
import com.xx.xxx.SomeService;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; /**
* @Author: Tylan
* @CreateDate: 2018/7/2 11:11
* @UpdateDate: 2018/7/2 11:11
*/
public class NewMpGameOrderControllerTestsTylan extends MockTestBase {
@InjectMocks
private ForTestController someController; @Mock
SomeService someService; @Override
public void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(someController).build();
} @Test
public void insertTestWrongParam() throws Exception {
/*
* 测试data为空
* */
System.out.println("insertTestWrongParam");
// 构造测试数据
String data1 = "";
String data2 = "";
String insertUrl = "/xx/xx/data/insert";
// 执行请求与断言
RequestBuilder request = MockMvcRequestBuilders.get(insertUrl).requestAttr("data1", data1).requestAttr("data2", data2);
mockMvc.perform(request).andDo(print()).andExpect(jsonPath("xxx").value("xx"));
} public static void main(String[] args) throws ClassNotFoundException {
testAll(Thread.currentThread().getStackTrace()[1].getClassName());
}
}

这就生成了一个测试Controller的模板。剩下的工作就是分析开发源码,设计测试用例并对Controller中的所有逻辑分支进行覆盖测试了。

在一个单元测试用例,标准顺序是mock、test、verify三个环节。其中mock举例:

GameOrder order = new GameOrder();
order.setId(xxx);
order.setScore(xxx); doReturn(order).when(gameService).initGameOrder(anyString(), anyString(), anyString(), any(MpEnum.class));

涉及对foreach循环的mock举例:

Iterator<SomeService> mockIter = mock(Iterator.class);
when(mockIter.hasNext()).thenReturn(true, true, true, false);
when(mockIter.next()).thenReturn(someService).thenReturn(someService).thenReturn(someService);
when(serviceList.iterator()).thenReturn(mockIter);
doReturn(SomeEnum.XXXXX).when(someService).getSomeEnum();
doReturn(true).when(someService).isValid(anyMap());

对于test环节,测试Controller就是模拟给Controller发请求,测试Service就是直接调用Service方法。

对于Verify环节,主要是对一些方法是否执行,路径是否走过做一下验证,以及一些值的验证。这里举个例子:

//Verify
verify(someService).initGameOrder(xx, xxx, xxxx, someEnum);
verify(someService).update(any(GameOrder.class));

这里验证了someService对象是否执行了initGameOrder方法和update方法。

感受与总结

想做好单元测试,要对待测试代码逻辑进行充分分析,重点逻辑是在Service层和Controller层的测试,而对于这两层来说,Controller中除了一些基本逻辑基本就是service层的调用,对于service层,除了定义service的interface类就是定义对应implements的接口实现类,interface类只定义方法接口,剩余的由service的impl类实现,service实现类中除了Override实现继承的接口类定义的方法接口就是Autowired声明一些在该实现类中要用到的其他service接口类(非实现类),剩下的就是接口类一层又一层的继承关系,而service层无非是围绕着dao层展开的,最终落在了dao层对数据库或缓存的操作上。所以纵观整个后端结构顺序就是Filter层-Controller层-Service层-Dao层,请求是按这个顺序前进与原路返回的。所以后端测试的重点,最终是落在Controller层逻辑的正确性与sql查询、redis和memcache等缓存查询的正确性上。

单测是否应该由开发人员进行?这个问题的答案和开发是否应该由测试人员完成是一样的,如果让开发搞测试没什么不可以,那么让测试做开发也能更好的保障质量。但是现实的分工中,没有那么多全栈人才,测试人员不懂开发,开发人员不懂测试。单测是应该由测试人员完成的,想要保障产品质量,如果QA都对代码逻辑没有了解的话,单凭瞎子摸象这种经验推测式的方法进行测试,能够发现的问题种类和层次也是有限的。而开发除了紧张的开发周期外,还要修改bug,没有更多的时间来投入到测试中。所以测试人员应该具备能够发现bug和定位问题原因的能力才能够更好的配合开发一起完成高质量的产品软件工程。

JUnit+Mockito结合测试Spring MVC Controller的更多相关文章

  1. 使用MockMvc测试Spring mvc Controller

    概述   对模块进行集成测试时,希望能够通过输入URL对Controller进行测试,如果通过启动服务器,建立http client进行测试,这样会使得测试变得很麻烦,比如,启动速度慢,测试验证不方便 ...

  2. 就是这么简单(续)!使用 RestAssuredMockMvc 测试 Spring MVC Controllers

    就是这么简单(续)!使用 RestAssuredMockMvc 测试 Spring MVC Controllers 转载注明出处:http://www.cnblogs.com/wade-xu/p/43 ...

  3. Spring MVC Controller 单元测试

    简介 Controller层的单元测试可以使得应用的可靠性得到提升,虽然这使得开发的时间有所增加,有得必失,这里我认为得到的比失去的多很多. Sping MVC3.2版本之后的单元测试方法有所变化,随 ...

  4. Spring MVC Controller与jquery ajax请求处理json

    在用 spring mvc 写应用的时候发现jquery传递的[json数组对象]参数后台接收不到,多订单的处理,ajax请求: "}]}]} $.ajax({ url : url, typ ...

  5. 关于Spring MVC Controller 层的单元测试

    关于Spring MVC Controller 层的单元测试 测试准备工作: 1.搭建测试Web环境 2.注入Controller 类 3.编写测试数据 测试数据的文件名一定要与测试类的文件名相同,比 ...

  6. Spring MVC Controller中解析GET方式的中文参数会乱码的问题(tomcat如何解码)

    Spring MVC Controller中解析GET方式的中文参数会乱码的问题 问题描述 在工作上使用突然出现从get获取中文参数乱码(新装机器,tomcat重新下载和配置),查了半天终于找到解决办 ...

  7. 转:【Spring MVC Controller单例陷阱】

    http://lavasoft.blog.51cto.com/62575/1394669/ Spring MVC Controller默认是单例的: 单例的原因有二:1.为了性能.2.不需要多例. 1 ...

  8. Spring MVC Controller单例陷阱

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://lavasoft.blog.51cto.com/62575/1394669 Spr ...

  9. Spring MVC Controller中GET方式传过来的中文参数会乱码的问题

    Spring MVC controller 这样写法通常意味着访问该请求,GET和POST请求都行,可是经常会遇到,如果碰到参数是中文的,post请求可以,get请求过来就是乱码.如果强行对参数进行了 ...

随机推荐

  1. Mac电脑如何设置DHCP

    通过设置MAC电脑的DHCP服务器可以为局域网内的其它设备自动分配IP地址,还能把物理地址和IP地址绑定起来,限制IP地址的获取.   OS X 10.11或以上版本已内置DHCP服务器软件,即boo ...

  2. 化学1(chem1)- 化学合成

    P2784 化学1(chem1)- 化学合成 还是spfa,距离数组初始化为-1,松弛操作改为*就好了,一开始老是超时,后来加了一个visit数组就过了,这个重复造成的效率浪费还是蛮大的,以后都要加. ...

  3. C++雾中风景番外篇3:GDB与Valgrind ,调试代码内存的工具

    写 C++的同学想必有太多和内存打交道的血泪经验了,常常被 C++的内存问题搅的焦头烂额.(写 core 的经验了)有很多同学一见到 core 就两眼一抹黑,不知所措了.笔者 入"坑&quo ...

  4. IdentityServer4-用EF配置Client(一)

    一.背景 IdentityServer4的介绍将不再叙述,百度下可以找到,且官网的快速入门例子也有翻译的版本.这里主要从Client应用场景方面介绍对IdentityServer4的应用. 首先简要介 ...

  5. Python3面向对象——案例-01

    经典的策略模式案例 问题描述 使用"策略"设计模式处理订单折扣的 UML 类图 定义一系列算法,把它们一一封装起来,并且使它们可以相互替换.本模式使得算法可以独立于使用它的客户而变 ...

  6. Oracle的decode、sign、trunc函数

    原文http://knowyouknowme.iteye.com/blog/574974 一.decode 在Oracle/PLSQL中,  decode 具有和 IF-THEN-ELSE 一样的功能 ...

  7. AngualrJS中每次$http请求时的一个遮罩层Directive

    在AngualrJS中使用$http每次向远程API发送请求,等待响应,这中间有些许的等待过程.如何优雅地处理这个等待过程呢? 如果我们在等待过程中弹出一个遮罩层,会是一个比较优雅的做法. 这就涉及到 ...

  8. android:如何通过自定义工程模板让新建的工程都默认支持lambda表达式

    首先参考这篇文章:自定义Android Studio工程模板,了解如何自定义模板   然后结合我们上一篇文章 android: 在android studio中使用retrolambda的步骤的要点, ...

  9. bash參考手冊之五(shell变量)续三

    LINENO 当前在运行的脚本或者shell函数的行号. LINES 命令select用来确定打印选择列表的列宽.收到SIGWINCH后,自己主动设置. MACHTYPE 是一个字符串,描写叙述了正在 ...

  10. SimpleCaptcha生成图片验证码内容为乱码

    转自:https://blog.csdn.net/wlwlwlwl015/article/details/51482065 前言 报表中发现有中文乱码和中文字体不整齐(重叠)的情况,首先考虑的就是操作 ...