SpringBoot测试实践

news2024/11/23 15:50:27

测试按照粒度可分为3层:

  1. 单元测试:单元测试(Unit Testing)又称为模块测试 ,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
  2. 集成测试:整合测试(Integration Testing),又称组装测试,即对程序模块采用一次性或增值方式组装起来,对系统的接口进行正确性检验的测试工作。整合测试一般在单元测试之后、系统测试之前进行。实践表明,有时模块虽然可以单独工作,但是并不能保证组装起来也可以同时工作。该测试,可以由程序员或是软件品保工程师进行。
  3. 端到端测试:端到端测试(End To End Testing),又称系统测试。

在这里插入图片描述

通常需求开发后需要经过RD单测&自测后进行提测,提测往往需要达到一定的单测/自测代码覆盖率,或者某些基本case通过(冒烟测试),符合提测要求后QA对整体功能进行端到端测试。

完善的测试流程有助于提升代码质量和研发效率,这中间一方面对RD自身的业务素养有要求,另一方面对团队研发流程的规范性有要求。

成熟的研发流程和体系应减少“人性”带来的不稳定性,测试即是应对该不稳定性的有效方法之一。

本文记录了结合SpringBoot进行测试的一些案例,示例代码参见: spring-boot-test-sample

注意区分JUnit4和JUnit5的注解,本文代码基于JUnit4
{: .prompt-warning }

首先我们引入依赖:


  <dependencies>
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-junit-jupiter</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-module-junit4</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-api-mockito2</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.6.13</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>2.28.2</version>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-junit-jupiter</artifactId>
        <version>2.28.2</version>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>2.0.2</version>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito2</artifactId>
        <version>2.0.2</version>
        <scope>test</scope>
      </dependency>
    </dependencies>

  </dependencyManagement>

Mockito & PowerMockito 单元测试

当我们仅仅需要验证代码逻辑,不需要Spring的bean注入时,使用Mockito & PowerMockito来快速测试。

Mockito用于mock对象便于对代码逻辑进行测试&验证,但Mockito mock的方法有限,无法mock final、private、static方法,而PowerMockito框架弥补了这一点。两者可以混合使用。

案例:


@RunWith(PowerMockRunner.class)
// mock static method
@PrepareOnlyThisForTest({SampleUtil.class})
@PowerMockIgnore({"javax.net.ssl.*","javax.management.*", "javax.security.*", "javax.crypto.*"})
public class UnitTest {


    @Mock
    private SampleRepository sampleRepository;

    @InjectMocks
    private SampleService sampleService;

    @BeforeClass
    public static void beforeAll(){
        System.out.print("\n\n\n++++++++++++++\n\n\n");
    }

    @AfterClass
    public static void afterAll(){
        System.out.print("\n\n\n==============\n\n\n");
    }

    @Before
    public void before(){}

    @After
    public void after(){}

    @Test
    public void getSamples() throws JSONException {

        PowerMockito.mockStatic(SampleUtil.class);

        // 注意所有when内部的方法参数必须用org.mockito.ArgumentMatchers的方法包一层,不能直接传
        PowerMockito
            .when(SampleUtil.getSomething(eq("1"))) // 反例:.when(SampleUtil.getSomething("1")) 
            .thenReturn(1L);


        PowerMockito.when(sampleRepository.selectSamples(argThat(id -> id.equals(1L))))
                        .thenReturn(new ArrayList<>());

        PowerMockito.when(sampleRepository.selectSamples(argThat(new GreaterOrEqual<>(1L))))
            .thenReturn(new ArrayList<>());

        // 这里有any(),anyString()等
        // 如果参数是String,mock方法传入的是null,则mock不生效,传null需指定为any()
        Mockito
            .when(sampleRepository.selectSamples(any()))
            .thenReturn(new ArrayList<>());
        
        // verify方法调用次数
        Mockito.verify(sampleRepository, Mockito.times(1)).selectSamples(any());
        // Mockito.verify(sampleRepository, Mockito.times(1)).selectSamples(argThat(i->i.equals(1)));

        // capture参数验证
        ArgumentCaptor<Long> paramCap = ArgumentCaptor.forClass(Long.class);
        Mockito.verify(sampleRepository, Mockito.times(1)).selectSamples(paramCap.capture());
        Assert.assertNotNull(paramCap.getValue());

        
        List<Sample> samples = sampleService.listSamples("1");

        // 如果sample.size()返回Long,需要加一个 sample.size().longValue()方法
        Assert.assertEquals(0,samples.size());
        
        // 比较JSON
        JSONAssert.assertEquals("{\"a\":1}","{\"a\":1}",false);
        // 解析JSON
        Assert.assertEquals(JsonPath.parse("{\"a\":1}").read("$.a").getClass(),Integer.class);
    }
    
