0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一种极简单的SpringBoot单元测试方法

京东云 来源:jf_75140285 作者:jf_75140285 2025-03-11 15:39 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

前言

本文主要提供了一种单元测试方法,力求0基础人员可以从本文中受到启发,可以搭建一套好用的单元测试环境,并能切实的提高交付代码的质量。极简体现在除了POM依赖和单元测试类之外,其他什么都不需要引入,只需要一个本地能启动的springboot项目。

目录

1.POM依赖

2.单元测试类示例及注解释义

3.单元测试经验总结

一、POM依赖

Springboot版本: 2.6.6

< 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 >
  < version >3.12.4< /version >
< /dependency >


二、单元测试类示例

主要有两种

第一种,偏集成测试

需要启动项目,需要连接数据库、RPC注册中心

主要注解:@SpringBootTest + @RunWith(SpringRunner.class) + @Transactional + @Resource + @SpyBean + @Test

•@SpringBootTest + @RunWith(SpringRunner.class) 启动了一套springboot的测试环境;

•@Transactional 对于一些修改数据库的操作,会执行回滚,能测试执行sql,但是又不会真正的修改测试库的数据;

•@Resource 主要引入被测试的类

•@SpyBean springboot环境下mock依赖的bean,可以搭配Mockito.doAnswer(…).when(xxServiceImpl).xxMethod(any())mock特定方法的返回值;

•@Test 标识一个测试方法

TIP:对于打桩有这几个注解@Mock @Spy @MockBean @SpyBean,每一个都有其对应的搭配,简单说@Mock和@Spy要搭配@InjectMocks去使用,@MockBean和@SpyBean搭配@SpringBootTest + @RunWith(SpringRunner.class)使用,@InjectMocks不用启动应用,它启动了一个完全隔离的测试环境,无法使用spring提供的所有bean,所有的依赖都需要被mock

上代码:

/**
 * @author jiangbo8
 * @since 2024/4/24 9:52
 */
@Transactional
@SpringBootTest
@RunWith(SpringRunner.class)
public class SalesAmountPlanControllerAppTest {
    @Resource
    private SalesAmountPlanController salesAmountPlanController;
    @SpyBean
    private ISaleAmountHourHistoryService saleAmountHourHistoryServiceImpl;
    @SpyBean
    private ISaleAmountHourForecastService saleAmountHourForecastServiceImpl;
    @SpyBean
    private ISaleAmountHourPlanService saleAmountHourPlanServiceImpl;

    @Test
    public void testGraph1()  {
        // 不写mock就走实际调用

        SalesAmountDTO dto = new SalesAmountDTO();
        dto.setDeptId1List(Lists.newArrayList(35));
        dto.setDeptId2List(Lists.newArrayList(235));
        dto.setDeptId3List(Lists.newArrayList(100));
        dto.setYoyType(YoyTypeEnum.SOLAR.getCode());
        dto.setShowWeek(true);
        dto.setStartYm("2024-01");
        dto.setEndYm("2024-10");
        dto.setTimeDim(GraphTimeDimensionEnum.MONTH.getCode());
        dto.setDataType(SalesAmountDataTypeEnum.AMOUNT.getCode());
        Result< ChartData > result = salesAmountPlanController.graph(dto);
        System.out.println(JSON.toJSONString(result));
        Assert.assertNotNull(result);
    }

    @Test
    public void testGraph11()  {
        // mock就走mock
        Mockito.doAnswer(this::mockSaleAmountHourHistoryListQuery).when(saleAmountHourHistoryServiceImpl).listBySaleAmountQueryBo(any());
        Mockito.doAnswer(this::mockSaleAmountHourPlansListQuery).when(saleAmountHourPlanServiceImpl).listBySaleAmountQueryBo(any());
        Mockito.doAnswer(this::mockSaleAmountHourForecastListQuery).when(saleAmountHourForecastServiceImpl).listBySaleAmountQueryBo(any());

        SalesAmountDTO dto = new SalesAmountDTO();
        dto.setDeptId1List(Lists.newArrayList(111));
        dto.setDeptId2List(Lists.newArrayList(222));
        dto.setDeptId3List(Lists.newArrayList(333));
        dto.setYoyType(YoyTypeEnum.SOLAR.getCode());
        dto.setShowWeek(true);
        dto.setStartYm("2024-01");
        dto.setEndYm("2024-10");
        dto.setTimeDim(GraphTimeDimensionEnum.MONTH.getCode());
        dto.setDataType(SalesAmountDataTypeEnum.AMOUNT.getCode());
        Result< ChartData > result = salesAmountPlanController.graph(dto);
        System.out.println(JSON.toJSONString(result));
        Assert.assertNotNull(result);
    }
    
