• SpringBoot中单元测试的使用


    pom文件需要引入的坐标

    
    <dependency>
       <groupId>junitgroupId>
       <artifactId>junitartifactId>
    dependency>
    <dependency>
       <groupId>org.springframework.bootgroupId>
       <artifactId>spring-boot-testartifactId>
    dependency>
    <dependency>
       <groupId>org.springframeworkgroupId>
       <artifactId>spring-testartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    需要使用的注解

    import org.elasticsearch.client.RestHighLevelClient;
    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.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = {YourApplication.class})
    public class EsDemoTest {
        @Autowired
        private XXXXXService xxxxxService;
    
        @Test
        public void test(){
            System.out.println(xxxxxService.xxx);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    出现的问题

    会报错,我是报下面的错误

     [o.s.test.context.TestContextManager ]: Caught exception while allowing TestExecutionListener
    java.lang.IllegalStateException: Failed to load ApplicationContext
    Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name
    Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name
    Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    问题解决

    我的是因为之前的一个配置文件没有修改所以才出现的问题,我看网上的解决方法有下面的几种

    1. spring-test坐标的版本较低,按我上面的pom坐标这么改便可以解决问题(这个是我第一次报错的时候改的),参考博文
    2. 在@SpringBootTest这个注解上面增加一个参数,如下所示:参考博客,Web项目
      @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
      
      • 1
      @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 作用是启动Spring的ApplicationContext,参数webEnvironment指定了运行的web环境

    注解

    注解与 Junit4 是一致的
    在这里插入图片描述
    更多的配置说明可以看:这篇博客

    @RunWith(SpringRunner.class)

    是JUnit的注解,作用是关联Spring Boot Test,使运行JUnit时同时启动Spring,该注解能够让Junit运行在Spring测试环境中,得到Spring上下文支持
    4.3版本中提供了等同于SpringJUnit4ClassRunner.class的简写类SpringRunner.class

    @SpringBootTest

    当测试中需要使用Spring Boot功能时,可以使用@SpringBootTest注解,装配Spring上下文。

    @SpringBootTest注解意思就是将SpringBoot主类中导入的bean全都包含进来。

    当作为普通Spring单元测试时,可以使用@ContextConfiguration,装配Spring 上下文。

    如果指定的classes为普通Configer(@SpringBootConfiguration)配置类,则会以此配置初始化Spring 上下文,而不会加载其他Bean到Spring容器

    如果未指定classes 参数或者指定的classes参数不是启动main函数入口@SpringBootTest(classes = {YourApplication.class})(也就是使用@SpringBootApplication注解的类),则会自动从当前测试类包一层一层向上检索,直到找到有@SpringBootApplication或@SpringBootConfiguration注释的类为止。以此来启动Spring Boot应用,并装载Spring上下文。

    如果未检索到Spring Boot启动主配置类,则会抛出异常: java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=…) with your test

    其包含的属性如下:

    1. value 指定配置属性
    2. properties 指定配置属性,和value意义相同
    3. classes 指定配置类,等同于@ContextConfiguration中的class,若没有显示指定,将查找嵌套的@Configuration类,然后返回到SpringBootConfiguration搜索配置
    4. webEnvironment 指定web环境,可选值有:MOCK、RANDOM_PORT、DEFINED_PORT、NONE
      • MOCK 此值为默认值,该类型提供一个mock环境,此时内嵌的服务(servlet容器)并没有真正启动,也不会监听web端口。
      • RANDOM_PORT 启动一个真实的web服务,监听一个随机端口。
      • DEFINED_PORT 启动一个真实的web服务,监听一个定义好的端口(从配置中读取)。
      • NONE 启动一个非web的ApplicationContext,既不提供mock环境,也不提供真是的web服务。

    集成测试侧重于集成应用程序的不同层,意味着不涉及Mock

    @ContextConfiguration

    @ContextConfiguration这个注解通常与@RunWith(SpringJUnit4ClassRunner.class)联合使用用来测试

    4.3版本中提供了等同于SpringJUnit4ClassRunner.class的简写类SpringRunner.class

    当作为普通Spring单元测试时,可以使用@ContextConfiguration,装配Spring 上下文。

    当一个类添加了注解@Component,那么他就自动变成了一个bean

    当这些bean收集起来之后,当我们想要在某个测试类使用@Autowired注解来引入这些收集起来的bean时,只需要给这个测试类添加@ContextConfiguration注解来标注我们想要导入这个测试类的某些bean

    使用

    @Component
    public class CDPlayConfig {
     
    }
    
    • 1
    • 2
    • 3
    • 4

    进行测试,这个只会将这个Config注入的ben获取到

    @RunWith(SpringRunner.class)
    @ContextConfiguration(classes=CDPlayConfig.class)
    public class CDPlayerTest {
     	@Autowired
     	private CDPlayConfig Config;
    	@Test
    	public void test(){
    		congiog.xxx;
    	}
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    多bean导入

    @RunWith(SpringRunner.class)
    @ContextConfiguration(classes = {A.class, B.class})
    public class CDPlayerTest {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果默认不写的话,可以根据测试的类名,去找到与之对应的配置文件。

    可以查看这篇博客

    @TestConfiguration

    通过@TestConfiguration创建的Bean,作用范围在test里,不会影响到正式环境。通过@Autowired可以拿到创建的bean:
    这一篇参考的博文代码有一点问题,下面的是我自己本地测试过的,没有写得大奖还是谨慎参考吧,这个博客后面的那些测试方法测试过,发现行不通,可能是pom坐标问题吧

    @TestConfiguration作为内部类

    通过@TestConfiguration创建的Bean,作用范围在test里,不会影响到正式环境。通过@Autowired可以拿到创建的bean:
    只能是同一个类里面,如果在其他类里面进行测试,使用@Autowired注入后,是会报错端的,需要不报错需要使用@Import把配置类导入进来,或者也可使用@SpringBootTest(classes = CourseConfig.class)导入配置类

    @SpringBootTest
    public class CourseInnerClassTest {
    
        @TestConfiguration
        static class CourseConfig {
        	//这里需要使用static,不使用的话是会报错的
            @Bean
            public static String course() {
                returnTestConfiguration;
            }
        }
    
        @Autowired
        private Course course;
    
        @Test
        public void test() {
        	//断言,判断返回的值是否正确,正确的话是不会有如何报错的,对比content的内容和"TestConfiguration"是否一致,不一致的话会报错
            Assert.assertEquals(TestConfiguration, course);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    @TestConfiguration作为独立的配置类

    @TestConfiguration
    public class CourseConfig {
        @Bean
        public String course2() {
            return "TestConfiguration";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在测试时,需要使用@Import把配置类导入进来,或者也可使用@SpringBootTest(classes = CourseConfig.class)导入配置类

    @SpringBootTest
    @Import(CourseConfig.class)
    public class CourseSeparateClassTest {
        @Autowired
        private Course course2;
    
        @Test
        public void test() {
            Assert.assertEquals("TestConfiguration", CourseConfig.course2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    
    
    • 1
    
    
    • 1

    @WebAppConfiguration

    若你在一个测试类上加了@ContextConfiguration注解,则表示告诉Spring该集成测试加载的ApplicationContext应该是WebApplicationContext,表明这是一个WEB应用配置

    作用是模拟ServletContext。注解在类上,用来声明加载的ApplicationContex 是一个WebApplicationContext ,它的属性指定的是Web资源的位置,默认为 src/main/webapp ,自定义修改为 resource

    是一个用于声明集成测试所加载的ApplicationContext须是WebApplicationContext的类级别的注解

    测试

    超时测试

    @Test(timeout = 1000)
    public void testTimeout() throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("Complete");
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上面测试会失败,在一秒后会抛出异常org.junit.runners.model.TestTimeOutException:test timedout after 1000 millseconds

    异常测试

    可以测试代码是否它抛出想要得到的异常。expected参数和@Test 注释一起使用。现在让我们看看活动中的@Test(expected)

    @Test(expected)
    public void testTimeout() throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("Complete");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    mock

    参考博文

    参考博客2

    参考博文3

    在面向对象的程序设计中,模拟对象(mock object)是以可控的方式模拟真实对象行为的假对象。在编程过程中,通常通过模拟一些输入数据,来验证程序是否达到预期结果。

    使用模拟对象,可以模拟复杂的、真实的对象行为。如果在单元测试中无法使用真实对象,可采用模拟对象进行替代。

    使用Mockito一般分为三个步骤:

    1.模拟测试类所需的外部依赖
    2.执行测试代码
    3.判断执行结果是否达到预期

    Mockito 是一种 Java Mock 框架,主要是用来做 Mock 测试,它可以模拟任何 Spring 管理的 Bean、模拟方法的返回值、模拟抛出异常等等,避免你为了测试一个方法,却要自行构建整个 bean 的依赖链

    同时也会记录调用这些模拟方法的参数、调用顺序,从而可以校验出这个 Mock 对象是否有被正确的顺序调用,以及按照期望的参数被调用

    在这里插入图片描述

    与Mockito使用–@MockBean

    使用@MockBean注解来生成一个mock的bean,我们可以使用Mockito.when来模拟一些方法(比如Mock Jpa的Repository的find方法,这样就算数据库里的数据还没有准备好,我们也可以自己模拟数据)
    参考博文
    使用

    Mockito.when( 对象.方法名() ).thenReturn( 自定义结果 )
    
    • 1
    
    @SpringBootTest
    public class CourseServiceTest {
        @MockBean
        private CourseService courseService;
    
        @Test
        public void test() {
            Mockito.when( 对象.方法名() ).thenReturn(自定义结果);
            Assert.assertEquals(自定义结果, 对象.方法名());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Service单元测试

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = {YourApplication.class})
    public class EsDemoTest {
        @Autowired
        private XXXXXService xxxxxService;
    
        @Test
        public void test(){
            System.out.println(xxxxxService.xxx);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    
    
    • 1

    Controller单元测试

    参考博客
    有时候需要对Controller层(API)做测试,这时候就得用到MockMvc,不必启动工程就能测试这些接口。

    MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便

    测试方法1:步骤

    在boot项目中写一个controller接口,如下:

    @RestController
    public class HelloController {
     
        @RequestMapping("/testMock")
        public String hello(String name){
            return "hello "+name;
        }
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    写测试方法,如下:

    import org.junit.Assert;
    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.context.web.WebAppConfiguration;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.MvcResult;
    import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
    import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
     
    //SpringBoot1.4版本之前用的是SpringJUnit4ClassRunner.class
    @RunWith(SpringRunner.class)
    //SpringBoot1.4版本之前用的是@SpringApplicationConfiguration(classes = Application.class)
    @SpringBootTest(classes = App.class)
    //测试环境使用,用来表示测试环境使用的ApplicationContext将是WebApplicationContext类型的
    @WebAppConfiguration
    public class HelloControllerTest {
     
        @Autowired
        private WebApplicationContext webApplicationContext;
        private MockMvc mockMvc;
     
        @Before
        public void setUp(){
            //MockMvcBuilders.webAppContextSetup(WebApplicationContext context):指定WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的MockMvc;
            mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();//建议使用这种
        }
     
        @Test
        public void getHello() throws Exception{
     
            /**
             * 1、mockMvc.perform执行一个请求,Post请求就用.post方法
             * 2、MockMvcRequestBuilders.get("XXX")构造一个请求,Post请求就用.post方法
             * 3、ResultActions.param添加请求传值
             * 4、ResultActions.accept(MediaType.TEXT_HTML_VALUE))设置返回类型
            	accept(MediaType.APPLICATION_JSON_UTF8)代表客户端希望接受的数据类型为application/json;charset=UTF-8
            	contentType(MediaType.APPLICATION_JSON_UTF8)代表发送端发送的数据格式是application/json;charset=UTF-8
             * 5、ResultActions.andExpect添加执行完成后的断言。
             * 6、ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情
             	比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。
             * 5、ResultActions.andReturn表示执行完成后,返回响应的结果。
             	比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息
             */
     
            MvcResult mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/testMock")
                    .param("name","testmock")
                    .accept(MediaType.TEXT_HTML_VALUE)
    				//.contentType(MediaType.APPLICATION_JSON_UTF8)
    				)
                    //等同于Assert.assertEquals(200,status);
                   // .andExpect(MockMvcResultMatchers.status().isOk())  
                   //等同于 Assert.assertEquals("hello testmock",content);           
                   // .andExpect(MockMvcResultMatchers.content().string("hello testmock"))    
                    .andDo(MockMvcResultHandlers.print())
                    .andReturn();
            int status=mvcResult.getResponse().getStatus();                 //得到返回代码
            String content=mvcResult.getResponse().getContentAsString();    //得到返回结果
     
    		//断言,判断返回代码是否正确
            Assert.assertEquals(200,status);                      
            //断言,判断返回的值是否正确,对比content的内容和"hello ddd"是否一致,不一致的话会报错
            Assert.assertEquals("hello ddd",content);  
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    然后直接测试即可

    补充

    MockMvcBuilders提供了对应的创建方法standaloneSetup 方法和webAppContextSetup方法,在使用时直接调用即可

    private MockMvc mockMvc;
    
    @Autowire
    private WebApplicationContext webApplicationContext;
    
    @Before
    public void setup() {
        /*实例化方式一*/
        mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
        /*实例化方式二*/
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    更多的测试可以看这个博客的Mockito章节

    assertThat 基本语法
    参考这篇博客

    assertThat( [value], [matcher statement] );
    
    • 1
    1. value 是接下来想要测试的变量值;
    2. matcher statement 是使用 Hamcrest 匹配符来表达的对前面变量所期望的值的声明,如果 value 值与 matcher statement 所表达的期望值相符,则测试成功,否则测试失败。
  • 相关阅读:
    <栈和队列及模拟实现>——《Data Structure in C Train》
    自定义泛型
    [蓝桥杯 2019 省 A] 填空问题 E
    Elasticsearch (一) 基于Docker-compose 搭建集群
    基于Echarts实现大屏数据可视化物虚拟数据统计
    ubuntu 18.04下安装Anaconda、Cuda、Cudnn、gpu-Pytorch
    8.MySQL DCL (数据控制语言) 语句
    c++day6
    UE427_Logging
    AI游戏外挂为何违法?
  • 原文地址:https://blog.csdn.net/yyuggjggg/article/details/126022110