    @Test
    public void mockPrivate() {
         try {
            Method method = PowerMockito.method(Sample.class, "privateMethodName", Long.class);
            method.invoke(sampleService, 0L);
            Assert.fail();
        } catch (Exception e) {
            Assert.assertEquals("报错信息", e.getCause().getMessage());
        }
    
    }

}




@Mock和@MockBean使用格式:Mockito.when(localVar.method()).thenXxx…

@Spy和@SpyBean使用格式:Mockito.doXxx().when(localVar).method()

Spring 测试

当依赖Spring时,可以利用Spring和PowerMockito一起完成mock和test

案例:

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@PrepareOnlyThisForTest({SampleUtil.class})
@ContextConfiguration(classes = ControllerSliceTestWithPowerMockito.Context.class)
public class ControllerSliceTestWithPowerMockito {

    // @Import加入需要扫描的Bean
    // @Configuration配合其他都行,参考@ContextConfiguration注释
    @Import(SampleController.class)
    static class Context {

    }
    
    @MockBean
    private SampleService sampleService;

    @SpyBean
    private SampleConverter sampleConverter;


    @Test
    public void zkSetup() {
        PowerMockito.mockStatic(SampleUtil.class);
        PowerMockito.when(SampleUtil.getSomething(eq("a")))
            .thenReturn(1L);

        sampleConverter.test();
        
        // assert, verify
    }

}

WebMvc 切片测试

  • @AutoConfigureWebMvc : Use this if you need to configure the web layer for testing but don’t need to use MockMvc
  • @AutoConfigureMockMvc : Use this when you just want to configure MockMvc
  • @WebMvcTest : Includes both the @AutoConfigureWebMvc and the @AutoConfigureMockMvc, among other functionality.

三者区别,参考:What’s the difference between @AutoConfigureWebMvc and @AutoConfigureMockMvc?

案例一:

@WebMvcTest(SampleController.class)
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestSampleController.TestContext.class)
public class TestSampleController {

    private static final Logger log = LoggerFactory.getLogger(TestSampleController.class);

    // 这里填入需要扫描的Bean,这样就不用扫描整个project文件,加快测试速度
    @Import({SampleController.class, ControllerExceptionAdvice.class})
    @Configuration // 这里兼容老版本,高版本不用加
    static class TestContext {
    }


    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private SampleService sampleService;

    // 这里用SpyBean注解:当SampleController中用到了SampleConverter,但是又不需要mock,得用converter原本的逻辑
    // 或用@MockBean时,在 Mockito.when(...).thenCallRealMethod()就行。
    @SpyBean
    private SampleConverter sampleConverter;

    @Before
    public void prepareMock() {
      // 对SampleController中调用了的SampleService的方法进行mock
        Mockito
            .doNothing()
            .when(sampleService)
            .sampleMethod(Mockito.any());
    }

    @Test
    public void shouldReturnSuccess() throws Exception {

        SampleRequest req = new SampleRequest();
        req.setA(1L);
        String bodyJson = JsonUtils.toJson(req);

        mockMvc.perform(MockMvcRequestBuilders
                            .post("/test")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(bodyJson))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(MockMvcResultMatchers.content().json("{\"success\":true}"));
    }

    @Test
    public void shouldReturnErrorMsg() throws Exception {

        SampleRequest req = new SampleRequest();
        req.setB
        String bodyJson = JsonUtils.toJson(req);

        mockMvc.perform(MockMvcRequestBuilders
                            .post("/test2")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(bodyJson))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(MockMvcResultMatchers.content().json("{\"success\":false,\"errorMsg\":\"错误信息\"}"));
    }
}

案例二:


@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("com.dianping.cat.Cat")
// mock static method
@PrepareForTest({SampleUtil.class})
// spring bean
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@PowerMockIgnore({"javax.net.ssl.*","javax.management.*", "javax.security.*", "javax.crypto.*"})
// @SpringBootTest从当前包向上找@SpringBootConfiguration,或者指定
@SpringBootTest(classes = SpringTestCommonConfig.class)
public class SpringBeanTest {

