zoukankan      html  css  js  c++  java
  • 吴裕雄--天生自然--SPRING BOOT--Spring Boot的单元测试

    Spring Boot为测试提供了一个名为spring-boot-starter-test的Starter。我们使用STS创建Spring Boot应用时,将自动添加spring-boot-starter-test依赖。这样在测试时,就没有必要再添加额外的JAR包。spring-boot-starter-test主要提供了以下测试库:
    1.Junit:标准的单元测试Java应用程序。
    2.Spring Test&Spring Boot Test:针对Spring Boot应用程序的单元测试。
    3.Mockito:Java mocking框架,用于模拟任何Spring管理的Bean,比如在单元测试中模拟一个第三方系统Service接口返回的数据,而不去真正调用第三方系统。
    4.AssertJ:一个流畅的assertion库,同时也提供了更多的期望值与测试返回值的比较方式。
    5.JSONassert:对JSON对象或JSON字符串断言的库。
    6.JsonPath:提供类似Xpath(一门在XML文档中查找信息的语言)那样的符号来获取JSON数据片段。
    Spring Boot单元测试程序模板
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class GoodsServiceTest {
        //注入要测试的service
        @Autowired
        private GoodsService goodsService;
        @Test
        public void testGoodsService() {
            //调用GoodsService的方法进行测试
        }
    }
    
    @RunWith注解是JUnit标准的一个注解,目的是用来告诉JUnit框架不要使用内置的方式进行单元测试,而应使用@RunWith指明的类来进行单元测试,所有的Spring单元测试总是使用SpringRunner.class。
    @SpringBootTest用于Spring Boot应用测试,它默认根据包名逐级往上找,一直找到Spring Boot主程序(包含@SpringBootApplication注解的类),并在单元测试时启动该主程序来创建Spring上下文环境。
    测试Service
    
    单元测试Service代码与通过Controller调用Service代码相比,需要特别考虑该Service是否依赖其他还未开发完毕的Service(第三方接口)。如果依赖其他还未开发完毕的Service,我们需要使用Mockito来模拟未完成的Service。
    
        假设,在UserService中依赖CreditService(第三方接口)的getCredit方法获得用户积分。
        那么,我们如何测试UserService呢?问题是单元测试不能实际调用CreditService(因为CreditService是第三方系统),因此,我们在单元测试类需要使用Mockito的注解@MockBean自动注入Spring管理的Service,用来提供模拟实现,在Spring上下文中,CreditService实现已经被模拟实现代替了。
    测试Controller
    
        在Spring Boot应用中,可以单独测试Controller代码,用来验证与Controller相关的URL路径映射、文件上传、参数绑定、参数校验等特性。可以通过@WebMvcTest注解来完成Controller单元测试,当然也可以通过@SpringBootTest测试Controller。
        需要注意的是,我们在使用@WebMvcTest注解测试Controller时,带有@Service以及别的注解组件类不会自动被扫描注册为Spring容器管理的Bean,而@SpringBootTest注解告诉Spring Boot去寻找一个主配置类(一个带@SpringBootApplication的类),并使用它来启动Spring应用程序上下文,注入所有Bean。另外,还需要注意的是,MockMvc用来在Servlet容器内对Controller进行单元测试,并未真正发起了HTTP请求调用Controller。
        @WebMvcTest用于从服务器端对Controller层进行统一测试;如果需要从客户端与应用程序交互时,应该使用@SpringBootTest做集成测试。
    模拟Controller请求
    
    MockMvc的核心方法是:
    public ResultActions perform(RequestBuilder requestBuilder)
    RequestBuilder类可以通过调用MockMvcRequestBuilders的get、post、multipart等方法来模拟Controller请求,常用示例如下:
    模拟一个get请求:
    mvc.peform(get("/getCredit/{id}", uid));
    模拟一个post请求:
    mvc.peform(post("/getCredit/{id}", uid));
    模拟文件上传:
    mvc.peform(multipart("/upload").file("file", "文件内容".getBytes("UTF-8")));
    模拟请求参数:
    //模拟提交errorMessage参数
    mvc.peform(get("/getCredit/{id}/{uname}", uid, uname).param("errorMessage", "用户名或密码错误"));
    //模拟提交check
    mvc.peform(get("/getCredit/{id}/{uname}", uid, uname).param("job", "收银员", "IT" ));
    比较Controller请求返回的结果
    
        我们知道,MockMvc的perform方法返回ResultActions实例,这个实例代表了请求Controller返回的结果。它提供了一系列andExpect方法来对请求Controller返回的结果进行比较。
    
    mvc.peform(get("/getOneUser/10"))
        .andExpect(status().isOk())  //期望请求成功,即状态码为200
        //期望返回内容是application/json
        .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 
        //使用JsonPath比较返回的JSON内容
        .andExpect(jsonPath("$.name").value("chenheng")); //检查返回内容
        1.比较返回的视图
        mvc.peform(get("/getOneUser/10"))
            .andExpect(view().name("/userDetail"));
        2.比较模型
        mvc.peform(post("/addOneUser"))
            .andExpect(status().isOk())
            .andExpect(model().size(1))
            .andExpect(model().attributeExists("oneUser"))
            .andExpect(model().attribute("oneUser", "chenheng"))
        3.比较转发或重定向
        mvc.peform(post("/addOneUser"))
            .andExpect(forwardedUrl("/user/selectAll")); //或者 redirectedUrl("/user/selectAll")
        4.比较返回的内容
        andExpect(content().string("测试很好玩")); //比较返回的字符串
        andExpect(content().xml(xmlContent)); //返回内容是XML,并且与xmlContent(变量)一样
        andExpect(content().json(jsonContent)); //返回内容是JSON,并且与jsonContent(变量)一样
    使用@WebMvcTest和@SpringBootTest两种方式测试某一个控制器方法。
    1.创建基于Spring Data JPA的Web应用ch9_2
    2.修改pom.xml文件,引入MySQL依赖
    3.配置数据库连接等基本属性
    4.创建持久化实体类
    5.创建数据访问层
    6.创建控制器层
    7.创建测试用例(@WebMvcTest和@SpringBootTest)
    8.运行
    <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.test</groupId>
        <artifactId>SpringBootTest</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.0.RELEASE</version>
            <relativePath /> <!-- lookup parent from repository -->
        </parent>
    
        <properties>
            <!-- 声明项目配置依赖编码格式为 utf-8 -->
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <fastjson.version>1.2.24</fastjson.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
            </dependency>
    
            <!-- 添加MySQL依赖 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
                <version>8.0.13</version><!--$NO-MVN-MAN-VER$ -->
            </dependency>
    
            <!-- MyBatis-Spring,Spring Boot应用整合MyBatis框架的核心依赖配置 -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.0</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    server.servlet.context-path=/ch9_2
    spring.datasource.url=jdbc:mysql://localhost:3306/springbootjpa?serverTimezone=UTC&autoReconnect=true
    spring.datasource.username=root
    spring.datasource.password=admin
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.jpa.database=MYSQL
    spring.jpa.show-sql=true
    spring.jpa.hibernate.ddl-auto=update
    spring.jackson.serialization.indent-output=true
    package com.ch.ch9_2.entity;
    
    import java.io.Serializable;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    
    @Entity
    @Table(name = "student_table")
    /**
     * 解决No serializer found for class
     * org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor异常
     */
    @JsonIgnoreProperties(value = { "hibernateLazyInitializer" })
    public class Student implements Serializable{
        private static final long serialVersionUID = 1L;
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;// 主键
        private String sno;
        private String sname;
        private String ssex;
    
        public Student() {
            super();
        }
    
        public Student(int id, String sno, String sname, String ssex) {
            super();
            this.id = id;
            this.sno = sno;
            this.sname = sname;
            this.ssex = ssex;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getSno() {
            return sno;
        }
    
        public void setSno(String sno) {
            this.sno = sno;
        }
    
        public String getSname() {
            return sname;
        }
    
        public void setSname(String sname) {
            this.sname = sname;
        }
    
        public String getSsex() {
            return ssex;
        }
    
        public void setSsex(String ssex) {
            this.ssex = ssex;
        }
    }
    package com.ch.ch9_2.repository;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    import com.ch.ch9_2.entity.Student;
    
    public interface StudentRepository extends JpaRepository<Student, Integer>{
    
    }
    package com.ch.ch9_2.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import com.ch.ch9_2.entity.Student;
    import com.ch.ch9_2.repository.StudentRepository;
    
    @RestController
    @RequestMapping("/student")
    public class StudentController {
        @Autowired
        private StudentRepository studentRepository;
    
        /**
         * 保存学生信息
         */
        @PostMapping("/save")
        public String save(@RequestBody Student student) {
            studentRepository.save(student);
            return "success";
        }
    
        /**
         * 根据id查询学生信息
         */
        @GetMapping("/getOne/{id}")
        public Student getOne(@PathVariable("id") int id) {
            return studentRepository.getOne(id);
        }
    }
    package com.ch.ch9_2;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class Ch92Application {
        public static void main(String[] args) {
            SpringApplication.run(Ch92Application.class, args);
        }
    }
    创建测试用例
    
    分别使用@WebMvcTest和@SpringBootTest两种方式测试控制器类StudentController中的请求处理方法。
    
    1)创建基于@WebMvcTest的测试用例
    
    使用@WebMvcTest注解测试Controller时,带有@Service以及别的注解组件类不会自动被扫描注册为Spring容器管理的Bean。因此,Controller所依赖的对象必须使用@MockBean来模拟实现。
    
    2)创建基于@SpringBootTest的测试用例
    
    @SpringBootTest注解告诉Spring Boot去寻找一个主配置类(一个带@SpringBootApplication的类),并使用它启动Spring应用程序的上下文,同时注入所有Bean。
    package com.ch.ch9_2;
    
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
    import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.BDDMockito;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    import org.springframework.boot.test.mock.mockito.MockBean;
    import org.springframework.http.MediaType;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.test.web.servlet.MockMvc;
    import com.ch.ch9_2.controller.StudentController;
    import com.ch.ch9_2.entity.Student;
    import com.ch.ch9_2.repository.StudentRepository;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    @RunWith(SpringRunner.class)
    /* 仅仅扫描这个StudentController类,即注入StudentController到Spring容器 */
    @WebMvcTest(StudentController.class)
    public class WebMvcTestStudentController {
        // MockMvc是Spring提供的专用于测试Controller类
        @Autowired
        private MockMvc mvc;
        // 因为在StudentController类依赖StudentRepository,所以需要mock掉
        @MockBean
        private StudentRepository studentRepository;
    
        @Test
        public void saveTest() throws Exception {
            Student stu = new Student(1, "5555", "陈恒", "男");
            ObjectMapper mapper = new ObjectMapper();// 把对象转换成JSON字符串
            mvc.perform(post("/student/save").contentType(MediaType.APPLICATION_JSON_UTF8)// 发送JSON数据格式
                    .accept(MediaType.APPLICATION_JSON_UTF8)// 接收JSON数据格式
                    .content(mapper.writeValueAsString(stu))// 传递JSON字符串参数
            ).andExpect(status().isOk())// 状态响应码为200,如果不是抛出异常,测试不通过。
                    .andDo(print());// 输出结果
        }
    
        @Test
        public void getStudent() throws Exception {
            Student stu = new Student(1, "5555", "陈恒", "男");
            // 模拟StudentRepository,getOne(1)将返回stu对象
            BDDMockito.given(studentRepository.getOne(1)).willReturn(stu);
            mvc.perform(get("/student/getOne/{id}", 1).contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk())// 状态响应码为200,如果不是抛出异常,测试不通过。
                    .andExpect(jsonPath("$.sname").value("陈恒")).andDo(print());// 输出结果
        }
    }
    打开WebMvcTestStudentController测试类,单击鼠标右键,选择“Run As”、“Junit Test”命令

    package com.ch.ch9_2;
    
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
    import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.http.MediaType;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.context.WebApplicationContext;
    import com.ch.ch9_2.entity.Student;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = Ch92Application.class) // 应用的主程序
    public class SpringBootTestStudentController {
        // 注入Spring容器
        @Autowired
        private WebApplicationContext wac;
        // MockMvc模拟实现对Controller的请求
        private MockMvc mvc;
    
        // 在测试前,初始化MockMvc对象
        @Before
        public void initMockMvc() {
            mvc = MockMvcBuilders.webAppContextSetup(wac).build();
        }
    
        @Test
        @Transactional
        public void saveTest() throws Exception {
            Student stu = new Student(1, "5555", "陈恒", "男");
            ObjectMapper mapper = new ObjectMapper();// 把对象转换成JSON字符串
            mvc.perform(post("/student/save").contentType(MediaType.APPLICATION_JSON_UTF8)// 发送JSON数据格式
                    .accept(MediaType.APPLICATION_JSON_UTF8)// 接收JSON数据格式
                    .content(mapper.writeValueAsString(stu))// 传递JSON字符串参数
            ).andExpect(status().isOk())// 状态响应码为200,如果不是抛出异常,测试不通过。
                    .andDo(print());// 输出结果
        }
    
        @Test
        public void getStudent() throws Exception {
            mvc.perform(get("/student/getOne/{id}", 1).contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk())// 状态响应码为200,如果不是抛出异常,测试不通过。
                    .andExpect(jsonPath("$.sname").value("陈恒")).andDo(print());// 输出结果
        }
    }
    package com.ch.ch9_2;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class Ch92ApplicationTests {
        @Test
        public void contextLoads() {
        }
    }
    打开SpringBootTestStudentController测试类,单击鼠标右键,选择“Run As”、“Junit Test”命令

  • 相关阅读:
    吉他谱----see you again
    web----tcp三次握手
    python shell 执行
    python jieba
    NLP gensim 相似度计算
    linux shell expect 自动交互脚本
    docker 安装镜像
    数据库 MySQL 数据导入导出
    爬虫 puppeteer
    Linux ssh相关
  • 原文地址:https://www.cnblogs.com/tszr/p/15379201.html
Copyright © 2011-2022 走看看