	private List< SaleAmountHourHistory > mockSaleAmountHourHistoryListQuery(org.mockito.invocation.InvocationOnMock s) {
        SaleAmountQueryBo queryBo = s.getArgument(0);
        if (queryBo.getGroupBy().contains("ymd")) {
            List< SaleAmountHourHistory > historyList = Lists.newArrayList();
            List< String > ymdList = DateUtil.rangeWithDay(DateUtil.parseFirstDayLocalDate(queryBo.getStartYm()), DateUtil.parseLastDayLocalDate(queryBo.getStartYm()));
            for (String ymd : ymdList) {
                SaleAmountHourHistory history = new SaleAmountHourHistory();
                history.setYear(Integer.parseInt(queryBo.getStartYm().split("-")[0]));
                history.setMonth(Integer.parseInt(queryBo.getStartYm().split("-")[1]));
                history.setYm(queryBo.getStartYm());
                history.setYmd(DateUtil.parseLocalDateByYmd(ymd));

                history.setAmount(new BigDecimal("1000"));
                history.setAmountSp(new BigDecimal("2000"));
                history.setAmountLunarSp(new BigDecimal("3000"));

                history.setSales(new BigDecimal("100"));
                history.setSalesSp(new BigDecimal("200"));
                history.setSalesLunarSp(new BigDecimal("300"));

                history.setCostPrice(new BigDecimal("100"));
                history.setCostPriceSp(new BigDecimal("100"));
                history.setCostPriceLunarSp(new BigDecimal("100"));
                historyList.add(history);
            }

            return historyList;
        }

        List< String > ymList = DateUtil.rangeWithMonth(DateUtil.parseFirstDayLocalDate(queryBo.getStartYm()), DateUtil.parseLastDayLocalDate(queryBo.getEndYm()));
        List< SaleAmountHourHistory > historyList = Lists.newArrayList();
        for (String ym : ymList) {
            SaleAmountHourHistory history = new SaleAmountHourHistory();
            history.setYear(Integer.parseInt(ym.split("-")[0]));
            history.setMonth(Integer.parseInt(ym.split("-")[1]));
            history.setYm(ym);

            history.setAmount(new BigDecimal("10000"));
            history.setAmountSp(new BigDecimal("20000"));
            history.setAmountLunarSp(new BigDecimal("30000"));

            history.setSales(new BigDecimal("1000"));
            history.setSalesSp(new BigDecimal("2000"));
            history.setSalesLunarSp(new BigDecimal("3000"));

            history.setCostPrice(new BigDecimal("100"));
            history.setCostPriceSp(new BigDecimal("100"));
            history.setCostPriceLunarSp(new BigDecimal("100"));
            historyList.add(history);
        }

        return historyList;
    } 
}

第二种,单元测试

不需要启动项目,也不会连接数据库、RPC注册中心等,但是相应的所有数据都需要打桩mock

这种方法可以使用testMe快速生成单元测试类的框架,具体方法见: 基于testMe快速生成单元测试类(框架)

主要注解:@InjectMocks + @Mock + @Test

•@InjectMocks标识了一个需要被测试的类,这个类中依赖的bean都需要被@Mock,并mock返回值,不然就会空指针

•@Mock mock依赖,具体mock数据还要搭配when(xxService.xxMethod(any())).thenReturn(new Object()); mock返回值

•@Test 标识一个测试方法

上代码:

/**
 * Created by jiangbo8 on 2022/10/17 15:02
 */
public class CheckAndFillProcessorTest {
    @Mock
    Logger log;
    @Mock
    OrderRelService orderRelService;
    @Mock
    VenderServiceSdk venderServiceSdk;
    @Mock
    AfsServiceSdk afsServiceSdk;
    @Mock
    PriceServiceSdk priceServiceSdk;
    @Mock
    ProductInfoSdk productInfoSdk;
    @Mock
    OrderMidServiceSdk orderMidServiceSdk;
    @Mock
    OrderQueueService orderQueueService;
    @Mock
    SendpayMarkService sendpayMarkService;
    @Mock
    TradeOrderService tradeOrderService;