    // 这个mock对象会注入Spring容器
    @MockBean
    private SampleRepository sampleRepository1;

    // 真实调用该对象逻辑
    @SpyBean
    private SampleRepository sampleRepository2;

    @Autowired
    private SampleRepository sampleRepository3;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private SampleConfig sampleConfig;

    @Test
    public void sampleBeanTest() throws JSONException {

        SampleRepository bean = applicationContext.getBean(SampleRepository.class);
        Assert.assertEquals(sampleRepository1,bean);

    }

}

此外我们使用h2内存数据库达到对Mapper的测试,也有testcontainers库推出用于测试与外部系统的交互,这里不赘述,详见示例代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1850147.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQlyog连接到主机时报错:错误号码2058Plugin sha256 password could not be loaded

1.问题重述 MySQL版本&#xff1a;8.4.0 SQlyog连接到主机时报错&#xff1a;错误号码2058Plugin sha256 password could not be loaded&#xff0c;如下图 经过查阅资料得知出现这个问题是因为 mysl8之前的加密规则是 mysql_native_password &#xff0c;而在mysql8之后&…

Python神经影像数据的处理和分析库之nipy使用详解

概要 神经影像学(Neuroimaging)是神经科学中一个重要的分支,主要研究通过影像技术获取和分析大脑结构和功能的信息。nipy(Neuroimaging in Python)是一个强大的 Python 库,专门用于神经影像数据的处理和分析。nipy 提供了一系列工具和方法,帮助研究人员高效地处理神经影…

Flutter学习:从搭建环境到运行

一、开发环境的搭建 本文所示内容都是在Windows系统下进行的。 1、下载 Flutter SDK Flutter 官网&#xff08;https://docs.flutter.cn/release/archive?tabwindows&#xff09; 或者通过 git clone -b master https://github.com/flutter/flutter.git 下载 2、配置环境…

【Redis】内存回收和内存淘汰机制

1 概念 Redis 所有的数据都是存储在内存中的, 如果不进行任何的内存回收, 那么很容易出现内存爆满的情况。因此&#xff0c;在某些情况下需要对占用的内存空间进行释放。 Redis 中内存的释放主要分为两类 Redis 中内存的释放主要分为两类: 内存回收: 将过期的 key 清除&#…

Vue77-编程式路由

一、需求 不写<router-link>实行路由的跳转。 因为<router-link>的本质是<a>&#xff0c;但是&#xff0c;有时&#xff0c;导航不一定是a标签&#xff01;或者&#xff0c;有时需要等一段时间&#xff0c;页面才跳转。 二、代码实现 三、小结

ciscn_2019_n_1

