zoukankan      html  css  js  c++  java
  • Spring Boot Mock单元测试学习总结

    单元测试的方法有很多种,比如使用Postman、SoapUI等工具测试,当然,这里的测试,主要使用的是基于RESTful风格的SpringMVC的测试,我们可以测试完整的Spring MVC流程,即从URL请求到控制器处理,再到视图渲染都可以测试。下面我主要总结下Spring Boot基于Mock的方式对控制层Controller和服务层Serivce的单元测试。尽管这种的文章已经有很多,我的总结只是作为自己学习的一个承载,总结有误的地方欢迎小伙伴们指正,同时也希望能帮助跟我一样还在学习的小伙伴们。

    在介绍Mock API测试方法之前先介绍下Spring MVC测试框架提供的两种方式:

    独立安装测试集成Web环境测试(此种方式并不会集成真正的web环境,而是通过相应的Mock API进行模拟测试,无须启动服务器)。

    MockMvcBuilder介绍:

    是用来构造MockMvc的构造器,其主要有两个实现:StandaloneMockMvcBuilder和DefaultMockMvcBuilder,StandaloneMockMvcBuilder继承了DefaultMockMvcBuilder。直接使用静态工厂MockMvcBuilders创建即可:

    1. 集成Web环境测试:

        MockMvcBuilders.webAppContextSetup(WebApplicationContext context):指定WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的MockMvc;

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes=MockServletContext.class)
    @WebAppConfiguration
    public class StudentControllerTest {
        
        @Autowired
        private WebApplicationContext wac;
        
        private MockMvc mockMvc;
    
        @Before
        public void setup() {
            this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); // 构造MockMvc
        }
        // ...
    }

    注意:

    (1)@WebAppConfiguration:测试环境使用,用来表示测试环境使用的ApplicationContext将是WebApplicationContext类型的;value指定web应用的根;
    (2)通过@Autowired WebApplicationContext wac:注入web环境的ApplicationContext容器;
    (3)然后通过MockMvcBuilders.webAppContextSetup(wac).build()创建一个MockMvc进行测试;

    2.独立测试方式

      MockMvcBuilders.standaloneSetup(Object... controllers):通过参数指定一组控制器,这样就不需要从上下文获取了;

     (SpringMVC测试需要添加:mockito-core和mockito-all依赖)

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration({"classpath*:/spring-context.xml"})
    public class DemoTest {
    
        @Mock
        private StudentService studentService;
        
        @InjectMocks
        private StudentController studentController;
        
        private MockMvc mockMvc;
        
        @Before
        public void setup() {
            MockitoAnnotations.initMocks(this);
            this.mockMvc = MockMvcBuilders.standaloneSetup(bookController).build();
        }
        
        @Test
        public void testXX() throws Exception {
            //...
        }
    }

    主要是两个步骤:
    (1)首先自己创建相应的控制器,注入相应的依赖
    (2)通过MockMvcBuilders.standaloneSetup模拟一个Mvc测试环境,通过build得到一个MockMvc。

    perform:执行一个RequestBuilder请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理;
    andExpect:添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确;
    andDo:添加ResultHandler结果处理器,比如调试时打印结果到控制台;
    andReturn:最后返回相应的MvcResult;然后进行自定义验证/进行下一步的异步处理;

    MockMvcRequestBuilders主要API:

    MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):根据uri模板和uri变量值得到一个GET请求方式的MockHttpServletRequestBuilder;如get("/user/{id}", 1L);

    MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVariables):同get类似,但是是POST方法;

    MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVariables):同get类似,但是是PUT方法;

    MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVariables) :同get类似,但是是DELETE方法;

    MockHttpServletRequestBuilder options(String urlTemplate, Object... urlVariables):同get类似,但是是OPTIONS方法;

    Mock测试过程:

    1、mockMvc.perform执行一个请求(MockMvcRequestBuilders.get("/user/1")构造一个请求).
    2、设置参数(这一步其实可以设置很多参数,MockMvc提供了丰富的方法)
    3、mockMvc调用perform,调用controller的业务处理逻辑
    4、perform返回ResultActions,返回操作结果,通过ResultActions,提供了统一的验证方式。(

            ResultActions.andExpect添加执行完成后的断言,

           ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情,比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。

           ResultActions.andReturn表示执行完成后返回相应的结果。

    测试说明:

    1. 依赖包导入:pom.xml中仅依赖spring-boot-starter-test,它把会把测试相关的依赖全部引入。

    2. 在测试类上的注解,常用的注解有三个:

     @RunWith(SpringJUnit4ClassRunner.class)//引用Spring-Test测试框架支持
     @SpringApplicationConfiguration(classes = StartApp.class) // StartApp :可以是Spring Boot的启动类,也可以使用MockServletContext来构建一个空的WebApplicationContext,这样我们创建的StudentController就可以在@Before函数中创建并传递到MockMvcBuilders.standaloneSetup()函数中。
     @WebAppConfiguration //用来表示测试环境使用的ApplicationContext将是WebApplicationContext类型的

    3. 测试类的文件结构,保持src/test/java和src/main/java结构一致,即:包+文件夹。测试配置文件任然用src/main/resources的。

    根据如上介绍看下如下案例:

    定义Student实体类:

    public class Student implements Serializable{
    
        /**
         * 
         */
        private static final long serialVersionUID = -9143765513634702342L;
    
        private Long id;
        
        private String name;
        
        private String age;
        
        private String address;
    
        public String getName() {
            return name;
        }
    //省略get/set方法...
    }
    View Code

    定义IStudentService接口类:

    public interface IStudentService {
     
        public List<Student> getStudentList();
        
        public int addStudent(Student student);
        
        public Student getStudent(Long id);
        
        public int updateStudent(Student student);
        
        public int deleteStudent(Long id);
    }
    View Code

    定义IStudentService接口类实现:

    @Service
    public class StudentService implements IStudentService {
    
        /**
         * 定义一个线程安全的集合(集合验证时获取不到存的值,不影响学习mock测试方法)
         */
        static Map<Long, Student> students = Collections.synchronizedMap(new HashMap<Long, Student>());
        
        @Override
        public List<Student> getStudentList() {
            List<Student> stuList = new ArrayList<Student>();
            stuList.addAll(students.values());
            return stuList;
        }
    
        @Override
        public int addStudent(Student student) {
            students.put(student.getId(), student);
            return 0;
        }
    
        @Override
        public Student getStudent(Long id) {
            Student stu = students.get(id);
            return stu;
        }
    
        @Override
        public int updateStudent(Student student) {
            Long id = student.getId();
            Student stu = students.get(id);
            stu.setAddress(student.getAddress());
            stu.setAge(student.getAge());
            stu.setName(student.getName());
            students.put(id, stu);
            return 0;
        }
    
        @Override
        public int deleteStudent(Long id) {
            students.remove(id);
            return 0;
        }
    }
    View Code

    定义Controller类:

    @RestController
    @RequestMapping(value="/stu")
    public class StudentController {
    
        @Autowired
        private IStudentService iStudentService;
        
        @RequestMapping(value="/",method=RequestMethod.GET)
        @ResponseBody
        public List<Student> getStudentList(){
            List<Student> list = iStudentService.getStudentList();
            System.out.println("==="+list.size());
            return list;
        }
        
        @RequestMapping(value="/{id}",method=RequestMethod.GET)
        @ResponseBody
        public Student getStudent(@PathVariable(value="id")Long id){
            return iStudentService.getStudent(id);
        }
        
        @RequestMapping(value = "/", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
        @ResponseBody
        public String addStudent(@ModelAttribute Student student) {
            iStudentService.addStudent(student);
            return "success";
        }
        
        @RequestMapping(value = "/", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
        @ResponseBody
        public String updateStudent(@ModelAttribute Student student) {
            iStudentService.addStudent(student);
            return "success";
        }
        
        @RequestMapping(value="/{id}",method=RequestMethod.DELETE)
        public String deleteStudent(@PathVariable Long id){
            iStudentService.deleteStudent(id);
            return "success";
        }
    }
    View Code

    定义Controller测试类:

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes=MockServletContext.class)
    @WebAppConfiguration
    public class StudentControllerTest {
    
        private static final Log log = LogFactory.getLog(StudentApplicationTest.class);
        
    //      @Autowired
    //      private WebApplicationContext context;
        
        @Mock
        private IStudentService iStudentService;
    
        //@Mock: 创建一个Mock.
        //@InjectMocks: 创建一个实例,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。
        @InjectMocks
        private StudentController studentController;
        
        private MockMvc mvc;
        
        private final String json = "[{"id":1,"name":"xiao","age":27,"address":"湖北"}]";
        
        @Before
        public void setUp(){
            MockitoAnnotations.initMocks(this);
            mvc = MockMvcBuilders.standaloneSetup(studentController).build();////设置要mock的Controller类
            //mvc = MockMvcBuilders.webAppContextSetup(context).build();//建议使用这种
        }
        
        @Test
        public void testStudentController() throws Exception {
            log.info("开始测试=================");
            log.info("测试新增学生===================");
            RequestBuilder request = post("/stu/").param("id", "1")
                                     .param("name", "xiao")
                                     .param("age", "27")
                                     .param("address","湖北");
            mvc.perform(request).andExpect(content().string(equalTo("success"))).andDo(print());
            
            log.info("测试获取学生============");
            request = get("/stu/");
            mvc.perform(request)
               .andExpect(status().isOk())
               .andExpect(content().string(equalTo(json)));
        }
    }
    View Code

    定义Service测试类:

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes=Application.class)
    @WebAppConfiguration
    public class StudentServiceTest {
    
        @Autowired
        private IStudentService iStudentSerivce;
        
        @Test
        public void testAddStudent(){
            Student stu = new Student();
            stu.setId(1l);
            stu.setAge("27");
            stu.setName("xiaoming");
            stu.setAddress("China");
            int result = iStudentSerivce.addStudent(stu);
            Assert.assertEquals(0, result);
        }
        
        @Test
        public void testgetStudent(){
            Student stu = iStudentSerivce.getStudent(1l);
            Assert.assertEquals(stu.getName(), "xiaohua");
        }
    }
    View Code

    附单元测试相关知识:

    Junit单元测试:

    //所有测试方法执行前.执行一次,作用:整体初始化
    @BeforeClass
    
    //所有测试方法完成后,执行一次,作用:销毁和释放资源
    @AfterClass
    
    //每个测试方法前执行,作用:初始化方法
    @Before
    
    //每个测试方法后执行,作用:还原现场
    @After
    
    // 测试方法超过1000毫秒,记为超时,测试失败
    @Test(timeout = 1000)
    
    // 测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败
    @Test(expected = Exception.class)
    
    // 执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类
    @Ignore(“not ready yet”)
    @Test
    
    @RunWith
    在JUnit中有很多个Runner,他们负责调用你的测试代码,每一个Runner都有各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。
    
    如果我们只是简单的做普通Java测试,不涉及spring Web项目,你可以省略@RunWith注解,这样系统会自动使用默认Runner来运行你的代码。

    Assert断言方法介绍:

       1、assertEquals
    
      函数原型1:assertEquals([String message],expected,actual)
    
    参数说明:
    
    message是个可选的消息,假如提供,将会在发生错误时报告这个消息。
    
      expected是期望值,通常都是用户指定的内容。
    
    actual是被测试的代码返回的实际值。
    
    例:assertEquals("equals","1","1");
    
      函数原型2:assertEquals([String message],expected,actual,tolerance)
    
    参数说明:
    
    message是个可选的消息,假如提供,将会在发生错误时报告这个消息。
    
      expected是期望值,通常都是用户指定的内容。
    
      actual是被测试的代码返回的实际值。
    
      tolerance是误差参数,参加比较的两个浮点数在这个误差之内则会被认为是
    
      相等的。
    
      例:assertEquals ("yes",5.8,11.0/2.0,0.5);
    
      2、assertTrue
    
       函数原型:assertTrue ([String message],Boolean condition)
    
       参数说明:
    
    message是个可选的消息,假如提供,将会在发生错误时报告这个消息。
    
           condition是待验证的布尔型值。
    
       该断言用来验证给定的布尔型值是否为真,假如结果为假,则验证失败。当然,更有验证为假的测试条件:
    
              函数原型:assertFalse([String message],Boolean condition)
    
              该断言用来验证给定的布尔型值是否为假,假如结果为真,则验证失败。
    
           例: assertTrue("true",1==1);
    
                  assertFalse("false",2==1);
    
      3、assertNull
    
      函数原型:assertNull([String message],Object object)
    
    参数说明:
    
    message是个可选的消息,假如提供,将会在发生错误时报告这个消息。
    
      object是待验证的对象。
    
      该断言用来验证给定的对象是否为null,假如不为null,则验证失败。相应地,还存在能够验证非null的断言:
    
      函数原型:assertNotNull([String message],Object object)
    
    该断言用来验证给定的对象是否为非null,假如为null,则验证失败。
    
    例:assertNull("null",null);
    
           assertNotNull("not null",new String());
    
      4、assertSame
    
      函数原型:assertSame ([String message], expected,actual)
    
    参数说明:
    
    message是个可选的消息,假如提供,将会在发生错误时报告这个消息。
    
      expected是期望值。
    
      actual是被测试的代码返回的实际值。
    
      该断言用来验证expected参数和actual参数所引用的是否是同一个对象,假如不是,则验证失败。相应地,也存在验证不是同一个对象的断言:
    
      函数原型:assertNotSame ([String message], expected,actual)
    
    该断言用来验证expected参数和actual参数所引用的是否是不同对象,假如所引用的对象相同,则验证失败。
    
    例:assertSame("same",2,4-2);
    
            assertNotSame("not same",2,4-3);
    
      5、Fail
    
      函数原型:Fail([String message])
    
    参数说明:
    
    message是个可选的消息,假如提供,将会在发生错误时报告这个消息。
    
      该断言会使测试立即失败,通常用在测试不能达到的分支上(如异常)。
    
    :新版的Junit中,assertEquals 方法已经被废弃,它建议我们使用assertArrayEquals,旨在让我们测试一个方法的时候多传几种参数进行多种可能性测试。
  • 相关阅读:
    Asp.net WebAPI Ioc
    Asp.net WebAPi gzip压缩和json格式化
    Asp.net WebApi版本控制
    Asp.net WebAPi Restful 的实现和跨域
    Asp.net WebAPI Request参数验证-请不要重复造轮子
    asp.net mvc源码分析-ModelValidatorProviders 客户端的验证
    MultipleRegularExpressionAttribute MVC中扩展自定义验证规则
    Asp.net5 Session Set扩展
    knockout 多值绑定
    ajax file upload 修改
  • 原文地址:https://www.cnblogs.com/guanzhyan/p/8324605.html
Copyright © 2011-2022 走看看