    @InjectMocks
    CheckAndFillProcessor checkAndFillProcessor;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testProcess2() throws Exception {

        OrderRel orderRel = new OrderRel();
        //orderRel.setJdOrderId(2222222L);
        orderRel.setSopOrderId(1111111L);
        orderRel.setVenderId("123");

        when(orderRelService.queryOrderBySopOrderId(anyLong())).thenReturn(orderRel);

        OrderDetailRel orderDetailRel = new OrderDetailRel();
        orderDetailRel.setJdSkuId(1L);
        when(orderRelService.queryDetailList(any())).thenReturn(Collections.singletonList(orderDetailRel));

        Vender vender = new Vender();
        vender.setVenderId("123");
        vender.setOrgId(1);
        when(venderServiceSdk.queryVenderByVenderId(anyString())).thenReturn(vender);
        when(afsServiceSdk.queryAfsTypeByJdSkuAndVender(anyLong(), anyString())).thenReturn(0);
        when(priceServiceSdk.getJdToVenderPriceByPriorityAndSaleTime(anyString(), anyString(), any())).thenReturn(new BigDecimal("1"));
        when(productInfoSdk.getProductInfo(any())).thenReturn(new HashMap< Long, Map< String, String >>() {{
            put(1L, new HashMap< String, String >() {{
                put("String", "String");
            }});
        }});

        when(orderQueueService.updateQueueBySopOrderId(any())).thenReturn(true);

        Order sopOrder = new Order();
        sopOrder.setYn(1);
        when(orderMidServiceSdk.getOrderByIdFromMiddleWare(anyLong())).thenReturn(sopOrder);

        when(sendpayMarkService.isFreshOrder(anyLong(), anyString())).thenReturn(true);

        doNothing().when(tradeOrderService).fillOrderProduceTypeInfo(any(), anyInt(), any());
        doNothing().when(tradeOrderService).fillOrderFlowFlagInfo(any(), any(), anyInt(), any());

        Field field = ResourceContainer.class.getDeclaredField("allInPlateConfig");
        field.setAccessible(true);
        field.set("allInPlateConfig", new AllInPlateConfig());

        OrderQueue orderQueue = new OrderQueue();
        orderQueue.setSopOrderId(1111111L);
        DispatchResult result = checkAndFillProcessor.process(orderQueue);
        Assert.assertNotNull(result);
    }
}

三、单元测试经验总结

在工作中总结了一些单元测试的使用场景:

1.重构,如果我们拿到了一个代码,我们要去重构这个代码,如果这个代码本身的单元测试比较完善,那么我们重构完之后可以执行一下现有的单元测试,以保证重构前后代码在各个场景的逻辑保证最终一致,但是如果单元测试不完善甚至没有,那我建议大家可以基于AI去生成这个代码的单元测试,然后进行重构,再用生成的单元测试去把控质量,这里推荐Diffblue去生成,有兴趣的可以去了解一下。

2.新功能,新功能建议使用上面推荐的两种方法去做单测,第一种方法因为偏集成测试,单元测试代码编写的压力比较小,可以以黑盒测试的视角去覆盖测试case就可以了,但是如果某场景极为复杂,想要单独对某个复杂计算代码块进行专门的测试,那么可以使用第二种方法,第二种方法是很单纯的单元测试,聚焦专门代码块,但是如果普遍使用的话,单元测试代码编写量会很大,不建议单纯使用某一种,可以具体情况具体分析。