前戏--------checksec,运行查看 进入就可以发现这段代码 很浅显易懂 我们要得到的后面是 这里 我们要利用的漏洞是 get函数 0x30大小 加上8 exp: from pwn import * ghust remote("node5.buuoj.cn",28777) addr 0x4006BE payload bA * 0x30 bB*0x8 p64(addr…

上手微服务框架go-zero

文章目录 微服务框架与web框架的区别点在哪儿&#xff1f;为什么还要有微服务框架微服务框架与web框架的对比小结 为什么选go-zero&#xff1f;框架对比 下载并认识go-zero认识go-zero环境要求组成下载 实践go-zero基础功能案例apirpc服务功能说明准备构建rpc服务构建api服务服…

YIA主题侧边栏如何添加3D旋转标签云?

WordPress站点侧边栏默认的标签云排版很一般&#xff0c;而3D旋转标签云就比较酷炫了。下面boke112百科就以YIA主题为例&#xff0c;跟大家说一说如何将默认的标签云修改成3D旋转标签云&#xff0c;具体步骤如下&#xff1a; 1、点此下载3d标签云文件&#xff08;密码&#xf…

经典机器学习方法(7)—— 卷积神经网络CNN

参考&#xff1a;《动手学深度学习》第六章 卷积神经网络&#xff08;convolutional neural network&#xff0c;CNN&#xff09;是一类针对图像数据设计的神经网络&#xff0c;它充分利用了图像数据的特点&#xff0c;具有适合图像特征提取的归纳偏置&#xff0c;因而在图像相…

Unity核心

回顾 Unity核心学习的主要内容 项目展示 基础知识 认识模型制作流程 2D相关 图片导入设置相关 图片导入概述 参数设置——纹理类型 参数设置——纹理形状 参数设置——高级设置 参数设置——平铺拉伸 参数设置——平台设置&#xff08;非常重要&#xff09; Sprite Sprite Edit…

一个cmake版的C++项目代码模板,包含流水线、git以及代码格式化配置等支持CICD发布流程

本文给出快速构建C项目的代码仓库模板 &#xff0c;简单却完整 主要包括&#xff1a; 编译脚本 打包上传脚本- 依赖拉取 代码格式化配置 git配置 流水线pipeline配置 使用这个模板 你只需要&#xff1a;将源文件放到模块目录下&#xff0c;并添加到cmake中即可 一、简…

今日头条屏幕适配深度剖析

基本概念 首先几个基本概念解释&#xff1a; ● dpi&#xff1a;该值代表的是一英寸上有多少个像素点&#xff0c;常见取值为120&#xff0c;160&#xff0c;240。一般这个值才叫做密度 在android里面获取的方法为 metrics.densityDpi; 屏幕尺寸/分辨率得出DPI&#xff0c;一个…

Spring Boot集成vaadin快速入门demo

1.什么是vaadin&#xff1f; Vaadin 是用于构建单页 Web 应用的流行 Java 框架。 它由一家专门从事富 Internet 应用设计和开发的芬兰公司开发。 估计有 15 万开发者使用 Vaadin。 它的开发始于 2002 年。 Vaadin 框架特性 以下是 Vaadin 特性的列表&#xff1a; 这是一个 J…

这周,接连两位程序员猝死...

这周接连发生了两起不幸的事。俩位程序员去世的消息&#xff0c;深感悲伤和惋惜。 6月17号下午&#xff0c;一位负责研发的女员工在虾皮研发中心办公室猝死&#xff0c;年仅 30 岁。 官方通告&#xff1a; 同一天&#xff0c;另一位科大讯飞的高级测试工程师在家突发不适离世…

修改文件的权限(linux篇)

1.在yl用户下创建一个demo.txt文件 [rootlocalhost ~]# su yl [yllocalhost root]$ cd [yllocalhost ~]$ cd Desktop/ [yllocalhost Desktop]$ ls [yllocalhost Desktop]$ vim demo.txt 填入一些信息进行保存 2.查看文件信息以及所对应的组 [yllocalhost Desktop]$ ll 总用量…

一颗B+树可以存储多少数据?

一、前言 这个问题&#xff0c;非常经典&#xff0c;考察的点很多&#xff1a; 比如&#xff1a; 1、操作系统存储的单元&#xff0c;毕竟mysql也是运行在操作系统之上的应用。 2、B树是针对Mysql的InnoDB存储引擎&#xff0c;所以要理解InnoDb的最小存储单元&#xff0c;页&…

解两道四年级奥数题(等差数列)玩玩

1、1&#xff5e;200这200个连续自然数的全部数字之和是________。 2、2&#xff0c;4&#xff0c;6&#xff0c;……&#xff0c;2008这些偶数的所有各位数字之和是________。 这两道题算易错吧&#xff0c;这里求数字之和&#xff0c;比如124这个数的全部数字之和是1247。 …

【yolov8语义分割】跑通:下载yolov8+预测图片+预测视频

1、下载yolov8到autodl上 git clone https://github.com/ultralytics/ultralytics 下载到Yolov8文件夹下面 另外&#xff1a;现在yolov8支持像包一样导入&#xff0c;pip install就可以 2、yolov8 语义分割文档 看官方文档&#xff1a;主页 -Ultralytics YOLO 文档 还能切…

使用 DISPATCHERS 进行 Blueprint 之间的通信

文章目录 初始准备DISPATCHERS 的创建和绑定实现效果 初始准备 首先 UE5 默认是不提供 静态网格体编辑器也就是 Modeling Mode 的&#xff0c;这里需要从插件中添加 Modeling Tools Editor Mode 进入 Modeling Mode 模式&#xff0c;创建一个正方体 然后利用 PolyGroup Edit 和…

告别手抖尴尬!教你轻松缓解手部震颤的小秘诀!

在我们的日常生活中&#xff0c;手抖这个现象可能并不罕见。不论是因为紧张、疲劳还是某些健康问题&#xff0c;手抖都会给我们的生活带来诸多不便。今天&#xff0c;就让我们一起探讨如何缓解手部震颤&#xff0c;让你告别手抖的尴尬&#xff01; 一、手抖的成因及影响 手抖&…