SpringBoot——测试

SpringBoot测试

源码在test-springboot

测试无非就是设定预期值与真实值比较,相同则测试通过,不同则测试失败

Ctrl+鼠标左键看源码,再按ctrl:f12查看方法

0、环境

<?xml version="1.0" encoding="UTF-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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
    </parent>

    <groupId>com.lmcode</groupId>
    <artifactId>lmcode</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>17</java.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-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

package com.lmcode;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/** 可以通过args传入一些属性进行临时配置 */
@SpringBootApplication
public class TestSpringbootApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestSpringbootApplication.class, args);
    }
}
test.prop=testValue
server.port=9090

1、当前测试类专用属性

在小范围测试环境(例如当前测试类)有效,比多环境开发中的测试环境影响范围更小;

package com.lmcode;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;


/**
 * 只在当前测试类中有效,加一个临时属性:properties
 * */
@SpringBootTest(properties = {"test.prop=testValue---properties"})
public class demo1_PropertiesTests {

    @Value("${test.prop}")
    private String msg;

    @Test
    public void testProperties(){
        System.out.println(msg);
    }
}
package com.lmcode;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;


/**
 * 只在当前测试类中有效,加一个临时属性:args
 * 优先级币properties高
 * 相对于在idea工具中写参数的好处是配置一直存在,不会因为换平台而消失
 * */
@SpringBootTest(properties = {"test.prop=testValue---properties"},
                args = {"--test.prop=testValue---args"})
public class demo2_ArgsTests {

    @Value("${test.prop}")
    private String msg;

    @Test
    void testArgs(){
        System.out.println(msg);
    }
}

2、 当前测试类专用配置

使用@Import加载当前测试类专用配置

package com.lmcode.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MsgConfig {

    @Bean
    public String msg(){
        return "bean---msg";
    }
}
package com.lmcode;

import com.lmcode.config.MsgConfig;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;

/**
 * 局部的配置导入,仅在当前测试类生效
 * 临时加载bean,服务于测试【不能下载源码(main)中】
 * */
@SpringBootTest
@Import({MsgConfig.class})
public class demo3_ConfigurationTests {

    @Autowired
    private String msg;

    @Test
    void testConfiguration(){
        System.out.println(msg);
    }
}

3、模拟Web环境测试表现层

可以匹配Status、Error message、Headers、Content type、Body、Forwarded URL、Redirected URL、Cookies等都可以匹配

package com.lmcode.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/books")
public class BookController {

    @GetMapping
    public String getById(){
        System.out.println("getById run success");
        return "success";
    }
}
3.1 测试类中启动web环境,发送虚拟请求并匹配响应执行状态
package com.lmcode;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.result.StatusResultMatchers;


/**
 * 属性是枚举类,其中RANDOM_PORT是随机端口
 * 可以通过把请求地址故意写错看预计不通过的控制台输出
 * */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
// 开启虚拟调用
@AutoConfigureMockMvc
public class demo4_WebTest {

    @Test
    void testWeb(@Autowired MockMvc mockMvc) throws Exception {
        // 模拟虚拟的http请求 并 执行
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        mockMvc.perform(builder);
    }

    @Test
    void testStatus(@Autowired MockMvc mockMvc) throws Exception {
        // 模拟虚拟的http请求 并 执行
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        ResultActions action = mockMvc.perform(builder);
        // 设定预期值
        StatusResultMatchers status = MockMvcResultMatchers.status();
        ResultMatcher ok = status.isOk();
        // 对比真实值,添加预计值到本次调用过程进行匹配 并 断言
        action.andExpect(ok);
    }
}
3.2 匹配响应体
package com.lmcode;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.ContentResultMatchers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.result.StatusResultMatchers;


/**
 * 属性是枚举类,其中RANDOM_PORT是随机端口
 * 可以通过把请求地址故意写错看预计不通过的控制台输出
 * */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
// 开启虚拟调用
@AutoConfigureMockMvc
public class demo4_WebTest {

    @Test
    void testBody(@Autowired MockMvc mockMvc) throws Exception {
        // 模拟虚拟的http请求 并 执行
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        ResultActions action = mockMvc.perform(builder);
        // 设定预期值
        ContentResultMatchers content = MockMvcResultMatchers.content();
        ResultMatcher result = content.string("success");
        // 对比真实值,添加预计值到本次调用过程进行匹配 并 断言
        action.andExpect(result);
    }
}
3.3 匹配响应体(json)
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
package com.lmcode.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
    private int id;
    private String name;
    private String type;
    private String description;
}
package com.lmcode.controller;

