Mock测试(模拟测试)
Mock测试介绍
在单元测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便 测试的测试方法,就是 mock 测试
这个虚拟的对象就是 mock 对象。 mock 对象就是真实对象在调试期间的代替品
使用Mock Object进行测试,主要是用来模拟那些在应用中不容易构造(如HttpServletRequest 必须在Servlet容器中才能构造出来)或者比较复杂的对象(如JDBC中的ResultSet对象)从而使 测试顺利进行的工具
目前,在Java阵营中主要的Mock测试工具有JMock,MockCreator,Mockrunner,EasyMock, MockMaker等,在微软的.Net阵营中主要是Nmock,.NetMock等
Mock测试优点
- 避免开发模块之间的耦合
- 轻量、简单、灵活
MockMVC介绍
基于RESTful风格的SpringMVC单元测试,我们可以测试完整的Spring MVC流程,即从URL请求到控制 器处理,再到视图渲染都可以测试
MockMvc
- 对于服务器端的Spring MVC测试支持主入口点
- 通过MockMVCBuilder构造
- MockMVCBuilder由MockMVCBuilders建造者的静态方法去建造
- 核心方法:perform(RequestBuilder rb)--- 执行一个RequestBuilder请求,会自动执行
- SpringMVC的流程并映射到相应的控制器执行处理,该方法的返回值是一个ResultActions
MockMVCBuilder
- MockMvcBuilder是使用构建者模式来构造MockMvc的构造器
- 其主要有两个实现:StandaloneMockMvcBuilder和DefaultMockMvcBuilder,分别对应之前的两种测试方式
- 不过我们可以直接使用静态工厂MockMvcBuilders创建即可,不需要直接使用上面两个实现类
MockMVCBuilders
-
负责创建MockMVCBuilder对象
-
有两种创建方式
-
standaloneSetup(Object... controllers):
通过参数指定一组控制器,这样就不需要从上下文获取了
-
webAppContextSetup(WebApplicationContext wac)
指定WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的MockMvc
-
MockMvcRequestBuilders
- 用来构建Request请求的
- 其主要有两个子类MockHttpServletRequestBuilder和MockMultipartHttpServletRequestBuilder(如文件上传使用),即用来Mock客户端请求需要的所有数据
ResultActions
- andExpect:添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确
- andDo:添加ResultHandler结果处理器,比如调试时打印结果到控制台
- andReturn:最后返回相应的MvcResult;然后进行自定义验证/进行下一步的异步处理
MockMvcResultMatchers
- 用来匹配执行完请求后的结果验证
- 如果匹配失败将抛出相应的异常
- 包含了很多验证API方法
MockMvcResultHandlers
- 结果处理器,表示要对结果做点什么事情
- 比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息
MvcResult
- 单元测试执行结果,可以针对执行结果进行自定义验证逻辑
MockMVC使用
添加依赖
<!-- spring 单元测试组件包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<!-- 单元测试Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- Mock测试使用的json-path依赖 -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.2.0</version>
</dependency>
测试类
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
//@WebAppConfiguration:可以在单元测试的时候,不用启动Servlet容器,就可以获取一个Web应用上下文
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/*.xml")
@WebAppConfiguration
public class TestMockMVC {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
// 初始化一个MockMVC对象的方式有两种:单独设置、web应用上下文设置
// 建议使用Web应用上下文设置
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void test() throws Exception {
// 通过perform去发送一个HTTP请求
// andExpect:通过该方法,判断请求执行是否成功
// andDo :对请求之后的结果进行输出
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/item/showEdit").param("id", "1"))
.andExpect(MockMvcResultMatchers.view().name("item/item-edit"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
System.out.println("================================");
System.out.println(result.getHandler());
}
@Test
public void test2() throws Exception {
// 通过perform去发送一个HTTP请求
// andExpect:通过该方法,判断请求执行是否成功
// andDo :对请求之后的结果进行输出
MvcResult result = mockMvc.perform(get("/item/findItem").param("id", "1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.id").value(1)) .andExpect(jsonPath("$.name").value("台式机123"))
.andDo(print()) .andReturn();
System.out.println("================================");
System.out.println(result.getHandler());
}
}
@WebAppConfiguration
用于声明一个ApplicationContext集成测试加载WebApplicationContext
ControllerAdvice
@ControllerAdvice
介绍
- 该注解顾名思义是一个增强器,是对注解了@Controller注解的类进行增强
- 该注解使用@Component注解,这样的话当我们使用context:component-scan扫描时也能扫描到
- 该注解内部使用@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法会应用到所有 的Controller类中 @RequestMapping注解的方法
使用
@ControllerAdvice
public class MyControllerAdvice {
//应用到所有@RequestMapping注解方法,在其执行之前把返回值放入ModelMap中
@ModelAttribute
public Map<String, Object> ma(){
Map<String, Object> map = new HashMap<>();
map.put("name", "tom");
return map;
}
//应用到所有【带参数】的@RequestMapping注解方法,在其执行之前初始化数据绑定器
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat,true));
System.out.println("...initBinder...");
}
//应用到所有@RequestMapping注解的方法,在其抛出指定异常时执行
@ExceptionHandler(Exception.class)
@ResponseBody
public String handleException(Exception e) {
return e.getMessage();
}
}
@ModelAttribute
就是将数据放入Model中,然后可以在页面中展示该数据
介绍
- 该注解特点:主要作用于ModelMap这个模型对象的,用于在视图中显示数据
- 该注解注意事项:和@ResponseBody注解的使用是互斥的
使用
执行时机:在本类内所有 @RequestMapping 注解方法之前执行
@Controller
public class ModelAttributeController {
// 如果方法有返回值,则直接将该返回值放入`ModelMap`中,`key`可以指定
// @ModelAttribute
@ModelAttribute(value="myUser")
public User populateModel() {
User user=new User();
user.setAccount("ray");
return user;
}
// 如果方法没有返回值,则可以利用它的执行时机这一特点,做一些预处理。
@ModelAttribute
public void populateModel(@RequestParam String abc, Model model) {
model.addAttribute("attributeName", abc);
}
@RequestMapping(value = "/hello")
public String hello() {
// 逻辑视图名称
return "hello";
}
}
@InitBinder
介绍
- 由 @InitBinder 标识的方法,可以通过 PropertyEditor 解决数据类型转换问题,比如 String-- >Date 类型
- 不过 PropertyEditor 只能完成 String-->任意类型 的转换,这一点不如 Converter 灵活, Converter可以完成 任意类型-->任意类型 的转换
- 它可以对 WebDataBinder 对象进行初始化, WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定
注意事项
- @InitBinder 方法不能有返回值,它必须声明为 void
- @InitBinder 方法的参数通常是 WebDataBinder
@ExceptionHandler
介绍
- 这个注解表示 Controller 中任何一个 @RequestMapping 方法发生异常,则会被注解了@ExceptionHandler 的方法拦截到
- 拦截到对应的异常类则执行对应的方法,如果都没有匹配到异常类,则采用近亲匹配的方式
异常处理器
异常处理器( HandlerExceptionResolver ): SpringMVC 在处理请求过程中出现的异常信息交由异 常处理器进行处理,异常处理器可以实现针对一个应用系统中出现的异常进行相应的处理。
异常理解
我们通常说的异常包含编译时异常(已检查异常、预期异常)和运行时异常(未检查异常)
- 继承 RuntimeException 类的异常类,就是运行时异常
- 继承 Exception 类的异常类,没有继承 RuntimeException 类的异常类,就是编译时异常
常见异常举例
- 运行时异常,比如:空指针异常、数组越界异常。对于这样的异常,只能通过程序员丰富的经验来 解决和测试人员不断的严格测试来解决
- 编译时异常,比如:数据库异常、文件读取异常、自定义异常等。对于这样的异常,必须使用 try catch 代码块或者 throws 关键字来处理异常
乱码解决
GET请求乱码
原因分析
- GET 请求参数是通过请求首行中的 URI 发送给Web服务器( Tomcat )的
- Tomcat 服务器会对 URI 进行编码操作(此时使用的是 Tomcat 设置的字符集,默认是 iso8859-1 )
解决方式一
-
修改tomcat配置文件,指定UTF-8编码
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080"protocol="HTTP/1.1" redirectPort="8443"/>
解决方式二
-
对请求参数进行重新编码
String username = request.getParamter("userName"); username = new String(username.getBytes("ISO8859-1"),"utf-8");
解决方式三
-
过滤器+请求装饰器统一解决请求乱码
MyRequestWrapper
MyCharacterEncodingFilter
POST请求乱码
-
在 web.xml 中加入
<filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name>, <url-pattern>/*</url-pattern> </filter-mapping>
响应乱码
使用 @RequestMapping 注解中的 produces 属性,指定响应体中的编码格式
// @RequestMapping注解中的consumes和produces分别是为请求头和响应头设置contentType
@RequestMapping(value = "findUserById", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String findUserById(Integer id) {
// 在使用@ResponseBody注解的前提下
// 如果返回值是String类型,则返回值会由StringHttpMessageConverter进行处理
return "查询失败";
}