SpringBoot测试实践

测试按照粒度可分为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.mfbz.cn/a/733038.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux驱动开发笔记(十三)Sysfs文件系统

文章目录 前言一、Sysfs1.1 Sysfs的引入1.2 Sysfs的目录结构1.2 Sysfs的目录详解1.2.1 devices1.2.2 bus1.2.3 class1.2.4 devices、bus、class目录之间的关系1.2.5 其他子目录 二、Sysfs使用2.1 核心数据结构2.2 相关函数2.2.1 kobject_create_and_add2.2.2 kobject_put()2.2.…

视觉理解与图片问答,学习如何使用 GPT-4o (GPT-4 Omni) 来理解图像

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、引言 OpenAI 最新发布的 GPT-4 Omni 模型&#xff0c;也被称为 GPT-4o&#xff0c;是一个多模态 AI 模型&#xff0c;旨在提供更加自然和全面的人机交互体验。 GPT-4o 与 GPT-4 Turbo 都具备视觉功…

MySQL程序使用的选项文件

MySQL程序使用的选项文件如下&#xff1a; 显示帮助消息并退出。 在具有多个网络接口的计算机上&#xff0c;使用此选项可以选择用于连接MySQL服务器的接口。 安装字符集的目录。 如果可能&#xff0c;压缩客户端和服务器之间发送的所有信息。 从MySQL 8.0.18开始&#xff0c;…

【因果推断python】50_去偏/正交机器学习2

目录 Frisch-Waugh-Lovell on Steroids CATE Estimation with Double-ML Frisch-Waugh-Lovell on Steroids 双重/偏差 ML 其思想非常简单&#xff1a;在构建结果和治疗残差时使用 ML 模型&#xff1a; 是估计&#xff0c;是估计 我们的想法是&#xff0c;ML 模型具有超强的…

【RK3588/算能/Nvidia智能盒子】AI算法应用于中国生物疫苗生产过程智能监测,赋能生产安全,提升品质管控

因操作失误导致食品药品质量事故频发 计算机视觉检测技术为监管提供新思路 近年来&#xff0c;各类因人员操作失误导致的食品药品质量事故不断发生。例如有员工取出原材料及称重确认时未进行双人复核导致“混药”、员工未能按照生产步骤对生牛奶进行杀菌导致奶酪污染、员工误将…

webpack5入门,根据官方文档简单学习,简单总结

c.**loader加载器&#xff1a;**webpack 只能理解 JS文件和 JSON 文件&#xff0c;loader 让 webpack 能够去处理其他类型的文件&#xff0c;并将它们转换为有效 模块&#xff0c;以供应用程序使用&#xff0c;以及被添加到依赖图中。&#xff08;比如css&#xff0c;less&…

人人讲视频如何下载

一、工具准备 1.VLC media player 2.谷歌浏览器 二、视频下载 1.打开人人讲网页&#xff0c;需要下载的视频 谷歌浏览器打开调试窗口 搜索m3u8链接 拷贝到VLCplayer打开网络串流方式打开测试是否能正常播放 2.下载视频 能正常播放后&#xff0c;切换播放为转换选择mp4格式…

分享excel全套教程速成,高效人士的Excel必修课,附视频课程!

我是阿星。今天&#xff0c;我要来聊聊那些让Excel变得像魔法一样的课程&#xff0c;它们能让你们在办公室里像超人一样高效。别急&#xff0c;听我慢慢道来。 首先&#xff0c;得说说这些课程&#xff0c;它们都是mp4格式&#xff0c;就像电影一样&#xff0c;但比电影实用多…

Python一文轻松搞定正则匹配

一、前言 日常工作中&#xff0c;不可避免需要进行文件及内容的查找&#xff0c;替换操作&#xff0c;python的正则匹配无疑是专门针对改场景而出现的&#xff0c;灵活地运用可以极大地提高效率&#xff0c;下图是本文内容概览。 ​ 二、正则表达式符号 对于所有的正则匹配表达…

强化学习中的自我博弈(self-play)