import com.lmcode.domain.Book;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/books")
public class BookController {

    @GetMapping
    public String getById(){
        System.out.println("getById run success");
        return "success";
    }

    @GetMapping("/getAll")
    public Book getAll(){
        Book book = new Book();
        book.setId(1);
        book.setName("SpringBoot");
        book.setType("technology");
        book.setDescription("I'm No.1");
        System.out.println("getAll run success");
        return book;
    }
}
package com.lmcode;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.ContentResultMatchers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.result.StatusResultMatchers;


/**
 * 属性是枚举类,其中RANDOM_PORT是随机端口
 * 可以通过把请求地址故意写错看预计不通过的控制台输出
 * */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
// 开启虚拟调用
@AutoConfigureMockMvc
public class demo4_WebTest {

    @Test
    void testWeb(@Autowired MockMvc mockMvc) throws Exception {
        // 模拟虚拟的http请求 并 执行
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        mockMvc.perform(builder);
    }

    @Test
    void testStatus(@Autowired MockMvc mockMvc) throws Exception {
        // 模拟虚拟的http请求 并 执行
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        ResultActions action = mockMvc.perform(builder);
        // 设定预期值
        StatusResultMatchers status = MockMvcResultMatchers.status();
        ResultMatcher ok = status.isOk();
        // 对比真实值,添加预计值到本次调用过程进行匹配 并 断言
        action.andExpect(ok);
    }

    @Test
    void testBody(@Autowired MockMvc mockMvc) throws Exception {
        // 模拟虚拟的http请求 并 执行
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/getAll");
        ResultActions action = mockMvc.perform(builder);
        // 设定预期值
        ContentResultMatchers content = MockMvcResultMatchers.content();
        ResultMatcher result = content.json("{\n" +
                "    \"id\": 1,\n" +
                "    \"name\": \"SpringBoot\",\n" +
                "    \"type\": \"technology\",\n" +
                "    \"description\": \"I'm No.1\"\n" +
                "}");
        // 对比真实值,添加预计值到本次调用过程进行匹配 并 断言
        action.andExpect(result);
    }
}
3.4 匹配响应头
package com.lmcode;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.ContentResultMatchers;
import org.springframework.test.web.servlet.result.HeaderResultMatchers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.result.StatusResultMatchers;


/**
 * 属性是枚举类,其中RANDOM_PORT是随机端口
 * 可以通过把请求地址故意写错看预计不通过的控制台输出
 * */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
// 开启虚拟调用
@AutoConfigureMockMvc
public class demo4_WebTest {

    @Test
    void testWeb(@Autowired MockMvc mockMvc) throws Exception {
        // 模拟虚拟的http请求 并 执行
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        mockMvc.perform(builder);
    }

    @Test
    void testStatus(@Autowired MockMvc mockMvc) throws Exception {
        // 模拟虚拟的http请求 并 执行
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        ResultActions action = mockMvc.perform(builder);
        // 设定预期值
        StatusResultMatchers status = MockMvcResultMatchers.status();
        ResultMatcher ok = status.isOk();
        // 对比真实值,添加预计值到本次调用过程进行匹配 并 断言
        action.andExpect(ok);
    }

    @Test
    void testBody(@Autowired MockMvc mockMvc) throws Exception {
        // 模拟虚拟的http请求 并 执行
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/getAll");
        ResultActions action = mockMvc.perform(builder);
        // 设定预期值
        ContentResultMatchers content = MockMvcResultMatchers.content();
        ResultMatcher result = content.json("{\n" +
                "    \"id\": 1,\n" +
                "    \"name\": \"SpringBoot\",\n" +
                "    \"type\": \"technology\",\n" +
                "    \"description\": \"I'm No.1\"\n" +
                "}");
        // 对比真实值,添加预计值到本次调用过程进行匹配 并 断言
        action.andExpect(result);
    }

    @Test
    void testContentType(@Autowired MockMvc mockMvc) throws Exception {
        // 模拟虚拟的http请求 并 执行
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/getAll");
        ResultActions action = mockMvc.perform(builder);
        // 设定预期值
        HeaderResultMatchers header = MockMvcResultMatchers.header();
        ResultMatcher result = header.string("Content-Type","application/json");
        // 对比真实值,添加预计值到本次调用过程进行匹配 并 断言
        action.andExpect(result);
    }
}

3.5 测试多匹配【规范】
package com.lmcode;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.ContentResultMatchers;
import org.springframework.test.web.servlet.result.HeaderResultMatchers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.result.StatusResultMatchers;


