SpringBoot(3)热部署和测试

热部署

开启热部署

热部署就是在程序改了之后,服务器自己把更新后的程序重新加载一遍,不用重新启动服务器,就是热部署。

手动开启热部署

1.导入开发者工具对应的坐标

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

2.每次修改之后,构建项目,使用快捷点激活热部署
在这里插入图片描述
对应的快捷键: +

重启与重载
一个springboot项目在运行时实际上是分两个过程进行的,根据加载的东西不同,划分成base类加载器与restart类加载器。

base类加载器: 用来加载jar包中的类,jar包中的类和配置文件由于不会发生变化,因此不管加载多少次,加载的内容不会发生变化
restart类加载器: 用来加载开发者自己开发的类、配置文件、页面等信息,这一类文件受开发者影响

当springboot项目启动时,base类加载器执行,加载jar包中的信息后,restart类加载器执行,加载开发者制作的内容。当执行构建项目后,由于jar中的信息不会变化,因此base类加载器无需再次执行,所以仅仅运行restart类加载即可,也就是将开发者自己制作的内容重新加载就行了,这就完成了一次热部署的过程,也可以说热部署的过程实际上是重新加载restart类加载器中的信息。

重启(Restart): 自定义开发代码,包含类,页面,配置文件等,加载位置reatart类加载器
重载(Reload): jar包,加载位置base类加载器

自动启动热部署

自动热部署其实就是设计一个开关,打开这个开关后,IDE工具就可以自动热部署。因此这个操作和IDE工具有关,以下以idea为例设置idea中启动热部署。

1.设置自动构建项目
打开【File】,选择【settings…】,在面板左侧的菜单中找到【Compile】选项,然后勾选【Build project automatically】,意思是自动构建项目。
在这里插入图片描述
2.允许在程序运行时进行自动构建,使用快捷键【Ctrl】+【Alt】+【Shit】+【/】打开维护面板,选择第1项【Registry…】
在这里插入图片描述
在选项中搜索comple,然后勾选对应项即可
在这里插入图片描述
3.开启idea的热部署策略
在这里插入图片描述
4.关闭浏览器缓存
在这里插入图片描述
这样程序在运行的时候就可以进行自动构建了,实现了热部署的效果。

注意: 如果每敲一个字母,服务器就重新构建一次,未免有点太频繁了,所以设置当idea工具失去焦点5秒后进行热部署。其实就是从idea工具中切换到其他工具时进行热部署,比如改完程序需要到浏览器上去调试,这个时候idea就自动进行热部署操作。

热部署范围配置
通过修改项目中的文件,可以发现并不是所有的文件修改都会激活热部署的,原因在于在开发者工具中有一组配置,当满足了配置中的条件后,才会启动热部署,配置中默认不参与热部署的目录信息如下:

  • /META-INF/maven
  • /META-INF/resources
  • /resources
  • /static
  • /public
  • /templates

    以上目录中的文件如果发生变化,是不参与热部署的。如果想修改配置,可以通过application.yml文件进行设定哪些文件不参与热部署操作
