• Spring Boot (三)


    1、热部署

            热部署可以替我们节省大把花在重启项目本身上的时间。热部署原理上,一个springboot项目在运行时实际上是分两个过程进行的,根据加载的东西不同,划分成base类加载器与restart类加载器。

    • base类加载器:用来加载jar包中的类,jar包中的类和配置文件由于不会发生变化,因此不管加载多少次,加载的内容不会发生变化

    • restart类加载器:用来加载开发者自己开发的类、配置文件、页面等信息,这一类文件受开发者影响

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

    1.1、手动启动热部署

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

    1. <dependency>
    2.   <groupId>org.springframework.boot</groupId>
    3.   <artifactId>spring-boot-devtools</artifactId>
    4.   <optional>true</optional>
    5. </dependency>

    构建项目

    ctrl+F9

    1.2、自动启动热部署

    步骤①:设置自动构建项目

     

    步骤②:允许在程序运行时进行自动构建

     

    1.3、参与热部署监控的文件范围配置

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

    • /META-INF/maven

    • /META-INF/resources

    • /resources

    • /static

    • /public

    • /templates

            以上目录中的文件如果发生变化,是不参与热部署的。如果想修改配置,可以通过application.yml文件进行设定哪些文件不参与热部署操作

    1. spring:
    2. devtools:
    3.   restart:
    4.     # 设置不参与热部署的文件或文件夹
    5.     exclude: static/**,public/**,config/application.yml

    1.4、关闭热部署

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

    1. spring:
    2. devtools:
    3.   restart:
    4.     enabled: false

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

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

    2、配置高级

    2.1、@ConfigurationProperties

            在基础篇学习了@ConfigurationProperties注解,此注解的作用是用来为bean绑定属性的。开发者可以在yml配置文件中以对象的格式添加若干属性

    1. servers:
    2. ip-address: 192.168.0.1
    3. port: 2345
    4. timeout: -1

    然后再开发一个用来封装数据的实体类,注意要提供属性对应的setter方法

    1. @Component
    2. @Data
    3. public class ServerConfig {
    4.    private String ipAddress;
    5.    private int port;
    6.    private long timeout;
    7. }

    使用@ConfigurationProperties注解就可以将配置中的属性值关联到开发的模型类上

    1. @Component
    2. @Data
    3. @ConfigurationProperties(prefix = "servers")
    4. public class ServerConfig {
    5.    private String ipAddress;
    6.    private int port;
    7.    private long timeout;
    8. }

            这样加载对应bean的时候就可以直接加载配置属性值了。但是目前我们学的都是给自定义的bean使用这种形式加载属性值,如果是第三方的bean呢?能不能用这种形式加载属性值呢?为什么会提出这个疑问?原因就在于当前@ConfigurationProperties注解是写在类定义的上方,而第三方开发的bean源代码不是你自己书写的,你也不可能到源代码中去添加@ConfigurationProperties注解,这种问题该怎么解决呢?下面就来说说这个问题。

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

    步骤①:使用@Bean注解定义第三方bean

    1. @Bean
    2. public DruidDataSource datasource(){
    3.    DruidDataSource ds = new DruidDataSource();
    4.    return ds;
    5. }

    步骤②:在yml中定义要绑定的属性,注意datasource此时全小写

    1. datasource:
    2. driverClassName: com.mysql.jdbc.Driver

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

    1. @Bean
    2. @ConfigurationProperties(prefix = "datasource")
    3. public DruidDataSource datasource(){
    4.    DruidDataSource ds = new DruidDataSource();
    5.    return ds;
    6. }

            操作方式完全一样,只不过@ConfigurationProperties注解不仅能添加到类上,还可以添加到方法上,添加到类上是为spring容器管理的当前类的对象绑定属性,添加到方法上是为spring容器管理的当前方法的返回值对象绑定属性,其实本质上都一样。

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

    步骤①:在配置类上开启@EnableConfigurationProperties注解,并标注要使用@ConfigurationProperties注解绑定属性的类

    1. @SpringBootApplication
    2. @EnableConfigurationProperties(ServerConfig.class)
    3. public class Springboot13ConfigurationApplication {
    4. }

    步骤②:在对应的类上直接使用@ConfigurationProperties进行属性绑定

    1. @Data
    2. @ConfigurationProperties(prefix = "servers")
    3. public class ServerConfig {
    4.    private String ipAddress;
    5.    private int port;
    6.    private long timeout;
    7. }

            有人感觉这没区别啊?注意观察,现在绑定属性的ServerConfig类并没有声明@Component注解。当使用@EnableConfigurationProperties注解时,spring会默认将其标注的类定义为bean,因此无需再次声明@Component注解了。

    总结

    1. 使用@ConfigurationProperties可以为使用@Bean声明的第三方bean绑定属性

    2. 当使用@EnableConfigurationProperties声明进行属性绑定的bean后,无需使用@Component注解再次进行bean声明

     

    2.2、松散绑定

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

    1. dataSource:
    2. driverClassName: com.mysql.jdbc.Driver

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

    1. @Bean
    2. @ConfigurationProperties(prefix = "dataSource")
    3. public DruidDataSource datasource(){
    4.    DruidDataSource ds = new DruidDataSource();
    5.    return ds;
    6. }

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

    1. Configuration property name 'dataSource' is not valid:
    2.    Invalid characters: 'S'
    3.    Bean: datasource
    4.    Reason: Canonical names should be kebab-case ('-' separated), lowercase alpha-numeric characters and must start with a letter
    5. Action:
    6. Modify 'dataSource' so that it conforms to the canonical names requirements.

            为什么会出现这种问题,这就要来说一说springboot进行属性绑定时的一个重要知识点了,有关属性名称的宽松绑定,也可以称为宽松绑定。

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

    在ServerConfig中的ipAddress属性名

    1. @Component
    2. @Data
    3. @ConfigurationProperties(prefix = "servers")
    4. public class ServerConfig {
    5.    private String ipAddress;
    6. }

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

    1. servers:
    2. ipAddress: 192.168.0.2       # 驼峰模式
    3. ip_address: 192.168.0.2      # 下划线模式
    4. ip-address: 192.168.0.2      # 烤肉串模式
    5. IP_ADDRESS: 192.168.0.2      # 常量模式

            也可以说,以上4种模式最终都可以匹配到ipAddress这个属性名。为什么这样呢?原因就是在进行匹配时,配置中的名称要去掉中划线和下划线后,忽略大小写的情况下去与java代码中的属性名进行忽略大小写的等值匹配,以上4种命名去掉下划线中划线忽略大小写后都是一个词ipaddress,java代码中的属性名忽略大小写后也是ipaddress,这样就可以进行等值匹配了,这就是为什么这4种格式都能匹配成功的原因。不过springboot官方推荐使用烤肉串模式,也就是中划线模式。

    到这里我们掌握了一个知识点,就是命名的规范问题。再来看开始出现的编程错误信息

    1. Configuration property name 'dataSource' is not valid:
    2.    Invalid characters: 'S'
    3.    Bean: datasource
    4.    Reason: Canonical names should be kebab-case ('-' separated), lowercase alpha-numeric characters and must start with a letter
    5. Action:
    6. Modify 'dataSource' so that it conforms to the canonical names requirements.

            其中Reason描述了报错的原因,规范的名称应该是烤肉串(kebab)模式(case),即使用-分隔,使用小写字母数字作为标准字符,且必须以字母开头。然后再看我们写的名称dataSource,就不满足上述要求。闹了半天,在书写前缀时,这个词不是随意支持的,必须使用上述标准。编程写了这么久,基本上编程习惯都养成了,到这里又被springboot教育了,没辙,谁让人家东西好用呢,按照人家的要求写吧。

    总结

    1. @ConfigurationProperties绑定属性时支持属性名宽松绑定,这个宽松体现在属性名的命名规则上

    2. @Value注解不支持松散绑定规则

    3. 绑定前缀名推荐采用烤肉串命名规则,即使用中划线做分隔符

     

    2.3、常用计量单位绑定

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

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

            但是每个人都这个值的理解会产生不同,比如线上服务器完成一次主从备份,配置超时时间240,这个240如果单位是秒就是超时时间4分钟,如果单位是分钟就是超时时间4小时。面对一次线上服务器的主从备份,设置4分钟,简直是开玩笑,别说拷贝过程,备份之前的压缩过程4分钟也搞不定,这个时候问题就来了,怎么解决这个误会?

            除了加强约定之外,springboot充分利用了JDK8中提供的全新的用来表示计量单位的新数据类型,从根本上解决这个问题。以下模型类中添加了两个JDK8中新增的类,分别是Duration和DataSize

    1. @Component
    2. @Data
    3. @ConfigurationProperties(prefix = "servers")
    4. public class ServerConfig {
    5.    //时间范围
    6.    @DurationUnit(ChronoUnit.HOURS)
    7.    private Duration serverTimeOut;
    8.    //文件单位
    9.    @DataSizeUnit(DataUnit.MEGABYTES)
    10.    private DataSize dataSize;
    11. }

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

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

            使用上述两个单位就可以有效避免因沟通不同步或文档不健全导致的信息不对称问题,从根本上解决了问题,避免产生误读。

    2.4、格式校验

            目前我们在进行属性绑定时可以通过松散绑定规则在书写时放飞自我了,但是在书写时由于无法感知模型类中的数据类型,就会出现类型不匹配的问题,比如代码中需要int类型,配置中给了非法的数值,例如写一个“a",这种数据肯定无法有效的绑定,还会引发错误。

            SpringBoot给出了强大的数据校验功能,可以有效的避免此类问题的发生。在JAVAEE的JSR303规范中给出了具体的数据校验标准,开发者可以根据自己的需要选择对应的校验框架,此处使用Hibernate提供的校验框架来作为实现进行数据校验。书写应用格式非常固定,话不多说,直接上步骤

    步骤①:开启校验框架

    1. <dependency>
    2.    <groupId>javax.validationgroupId>
    3.    <artifactId>validation-apiartifactId>
    4. dependency>
    5. <dependency>
    6.    <groupId>org.hibernate.validatorgroupId>
    7.    <artifactId>hibernate-validatorartifactId>
    8. dependency>

    步骤②:在需要开启校验功能的类上使用注解@Validated开启校验功能

    1. @Component
    2. @Data
    3. @ConfigurationProperties(prefix = "servers")
    4. //开启对当前bean的属性注入校验
    5. @Validated
    6. public class ServerConfig {
    7. }

    步骤③:对具体的字段设置校验规则

    1. @Component
    2. @Data
    3. @ConfigurationProperties(prefix = "servers")
    4. //开启对当前bean的属性注入校验
    5. @Validated
    6. public class ServerConfig {
    7.    //设置具体的规则
    8.    @Max(value = 8888,message = "最大值不能超过8888")
    9.    @Min(value = 202,message = "最小值不能低于202")
    10.    private int port;
    11. }

            通过设置数据格式校验,就可以有效避免非法数据加载,其实使用起来还是挺轻松的,基本上就是一个格式。

    总结

    1. 开启Bean属性校验功能一共3步:导入JSR303与Hibernate校验框架坐标、使用@Validated注解启用校验功能、使用具体校验规则规范数据校验格式

    3、测试

    3.1、加载测试专用属性

    临时属性

            springboot已经为我们开发者早就想好了这种问题该如何解决,并且提供了对应的功能入口。在测试用例程序中,可以通过对注解@SpringBootTest添加属性来模拟临时属性,具体如下:

    1. //properties属性可以为当前测试用例添加临时的属性配置
    2. @SpringBootTest(properties = {"test.prop=testValue1"})
    3. public class PropertiesAndArgsTest {
    4.    @Value("${test.prop}")
    5.    private String msg;
    6.    
    7.    @Test
    8.    void testProperties(){
    9.        System.out.println(msg);
    10.   }
    11. }

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

    临时参数

            除了上述这种情况,在前面讲解使用命令行启动springboot程序时讲过,通过命令行参数也可以设置属性值。而且线上启动程序时,通常都会添加一些专用的配置信息。作为运维人员他们才不懂java,更不懂这些配置的信息具体格式该怎么写,那如果我们作为开发者提供了对应的书写内容后,能否提前测试一下这些配置信息是否有效呢?当时是可以的,还是通过注解@SpringBootTest的另一个属性来进行设定。

    1. //args属性可以为当前测试用例添加临时的命令行参数
    2. @SpringBootTest(args={"--test.prop=testValue2"})
    3. public class PropertiesAndArgsTest {
    4.    
    5.    @Value("${test.prop}")
    6.    private String msg;
    7.    
    8.    @Test
    9.    void testProperties(){
    10.        System.out.println(msg);
    11.   }
    12. }

            使用注解@SpringBootTest的args属性就可以为当前测试用例模拟命令行参数并进行测试。

    临时参数>临时配置>配置文件

    3.2、加载测试专用配置

            学习过Spring的知识,我们都知道,其实一个spring环境中可以设置若干个配置文件或配置类,若干个配置信息可以同时生效。现在我们的需求就是在测试环境中再添加一个配置类,然后启动测试环境时,生效此配置就行了。其实做法和spring环境中加载多个配置信息的方式完全一样。具体操作步骤如下:

    步骤①:在测试包test中创建专用的测试环境配置类

    1. @Configuration
    2. public class MsgConfig {
    3.    @Bean
    4.    public String msg(){
    5.        return "bean msg";
    6.   }
    7. }

    上述配置仅用于演示当前实验效果,实际开发可不能这么注入String类型的数据

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

    1. @SpringBootTest
    2. @Import({MsgConfig.class})
    3. public class ConfigurationTest {
    4.    @Autowired
    5.    private String msg;
    6.    @Test
    7.    void testConfiguration(){
    8.        System.out.println(msg);
    9.   }
    10. }

            到这里就通过@Import属性实现了基于开发环境的配置基础上,对配置进行测试环境的追加操作,实现了1+1的配置环境效果。这样我们就可以实现每一个不同的测试用例加载不同的bean的效果,丰富测试用例的编写,同时不影响开发环境的配置。

    总结

    1. 定义测试环境专用的配置类,然后通过@Import注解在具体的测试中导入临时的配置,例如测试用例,方便测试过程,且上述配置不影响其他的测试类环境

    3.3、Web环境模拟测试

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

    3.3.1、测试类中启动web环境

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

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

            测试类中启动web环境时,可以指定启动的Web环境对应的端口,springboot提供了4种设置值,分别如下:

    • MOCK:根据当前设置确认是否启动web环境,例如使用了Servlet的API就启动web环境,属于适配性的配置

    • DEFINED_PORT:使用自定义的端口作为web服务器端口

    • RANDOM_PORT:使用随机端口作为web服务器端口

    • NONE:不启动web环境

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

            测试环境中的web环境已经搭建好了,下面就可以来解决第二个问题了,如何在程序代码中发送web请求。

    3.3.2、测试类中发送请求

            对于测试类中发送请求,其实java的API就提供对应的功能,只不过平时各位小伙伴接触的比较少,所以较为陌生。springboot为了便于开发者进行对应的功能开发,对其又进行了包装,简化了开发步骤,具体操作如下:

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

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

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

    1. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    2. //开启虚拟MVC调用
    3. @AutoConfigureMockMvc
    4. public class WebTest {
    5.    @Test
    6.    void testWeb(@Autowired MockMvc mvc) {
    7.   }
    8. }

    步骤③:创建一个虚拟请求对象,封装请求的路径,并使用MockMVC对象发送对应请求

    1. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    2. //开启虚拟MVC调用
    3. @AutoConfigureMockMvc
    4. public class WebTest {
    5.    @Test
    6.    void testWeb(@Autowired MockMvc mvc) throws Exception {
    7.        //http://localhost:8080/books
    8.        //创建虚拟请求,当前访问/books
    9.        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
    10.        //执行对应的请求
    11.        mvc.perform(builder);
    12.   }
    13. }

            执行测试程序,现在就可以正常的发送/books对应的请求了,注意访问路径不要写http://localhost:8080/books,因为前面的服务器IP地址和端口使用的是当前虚拟的web环境,无需指定,仅指定请求的具体路径即可。

    总结

    1. 在测试类中测试web层接口要保障测试类启动时启动web容器,使用@SpringBootTest注解的webEnvironment属性可以虚拟web环境用于测试

    2. 为测试方法注入MockMvc对象,通过MockMvc对象可以发送虚拟请求,模拟web请求调用过程

    3.3.3、web环境请求结果比对

            上一节已经在测试用例中成功的模拟出了web环境,并成功的发送了web请求,本节就来解决发送请求后如何比对发送结果的问题。其实发完请求得到的信息只有一种,就是响应对象。至于响应对象中包含什么,就可以比对什么。常见的比对内容如下:

    • 响应状态匹配

      1. @Test
      2. void testStatus(@Autowired MockMvc mvc) throws Exception {
      3.    MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
      4.    ResultActions action = mvc.perform(builder);
      5.    //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
      6.    //定义本次调用的预期值
      7.    StatusResultMatchers status = MockMvcResultMatchers.status();
      8.    //预计本次调用时成功的:状态200
      9.    ResultMatcher ok = status.isOk();
      10.    //添加预计值到本次调用过程中进行匹配
      11.    action.andExpect(ok);
      12. }
    • 响应体匹配(非json数据格式)

      1. @Test
      2. void testBody(@Autowired MockMvc mvc) throws Exception {
      3.    MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
      4.    ResultActions action = mvc.perform(builder);
      5.    //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
      6.    //定义本次调用的预期值
      7.    ContentResultMatchers content = MockMvcResultMatchers.content();
      8.    ResultMatcher result = content.string("springboot2");
      9.    //添加预计值到本次调用过程中进行匹配
      10.    action.andExpect(result);
      11. }
    • 响应体匹配(json数据格式,开发中的主流使用方式)

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

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

            基本上齐了,头信息,正文信息,状态信息都有了,就可以组合出一个完美的响应结果比对结果了。以下范例就是三种信息同时进行匹配校验,也是一个完整的信息匹配过程。

    1. @Test
    2. void testGetById(@Autowired MockMvc mvc) throws Exception {
    3.    MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
    4.    ResultActions action = mvc.perform(builder);
    5.    StatusResultMatchers status = MockMvcResultMatchers.status();
    6.    ResultMatcher ok = status.isOk();
    7.    action.andExpect(ok);
    8.    HeaderResultMatchers header = MockMvcResultMatchers.header();
    9.    ResultMatcher contentType = header.string("Content-Type", "application/json");
    10.    action.andExpect(contentType);
    11.    ContentResultMatchers content = MockMvcResultMatchers.content();
    12.    ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot\",\"type\":\"springboot\"}");
    13.    action.andExpect(result);
    14. }

    总结

    1. web虚拟调用可以对本地虚拟请求的返回响应信息进行比对,分为响应头信息比对、响应体信息比对、响应状态信息比对

    3.3.4、数据层测试回滚

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

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

    1. @SpringBootTest
    2. @Transactional
    3. @Rollback(true)
    4. public class DaoTest {
    5.    @Autowired
    6.    private BookService bookService;
    7.    @Test
    8.    void testSave(){
    9.        Book book = new Book();
    10.        book.setName("springboot3");
    11.        book.setType("springboot3");
    12.        book.setDescription("springboot3");
    13.        bookService.save(book);
    14.   }
    15. }

            如果开发者想提交事务,也可以,再添加一个@RollBack的注解,设置回滚状态为false即可正常提交事务,是不是很方便?springboot在辅助开发者日常工作这一块展现出了惊人的能力,实在太贴心了。

    总结

    1. 在springboot的测试类中通过添加注解@Transactional来阻止测试用例提交事务

    2. 通过注解@Rollback控制springboot测试类执行结果是否提交事务,需要配合注解@Transactional使用

    3.3.5、测试用例数据设定

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

    1. testcase:
    2. book:
    3.   id: ${random.int}
    4.   id2: ${random.int(10)}
    5.   type: ${random.int!5,10!}
    6.   name: ${random.value}
    7.   uuid: ${random.uuid}
    8.   publishTime: ${random.long}

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

    1. @Component
    2. @Data
    3. @ConfigurationProperties(prefix = "testcase.book")
    4. public class BookCase {
    5.    private int id;
    6.    private int id2;
    7.    private int type;
    8.    private String name;
    9.    private String uuid;
    10.    private long publishTime;
    11. }

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

    • ${random.int}表示随机整数

    • ${random.int(10)}表示10以内的随机数

    • ${random.int(10,20)}表示10到20的随机数

    • 其中()可以是任意字符,例如[],!!均可

    总结

    1. 使用随机数据可以替换测试用例中书写的固定数据,提高测试用例中的测试数据有效性

     

  • 相关阅读:
    微信小程序酒店选择日期和入住人数(有效果图)
    SpringBoot实用开发篇复习4(实用开发篇完)
    [附源码]JAVA毕业设计商店管理系统(系统+LW)
    python学习——python的内建函数总结
    计算机毕业设计(附源码)python智慧景区一体化售票系统
    @Zabbix6.2安装部署【 Red Hat Enterprise Linux 8.2】
    1024节日快乐!
    Jupyter Notebook修改默认浏览器方法
    html盒子模型
    在SpringBoot项目中整合拦截器
  • 原文地址:https://blog.csdn.net/keleID/article/details/134349296