建议大家做单元测试不要单纯的追求行覆盖率,还是要本着提高质量的心态去做单元测试。

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 测试
    +关注

    关注

    8

    文章

    6029

    浏览量

    130720
  • 单元测试
    +关注

    关注

    0

    文章

    52

    浏览量

    3463
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    电源与时钟的单元测试方案解析

    单元测试的问题全部要提问题单跟踪解决,测出问题在记录在跟踪表的同时就马上提问题单(单元测试的问题单可以走短流程),不要积累到最后起提。
    发表于 03-03 14:45 1561次阅读

    MCU进行单元测试方法

    背景MCU软件不同于常规的PC机或基于SOC的嵌入式软件,其般情况下,与底层硬件耦合度高,资源有限,如何进行单元测试的问题困扰我很久。解决方案根据目前已知如下3类型的方案:在目标板上运行此方案下,在程序代码中加入
    发表于 11-01 06:58

    单元测试/集成测试自动化工具--WinAMS

    的对安全性要求极高的领域,单元测试已经成为不可缺少的部分。使用目标机代码进行单元测试也是为了符合汽车行业中ISO26262功能安全认证标准。产品特长全面支持嵌入式微机!验证嵌入式C/C++软件 实施以模块
    发表于 06-17 18:26

    如何提高嵌入式软件单元测试效率

    每个代码片段是否按预期工作。 如果运行回归测试套件太耗时: ·工程资源在等待测试完成时未被使用·代码质量因代码测试频率降低而降低·上市时间增加本指南介绍了一种使用虚拟平台的
    发表于 08-28 06:31

    系统测试单元测试、集成测试、验收测试、回归测试

    系统测试单元测试、集成测试、验收测试、回归测试 单元测试
    发表于 10-22 12:38 2040次阅读

    单元测试常用的方法

    单元测试,是指对软件中的最小可测试单元进行检查和验证。对于单元测试单元的含义,般来说,要根据
    发表于 12-21 10:17 4w次阅读
    <b class='flag-5'>单元测试</b>常用的<b class='flag-5'>方法</b>

    什么是单元测试_单元测试的目的是什么

    工厂在组装台电视机之前,会对每个元件都进行测试,这,就是单元测试单元测试是开发者编写的小段代码,用于检验被测代码的
    发表于 12-21 13:44 3.4w次阅读

    java单元测试的好处

    单元测试是编写测试代码,应该准确、快速地保证程序基本模块的正确性。好的单元测试的标准,JUnit是Java单元测试框架,已经在Eclipse中默认安装。许多开发者都有个习惯,常常不乐意
    发表于 12-21 14:24 4577次阅读

    java单元测试怎么写

    Java是门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。单元测试,是指对软件中的最小可测试
    发表于 12-21 14:54 9149次阅读
    java<b class='flag-5'>单元测试</b>怎么写

    什么是单元测试,为什么要做单元测试

    。 什么是单元测试单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。通常而言,
    的头像 发表于 04-28 17:21 1.1w次阅读

    MCU如何进行单元测试

    背景MCU软件不同于常规的PC机或基于SOC的嵌入式软件,其般情况下,与底层硬件耦合度高,资源有限,如何进行单元测试的问题困扰我很久。解决方案根据目前已知如下3类型的方案:在目标板上运行此方案下,在程序代码中加入
    发表于 10-26 10:06 35次下载
    MCU如何进行<b class='flag-5'>单元测试</b>

    RT-Thread上的单元测试:什么是单元测试单元测试的作用是什么?

    RT-Thread上的单元测试:什么是单元测试单元测试的作用是什么?           审核编辑:彭静
    的头像 发表于 05-27 16:06 2247次阅读
    RT-Thread上的<b class='flag-5'>单元测试</b>:什么是<b class='flag-5'>单元测试</b>?<b class='flag-5'>单元测试</b>的作用是什么?

    软件单元测试真的有必要吗?(上)

    本文着重探讨单元测试的重要性及其正面临的困境,并介绍功能安全标准中罗列的单元测试方法
    的头像 发表于 11-03 14:58 1509次阅读
    软件<b class='flag-5'>单元测试</b>真的有必要吗?(上)

    一种通用的汽车车身电子单元测试工装的研究设计

    电子发烧友网站提供《一种通用的汽车车身电子单元测试工装的研究设计.pdf》资料免费下载
    发表于 11-07 10:07 1次下载
    <b class='flag-5'>一种</b>通用的汽车车身电子<b class='flag-5'>单元测试</b>工装的研究设计

    边聊安全 | 软件单元测试的设计方法

    的设计是确保代码正确性和可靠性的关键步骤。在软件单元测试中,等价类测试一种很重要的测试设计方法,它通过将输入数据划分为若干个等价类,并从每
    的头像 发表于 09-05 16:18 4325次阅读
    边聊安全 | 软件<b class='flag-5'>单元测试</b>的设计<b class='flag-5'>方法</b>