@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class demo5_WebTestFinal {

    @Test
    void getAll(@Autowired MockMvc mockMvc) throws Exception {
        // 模拟虚拟的http请求 并 执行
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/getAll");
        // 设定预期值
        ResultActions action = mockMvc.perform(builder);

        // status
        StatusResultMatchers status = MockMvcResultMatchers.status();
        ResultMatcher ok = status.isOk();
        action.andExpect(ok);

        // body
        ContentResultMatchers content = MockMvcResultMatchers.content();
        ResultMatcher result = content.json("{\n" +
                "    \"id\": 90901,\n" +
                "    \"name\": \"SpringBoot\",\n" +
                "    \"type\": \"technology\",\n" +
                "    \"description\": \"I'm No.1\"\n" +
                "}");
        action.andExpect(result);

        // contentType
        HeaderResultMatchers header = MockMvcResultMatchers.header();
        ResultMatcher contentType = header.string("Content-Type","application/json");
        action.andExpect(contentType);
    }

}

4、业务层测试事物回滚

为测试用例添加事物,SpringBoot会对测试用例对应的事物提交操作进行回滚

如皋要提交事物,可以通过@Rollback配置

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.6</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/mini_springmvc?serverTimezone=UTC
      username: root
      password: 1234
mybatis-plus:
  global-config:
    db-config:
      table-prefix: tb_
      id_type: auto
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
package com.lmcode.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
    private Integer id;
    private String name;
    private String type;
    private String description;
}
package com.lmcode.controller;

import com.lmcode.domain.Book;
import com.lmcode.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


@RestController
@RequestMapping("/books")
public class BookController {

    @GetMapping
    public String getById(){
        System.out.println("getById run success");
        return "success";
    }

    @GetMapping("/getAll")
    public Book getAll(){
        Book book = new Book();
        book.setId(90901);
        book.setName("SpringBoot");
        book.setType("technology");
        book.setDescription("I'm No.1");
        System.out.println("getAll run success");
        return book;
    }

    @Autowired
    private BookService bookService;
    @PostMapping("/save")
    public Boolean save(@RequestBody Book book){
        return bookService.save(book);
    }
}
package com.lmcode.service;

import com.lmcode.domain.Book;

public interface BookService {
    boolean save(Book book);
}
package com.lmcode.service.impl;

import com.lmcode.domain.Book;
import com.lmcode.mapper.BookMapper;
import com.lmcode.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookMapper bookMapper;
    @Override
    public boolean save(Book book) {
        return bookMapper.insert(book) > 0;
    }
}
package com.lmcode.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lmcode.domain.Book;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface BookMapper extends BaseMapper<Book> {
}
package com.lmcode;

import com.lmcode.domain.Book;
import com.lmcode.service.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;


/** spring会自动识别,在测试类加事物自动回滚*/
@SpringBootTest
@Transactional
@Rollback(value = false)
//@Rollback(value = true)
public class demo6_ServiceTest {
    @Autowired
    private BookService bookService;

    @Test
    void testSave(){
        Book book = new Book();
        book.setName("傲慢与偏见");
        book.setType("文学类");
        book.setDescription("你要放下你的偏见");
        bookService.save(book);
    }
}

5、测试用例设置随机数据

测试用例数据通常采用随机值进行测试,可以通过配置使用SpringBoot提供的随机数为其赋值

spring:
  datasource:
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/mini_springmvc?serverTimezone=UTC
      username: root
      password: 1234
mybatis-plus:
  global-config:
    db-config:
      table-prefix: tb_
      id_type: auto
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# int,value(String),uuid(String),long(long,可以存时间)
testcase:
  book:
    id: ${random.int(90900,10000)}
    name: ${random.value}
    type: ${random.value}
    description: 描述${random.value}
package com.lmcode.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
// 未配置Spring Boot 配置注解处理器
@ConfigurationProperties(prefix = "testcase.book")
public class Book {
    private Integer id;
    private String name;
    private String type;
    private String description;
}
package com.lmcode;

import com.lmcode.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class demo7_UseTestCase {
    @Autowired
    private Book book;
    @Test
    void testCase(){
        System.out.println(book);
        /* md5加密字符串,32位
         Book(id=-552308655,
         name=ab6939419e9dbcb95d679d185d9595ea,
         type=70d0625bf03e57a3e8b7e312b0ac865c,
         description=描述be4dee034654bd1ecaab738073459fa2)
         */

    }
}
<!--配置Spring Boot 配置注解处理器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
posted @ 2024-04-09 10:21  燕子去了  阅读(12)  评论(0编辑  收藏  举报

Powered by .NET 8.0 on Kubernetes

我会翻山越岭,到每一个我想去的地方

...