spring:
  devtools:
    restart:
      # 设置不参与热部署的文件或文件夹
      exclude: static/**,public/**,config/application.yml

关闭热部署

线上环境运行时是不可能使用热部署功能的,所以需要强制关闭此功能,通过配置可以关闭此功能。

spring:
  devtools:
    restart:
      enabled: false

如果当心配置文件层级过多导致相符覆盖最终引起配置失效,可以提高配置的层级,在更高层级中配置关闭热部署。例如在启动容器前通过系统属性设置关闭热部署功能。

@SpringBootApplication
public class SSMPApplication {
    
    
    public static void main(String[] args) {
    
    
        System.setProperty("spring.devtools.restart.enabled","false");
        SpringApplication.run(SSMPApplication.class);
    }
}

其实上述担心略微有点多余,因为线上环境的维护是不可能出现修改代码的操作的,这么做唯一的作用是降低资源消耗。

配置高级

@EnableConfigurationProperties

入门篇中,是为自定义的bean绑定属性。

使用@ConfigurationProperties注解可以为第三方bean加载属性,格式特殊一点而已。

1.使用@Bean注解定义第三方bean,将方法的返回值作为bean注入spring容器

@Bean
public DruidDataSource datasource(){
    
    
    DruidDataSource ds = new DruidDataSource();
    return ds;
}

2.在yml中定义要绑定的属性,注意datasource此时全小写

datasource:
  driverClassName: com.mysql.jdbc.Driver

3.使用@ConfigurationProperties注解为第三方bean进行属性绑定,注意前缀是全小写的datasource

@Bean
@ConfigurationProperties(prefix = "datasource")
public DruidDataSource datasource(){
    
    
    DruidDataSource ds = new DruidDataSource();
    return ds;
}

4.测试

 public static void main(String[] args) {
    
    
        ConfigurableApplicationContext ctx = SpringApplication.run(Springboot13ConfigurationApplication.class, args);
        DruidDataSource ds = ctx.getBean(DruidDataSource.class);
        System.out.println(ds.getDriverClassName());
    }

在这里插入图片描述
@ConfigurationProperties注解不仅能添加到类上,还可以添加到方法上,添加到类上是为spring容器管理的当前类的对象绑定属性,添加到方法上是为spring容器管理的当前方法的返回值对象绑定属性,其实本质上都一样。

目前定义的bean不是通过类注解定义就是通过@Bean定义,使用@ConfigurationProperties注解可以为bean进行属性绑定,那在一个业务系统中,哪些bean通过注解@ConfigurationProperties去绑定属性了呢?因为这个注解不仅可以写在类上,还可以写在方法上,所以找起来就比较麻烦了。为了解决这个问题,spring给我们提供了一个全新的注解,专门标注使用@ConfigurationProperties注解绑定属性的bean是哪些。这个注解叫做@EnableConfigurationProperties。使用步骤如下:

1.在配置类上开启@EnableConfigurationProperties注解,并标注要使用@ConfigurationProperties注解绑定属性的类。

@SpringBootApplication
@EnableConfigurationProperties(ServerConfig.class)
public class Application {
    
    
	 public static void main(String[] args) {
    
    
         SpringApplication.run(Application.class);
    }
}

2.在对应的类上直接使用@ConfigurationProperties进行属性绑定

//Component
@Data
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
    
    
    private String ipAddress;
    private int port;
    private long timeout;
}

3.yml内容

servers:
  ip-address: 192.168.0.1 
  port: 2345
  timeout: -1

4.测试

@SpringBootApplication
@EnableConfigurationProperties(ServerConfig.class)
public class Springboot13ConfigurationApplication {
    
    

 public static void main(String[] args) {
    
    
        ConfigurableApplicationContext ctx = SpringApplication.run(Springboot13ConfigurationApplication.class, args);
        ServerConfig bean = ctx.getBean(ServerConfig.class);
        System.out.println(bean);
    }

在这里插入图片描述

注意: 当使用@EnableConfigurationProperties注解时,spring会默认将其标注的类定义为bean,因此无需再次声明@Component注解了。

使用@ConfigurationProperties注解时,会出现一个提示信息:
在这里插入图片描述
出现这个提示后只需要添加一个坐标此提醒就消失了:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

总结

  1. 使用@ConfigurationProperties可以为使用@Bean声明的第三方bean绑定属性
  2. 当使用@EnableConfigurationProperties声明进行属性绑定的bean后,无需使用@Component注解再次进行bean声明

宽松绑定

在进行属性绑定时,可能会遇到如下情况,为了进行标准命名,开发者会将属性名严格按照驼峰命名法书写,在yml配置文件中将datasource修改为dataSource,如下:

dataSource:
  driverClassName: com.mysql.jdbc.Driver

此时程序可以正常运行,然后又将代码中的前缀datasource修改为dataSource,如下:

@Bean
@ConfigurationProperties(prefix = "dataSource")
public DruidDataSource datasource(){
    
    
    DruidDataSource ds = new DruidDataSource();
    return ds;
}

此时就发生了编译错误,而且并不是idea工具导致的,运行后依然会出现问题,配置属性名dataSource是无效的:

Configuration property name 'dataSource' is not valid:

    Invalid characters: 'S'
    Bean: datasource
    Reason: Canonical names should be kebab-case ('-' separated), lowercase alpha-numeric characters and must start with a letter
   #推荐使用烤肉串形式: @ConfigurationProperties(prefix = "data-source"),或者小写 @ConfigurationProperties(prefix = "datasource")
Action:
Modify 'dataSource' so that it conforms to the canonical names requirements.

为什么会出现这种问题,就涉及到springboot进行属性绑定时有关属性名称的宽松绑定。

​什么是宽松绑定?实际上是springboot进行编程时人性化设计的一种体现,即配置文件中的命名格式与变量名的命名格式可以进行格式上的最大化兼容。兼容到什么程度呢?几乎主流的命名格式都支持,例如:

在ServerConfig中的ipAddress属性名:

@Component
@Data
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
    
    
    private String ipAddress;
}

可以与下面的配置属性名规则全兼容

servers:
  ipaddress: 192.168.0.2 
  ipAddress: 192.168.0.2       # 驼峰模式
  ip_address: 192.168.0.2      # 下划线模式
  ip-address: 192.168.0.2      # 烤肉串模式
  IP_ADDRESS: 192.168.0.2      # 常量模式

使用@Value

dataSource:
  driverClassName: com.mysql.jdbc.Driver789
  passWord: 0127

测试:

@SpringBootTest
class Springboot13ConfigurationApplicationTests {
    
    

    @Value("${dataSource.password}")
    private String password;

    @Test
    void contextLoads() {
    
    
        System.out.println(password);
    }
}

报错:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.itheima.Springboot13ConfigurationApplicationTests': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'dataSource.password' in value "${dataSource.password}"

总结

  1. @ConfigurationProperties绑定属性时支持属性名宽松绑定,这个宽松体现在属性名的命名规则上
  2. @Value注解不支持松散绑定规则
  3. 绑定前缀名推荐采用烤肉串命名规则,即使用中划线做分隔符

常用计量单位绑定

在前面的配置中,我们书写了如下配置值,其中第三项超时时间timeout描述了服务器操作超时时间,当前值是-1表示永不超时。

servers:
  ip-address: 192.168.0.2       
  port: 2345
  timeout: -1
  serverTimeOut: 3
  dataSize: 10MB
@Component
@Data
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
    
    
    private String ipAddress;
    private int port;
    private long timeout;
 	//设置时间单位
    @DurationUnit(ChronoUnit.HOURS)
    private Duration serverTimeOut;
    //设置容量单位
    @DataSizeUnit(DataUnit.MEGABYTES)
    private DataSize dataSize;
}

在这里插入图片描述
Duration: 表示时间间隔,可以通过@DurationUnit注解描述时间单位,例如上例中描述的单位为小时(ChronoUnit.HOURS)

DataSize: 表示存储空间,可以通过@DataSizeUnit注解描述存储空间单位,例如上例中描述的单位为MB(DataUnit.MEGABYTES)

​使用上述两个单位就可以有效避免因沟通不同步或文档不健全导致的信息不对称问题,从根本上解决了问题,避免产生误读。
Druation常用单位如下:
在这里插入图片描述
DataSize常用单位如下:
在这里插入图片描述

校验

SpringBoot给出了强大的数据校验功能,可以有效的避免此类问题的发生。在JAVAEE的JSR303规范中给出了具体的数据校验标准,开发者可以根据自己的需要选择对应的校验框架,此处使用Hibernate提供的校验框架来作为实现进行数据校验。

1.开启校验框架

<!--1.导入JSR303规范-->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
</dependency>
<!--使用hibernate框架提供的校验器做实现-->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

2.在需要开启校验功能的类上使用注解@Validated开启校验功能

@Component
@Data
@ConfigurationProperties(prefix = "servers")
//开启对当前bean的属性注入校验
@Validated
public class ServerConfig {
    
    
}

3.对具体的字段设置校验规则

@Data
@ConfigurationProperties(prefix = "servers")
//2.开启对当前bean的属性注入校验
@Validated
public class ServerConfig {
    
    
    private String ipAddress;
    //3.设置具体的规则
    @Max(value = 8888,message = "最大值不能超过8888")
    @Min(value = 202,message = "最小值不能低于202")
    private int port;
    private long timeout;
    //定义单位
    @DurationUnit(ChronoUnit.HOURS)
    private Duration serverTimeOut;
    @DataSizeUnit(DataUnit.MEGABYTES)
    private DataSize dataSize;
}

4.设置参数

servers:
  ip-address: 192.168.0.2       
  port: 56
  timeout: -1
  serverTimeOut: 3
  dataSize: 10MB

5.测试

@SpringBootApplication
@EnableConfigurationProperties(ServerConfig.class)
public class Springboot13ConfigurationApplication {
    
    

 public static void main(String[] args) {
    
    
        ConfigurableApplicationContext ctx = SpringApplication.run(Springboot13ConfigurationApplication.class, args);
        ServerConfig bean = ctx.getBean(ServerConfig.class);
        System.out.println(bean);
    }

在这里插入图片描述

validation参数校验:https://blog.csdn.net/weixin_43994244/article/details/125596803?spm=1001.2014.3001.5502

进制数据类型转换

在学习阶段其实我们遇到的问题往往复杂度比较低,单一性比较强,但是到了线上开发时,都是综合性的问题,而这个开发者遇到的问题就是由于bean的属性注入引发的灾难。

连接数据库正常操作,但是运行程序后显示的信息是密码错误。来看看用户名密码的配置是如何写的:

dataSource:
  driverClassName: com.mysql.jdbc.Driver789
  passWord: 0127

测试结果输出为87

@SpringBootApplication
@EnableConfigurationProperties(ServerConfig.class)
public class Springboot13ConfigurationApplication {
    
    

    @Bean
    @ConfigurationProperties(prefix = "data-source")
    public DruidDataSource datasource(){
    
    
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }

    public static void main(String[] args) {
    
    
        ConfigurableApplicationContext ctx = SpringApplication.run(Springboot13ConfigurationApplication.class, args);
        DruidDataSource ds = ctx.getBean(DruidDataSource.class);
        System.out.println(ds.getPassword());
    }

}

在这里插入图片描述
是因为,整数支持类型,支持二进制,八进制,十六进制

在这里插入图片描述
因为0127在开发者眼中是一个字符串“0127”,但是在springboot看来,这就是一个数字,而且是一个八进制的数字。当后台使用String类型接收数据时,如果配置文件中配置了一个整数值,它先按照整数进行处理,读取后再转换成字符串。巧了,0127撞上了八进制的格式,所以最终以十进制数字87的结果存在了。

注意:

  1. 养成习惯,字符串标准书写加上引号包裹
  2. 遇到0开头的数据多注意点
dataSource:
  driverClassName: com.mysql.jdbc.Driver789
  passWord: "0127"

在这里插入图片描述

测试

测试是保障程序正确性的唯一屏障,在企业级开发中更是不可缺少。

加载测试专用属性

测试过程本身并不是一个复杂的过程,但是很多情况下测试时需要模拟一些线上情况,或者模拟一些特殊情况。如果当前环境按照线上环境已经设定好了,例如是下面的配置:

env:
  maxMemory: 32GB
  minMemory: 16GB

但是现在想测试对应的兼容性,需要测试如下配置:

env:
  maxMemory: 16GB
  minMemory: 8GB

能不能每次测试的时候都去修改源码application.yml中的配置进行测试呢?
显然是不行的。每次测试前改过来,每次测试后改回去,这太麻烦了。于是就需要在测试环境中创建一组临时属性,去覆盖源码中设定的属性,这样测试用例就相当于是一个独立的环境,能够独立测试,就方便多了。

临时属性
可以通过对注解@SpringBootTest添加属性来模拟临时属性,具体如下:

配置文件为:

test:
  prop: testValue

使用properties属性可以为当前测试用例添加临时的属性配置

@SpringBootTest(properties = {
    
    "test.prop=testValue1"})
public class PropertiesAndArgsTest {
    
    

    @Value("${test.prop}")
    private String msg;
    
    @Test
    void testProperties(){
    
    
        System.out.println(msg);
    }
}

在这里插入图片描述
使用注解@SpringBootTest的properties属性就可以为当前测试用例添加临时的属性,覆盖源码配置文件中对应的属性值进行测试。

临时参数
在线上启动程序时,可以通过命令行参数设置属性值,能否提前测试一下这些配置信息是否有效呢?

使用args属性可以为当前测试用例添加临时的命令行参数

@SpringBootTest(args={
    
    "--test.prop=testValue2"})
public class PropertiesAndArgsTest {
    
    
    
    @Value("${test.prop}")
    private String msg;
    
    @Test
    void testProperties(){
    
    
        System.out.println(msg);
    }
}

在这里插入图片描述

优势: 比多环境开发中的测试环境影响范围更小,仅对当前测试类有效。

加载测试专用配置

如果在测试时需要一些独立的bean,专门应用于测试环境,能否实现呢?

其实一个spring环境中可以设置若干个配置文件或配置类,若干个配置信息可以同时生效。现在需求就是在测试环境中再添加一个配置类,然后启动测试环境时,生效此配置就行了。具体操作步骤如下:

1.在测试包test中创建专用的测试环境配置类

@Configuration
public class MsgConfig {
    
    
	//等效于创建了String类型的bean
    @Bean
    public String msg(){
    
    
        return "bean msg";
    }
}

2.在启动测试环境时,导入测试环境专用的配置类,使用@Import注解即可实现

@SpringBootTest
//不仅追加了原始配置,还导入自己编写的配置
@Import({
    
    MsgConfig.class})
public class ConfigurationTest {
    
    

    @Autowired
    private String msg;

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

在这里插入图片描述

Web环境模拟测试

当前已经可以实现业务层和数据层的测试,并且通过临时配置,控制每个测试用例加载不同的测试数据。但是实际企业开发不仅要保障业务层与数据层的功能安全有效,也要保障表现层的功能正常。但目的对表现层的测试都是通过postman手工测试的,并没有在打包过程中体现表现层功能被测试通过。能否在测试用例中对表现层进行功能测试呢?

在测试中对表现层功能进行测试需要一个基础和一个功能。所谓的一个基础是运行测试程序时,必须启动web环境,不然没法测试web功能。一个功能是必须在测试程序中具备发送web请求的能力,不然无法实现web功能的测试。所以在测试用例中测试表现层接口这项工作就转换成了两件事,一,如何在测试类中启动web测试,二,如何在测试类中发送web请求。

测试类中启动web环境
每一个springboot的测试类上方都会标准@SpringBootTest注解,而注解带有一个属性,叫做webEnvironment。通过该属性就可以设置在测试用例中启动web环境,具体如下:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WebTest {
    
    	
}

测试类中启动web环境时,可以指定启动的Web环境对应的端口,springboot提供了4种设置值,分别如下:
在这里插入图片描述

  • MOCK:根据当前设置确认是否启动web环境,例如使用了Servlet的API就启动web环境,属于适配性的配置
  • DEFINED_PORT:使用自定义的端口作为web服务器端口
  • RANDOM_PORT:使用随机端口作为web服务器端口
  • NONE:不启动web环境

通过上述配置,现在启动测试程序时就可以正常启用web环境了,建议测试时使用RANDOM_PORT,避免代码中因为写死设定引发线上功能打包测试时由于端口冲突导致意外现象的出现。就是说程序中写了用8080端口,结果线上环境8080端口被占用了,结果代码中所有写的东西都要改,这就是写死代码的代价。现在用随机端口就可以测试出来就没有这种问题的隐患了。

测试类中发送请求
springboot为了便于开发者进行对应的功能开发,对其又进行了包装,简化了开发步骤,具体操作如下:

请求接口


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

   @GetMapping
    public String getById(){
    
    
        System.out.println("getById is running .....");
        return "springboot";
    }
   }

1.在测试类中开启web虚拟调用功能,通过注解@AutoConfigureMockMvc实现此功能的开启

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//开启虚拟MVC调用
@AutoConfigureMockMvc
public class WebTest {
    
    
}

2.定义发起虚拟调用的对象MockMVC,通过自动装配的形式初始化对象

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//开启虚拟MVC调用
@AutoConfigureMockMvc
public class WebTest {
    
    

    @Test
    void testWeb(@Autowired MockMvc mvc) {
    
    
    }
}

3.创建一个虚拟请求对象,封装请求的路径,并使用MockMVC对象发送对应请求

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//开启虚拟MVC调用
@AutoConfigureMockMvc
public class WebTest {
    
    

    @Test
    void testWeb(@Autowired MockMvc mvc) throws Exception {
    
    
        //http://localhost:8080/books
        //创建虚拟请求,当前访问/books
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        //执行对应的请求
        mvc.perform(builder);
    }
}

在这里插入图片描述

web环境请求结果比对
目前已经成功的发送了请求,但是还没有起到测试的效果,测试过程必须出现预计值与真实值的比对结果才能确认测试结果是否通过,发完请求得到响应对象,至于响应对象中包含什么,就可以比对什么。

响应状态匹配:

@Test
void testStatus(@Autowired MockMvc mvc) throws Exception {
    
    
    MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books1");
    //本次调用的真实值
    ResultActions action = mvc.perform(builder);
    //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
    //定义本次调用的预期值,结果匹配器
    StatusResultMatchers status = MockMvcResultMatchers.status();
    //预计本次调用时成功的:状态200
    ResultMatcher ok = status.isOk();
    //添加预计值到本次调用过程中进行匹配
    action.andExpect(ok);
}

本次请求调用请求结果,匹配成功不打印内容,匹配失败打印请求内容和结果:
在这里插入图片描述
响应体匹配(非json数据格式)

@Test
void testBody(@Autowired MockMvc mvc) throws Exception {
    
    
    MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
    ResultActions action = mvc.perform(builder);
    //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
    //定义本次调用的预期值
    ContentResultMatchers content = MockMvcResultMatchers.content();
    ResultMatcher result = content.string("springboot1");
    //添加预计值到本次调用过程中进行匹配
    action.andExpect(result);
}

在这里插入图片描述

响应体匹配(json数据格式,开发中的主流使用方式)
请求接口:

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

    @GetMapping
    public Book getById(){
    
    
        System.out.println("getById is running .....");

        Book book = new Book();
        book.setId(1);
        book.setName("springboot");
        book.setType("springboot");
        book.setDescription("springboot");

        return book;
    }
}

测试:

@Test
void testJson(@Autowired MockMvc mvc) throws Exception {
    
    
    MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
    ResultActions action = mvc.perform(builder);
    //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
    //定义本次调用的预期值
    ContentResultMatchers content = MockMvcResultMatchers.content();
    ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot2\",\"type\":\"springboot\"}");
    //添加预计值到本次调用过程中进行匹配
    action.andExpect(result);
}

在这里插入图片描述

响应头信息匹配

@Test
void testContentType(@Autowired MockMvc mvc) throws Exception {
    
    
    MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
    ResultActions action = mvc.perform(builder);
    //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
    //定义本次调用的预期值
    HeaderResultMatchers header = MockMvcResultMatchers.header();
    ResultMatcher contentType = header.string("Content-Type", "application/json");
    //添加预计值到本次调用过程中进行匹配
    action.andExpect(contentType);
}

在这里插入图片描述

以下范例就是三种信息同时进行匹配校验,也是一个完整的信息匹配过程。

@Test
void testGetById(@Autowired MockMvc mvc) throws Exception {
    
    
    //请求
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        ResultActions action = mvc.perform(builder);
        //状态
        StatusResultMatchers status = MockMvcResultMatchers.status();
        ResultMatcher ok = status.isOk();
        action.andExpect(ok);
        //请求头
        HeaderResultMatchers header = MockMvcResultMatchers.header();
        ResultMatcher contentType = header.string("Content-Type", "application/json");
        action.andExpect(contentType);
        //请求结果
        ContentResultMatchers content = MockMvcResultMatchers.content();
        ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot\",\"type\":\"springboot\",\"description\":\"springboot\"}");
        action.andExpect(result);
}

数据层测试回滚

当前我们的测试程序可以完美的进行表现层、业务层、数据层接口对应的功能测试了,但是测试用例开发完成后,在打包的阶段由于test生命周期属于必须被运行的生命周期,如果跳过会给系统带来极高的安全隐患,所以测试用例必须执行。但是新的问题就呈现了,测试用例如果测试时产生了事务提交就会在测试过程中对数据库数据产生影响,进而产生垃圾数据。这个过程不是我们希望发生的,作为开发者测试用例该运行运行,但是过程中产生的数据不要在我的系统中留痕,这样该如何处理呢?

​springboot早就为开发者想到了这个问题,并且针对此问题给出了最简解决方案,在原始测试用例中添加注解@Transactional即可实现当前测试用例的事务不提交。当程序运行后,只要注解@Transactional出现的位置存在注解@SpringBootTest,springboot就会认为这是一个测试程序,无需提交事务,所以也就可以避免事务的提交。

@SpringBootTest
//添加事务,不加入数据库中
@Transactional
//Rollback默认回滚
@Rollback(true)
public class DaoTest {
    
    
    @Autowired
    private BookService bookService;

    @Test
    void testSave(){
    
    
        Book book = new Book();
        book.setName("springboot3");
        book.setType("springboot3");
        book.setDescription("springboot3");

        bookService.save(book);
    }
}

如果开发者想提交事务,也可以,再添加一个@RollBack的注解,设置回滚状态为false即可正常提交事务。
总结

  1. 在springboot的测试类中通过添加注解@Transactional来阻止测试用例提交事务
  2. 通过注解@Rollback控制springboot测试类执行结果是否提交事务,需要配合注解@Transactional使用

测试用例数据设定

对于测试用例的数据固定书写肯定是不合理的,springboot提供了在配置中使用随机值的机制,确保每次运行程序加载的数据都是随机的。具体如下:

testcase:
  book:
    id: ${
    
    random.int} # 随机整数
    id2: ${
    
    random.int(10)} # 10以内的整数
    type: ${
    
    random.int!5,10!} # 5-10以内的
    name: ${
    
    random.value} #随意字符串
    uuid: ${
    
    random.uuid} # uuid
    publishTime: ${
    
    random.long} #时间

当前配置就可以在每次运行程序时创建一组随机数据,避免每次运行时数据都是固定值的尴尬现象发生,有助于测试功能的进行。数据的加载按照之前加载数据的形式,使用@ConfigurationProperties注解即可

@Component
@Data
@ConfigurationProperties(prefix = "testcase.book")
public class BookCase {
    
    
    private int id;
    private int id2;
    private int type;
    private String name;
    private String uuid;
    private long publishTime;
}

对于随机值的产生,还有一些小的限定规则,比如产生的数值性数据可以设置范围等,具体如下:

  • ${random.int}表示随机整数
  • ${random.int(10)}表示10以内的随机数
  • ${random.int(10,20)}表示10到20的随机数
  • 其中()可以是任意字符,例如[],!!均可

测试:

@SpringBootTest
public class PropertiesAndArgsTest {
    
    
    @Autowired
    private BookCase bookCase;

    @Test
    void testProperties(){
    
    
        System.out.println(bookCase);
    }
}

结果:

BookCase(id=1102808002, id2=1, type=6, name=\u9ED1\u9A6Ccfb3dac22b7b35ce3ec046cb85f94bdc, uuid=965a3dd5-094f-491f-b16b-c20782d6025d, publishTime=-4024999663751008632)

猜你喜欢

转载自blog.csdn.net/weixin_43994244/article/details/129992206