自我博弈&#xff08;Self-Play&#xff09;[1]是应用于智能体于智能体之间处于对抗关系的训练方法&#xff0c;这里的对抗关系指的是一方的奖励上升必然导致另一方的奖励下降。通过轮流训练双方的智能体就能使得双方的策略模型的性能得到显著提升&#xff0c;使得整个对抗系统…

动态规划02(Leetcode62、63、343、96)

参考资料&#xff1a; https://programmercarl.com/0062.%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.html 62. 不同路径 题目描述&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移…

STM32之二:时钟树

目录 1. 时钟 2. STM3时钟源&#xff08;哪些可以作为时钟信号&#xff09; 2.1 HSE时钟 2.1.1 高速外部时钟信号&#xff08;HSE&#xff09;来源 2.1.2 HSE外部晶体电路配置 2.2 HSI时钟 2.3 PLL时钟 2.4 LSE时钟 2.5 LSI时钟 3. STM32时钟&#xff08;哪些系统使用时…

html做一个分组散点图图的软件

在HTML中创建一个分组散点图&#xff0c;可以结合JavaScript库如D3.js或Plotly.js来实现。这些库提供了强大的数据可视化功能&#xff0c;易于集成和使用。下面是一个使用Plotly.js创建分组散点图的示例&#xff1a; 要添加文件上传功能&#xff0c;可以让用户上传包含数据的文…

使用 Python 进行测试(6)Fake it...

总结 如果我有: # my_life_work.py def transform(param):return param * 2def check(param):return "bad" not in paramdef calculate(param):return len(param)def main(param, option):if option:param transform(param)if not check(param):raise ValueError(…

matlab入门基础笔记

1、绘制简单三角函数&#xff1a; 绘制正弦曲线和余弦曲线。x[0:0.5:360]*pi/180; plot(x,sin(x),x,cos(x)); &#xff08;1&#xff09;明确x轴与y轴变量&#xff1a; 要求为绘制三角函数&#xff1a; X轴&#xff1a;角度对应的弧度数组 Y轴&#xff1a;对应sin(x)的值 求…

python pynput实现鼠标点击两坐标生成截图

脚本主要实现以下功能&#xff1a; 按ctrl开始截图&#xff0c;点击两个坐标&#xff0c;保存截图tk输出截图文本信息&#xff0c;文本输出内容倒序处理默认命名为A0自增。支持自定义名称&#xff0c;自增编号&#xff0c;修改自定义名称自增重新计算清空文本框内容 from pyn…

C++ (week8):数据库

文章目录 一、数据库简介1.数据库2.MySQL(1)数据库的结构(2)MySQL的三种使用方式(3)命令行(4)Navicat Premium 二、SQL1.SQL (Structured Query Language)&#xff0c;即结构化查询语言2.数据定义语言 DDL (Data Definition Language) &#xff0c;创建、修改、删除数据库、表结…

Leetcode3184. 构成整天的下标对数目 I

Every day a Leetcode 题目来源&#xff1a;3184. 构成整天的下标对数目 I 解法1&#xff1a;遍历 统计满足 i < j 且 hours[i] hours[j] 构成整天的下标对 i, j 的数目。 构成整天的条件&#xff1a;(hours[i] hours[j]) % 24 0。 代码&#xff1a; /** lc applee…

20分钟攻破DISCUZ论坛并盗取数据库(web安全白帽子)

20分钟攻破DISCUZ论坛并盗取数据库&#xff08;web安全白帽子&#xff09; 1 快速搭建discuz论坛1.1 攻击思路1.2 快速搭建实验环境1.2.1&#xff0c;漏洞概述1.2.2&#xff0c;在centos7虚拟机上搭建LAMP环境1.2.3&#xff0c;上传到discuz_X2_SC_UTF8.zip 到Linux系统/root下…

JAVA大型医院绩效考核系统源码:​医院绩效考核实施的难点痛点

JAVA大型医院绩效考核系统源码&#xff1a;​医院绩效考核实施的难点痛点 绩效考核数字化综合管理系统是一个基于数字化技术的管理平台&#xff0c;用于帮助企业、机构等组织进行绩效考评的各个环节的管理和处理。它将绩效考评的各个环节集成到一个系统中&#xff0c;包括目标…