当前位置: 首页 > news >正文

单测的思路

文章目录

  • 单测的分类
  • 方法的单测
    • 生成工具的对比
    • 生成步骤
  • 接口的单测
    • mock步骤
    • 部分依赖mock的方式
  • 场景的单测
  • 参考

单测的分类

  • 单元测试(Unit Testing)是一种软件开发中的测试方法,它的主要目的是确保软件中的最小可测试单元(通常是函数、方法或类)在被单独测试和验证时能够按照预期工作。尽管单元测试有很多优点,如提高代码质量、减少Bug、简化调试过程等,但它也存在一些缺点:
    • 增加开发时间:如要求覆盖率到80%甚至90%,或者入参几十个难以构造,单测时间占比可能超过30%。
    • 需要维护:随着代码的改变,特别是大规模的重构,单元测试也需要相应地更新和维护,增加开发的负担。
    • 无法发现对其他类的影响:单元测试主要关注单个单元的行为,无法发现与多个单元交互或整个系统相关的问题。
  • 按照接口、场景维度调用对应的方法。
    • 优点有:
      • 减少开发时间:mock框架搭起来后只需要构造入参、和个别新增的下游依赖。
      • 维护成本低:接口内部的重构不会影响到单测的入参和出参。
      • 可以发现对其他类的影响:一次调用涉及到多个类,可以测出对其他类甚至其他接口的影响。
    • 缺点有:
      • 分支逻辑覆盖困难。如果接口涉及10个串联的类,每个类有2个分支,则需要构造1024个入参,远超方法维度单测的20个。
  • 所以最好能同时使用两种单测,做到优势互补。
    • 方法的单测:覆盖入参少、业务分支多的场景。
    • 接口、场景的单测:覆盖主干接口。

方法的单测

推荐用更智能的squaretest生成单测模板后,手工调整。

生成工具的对比

  • squaretest
    • 优点:
      • 生成测试用例相对智能,自动生成入参、覆盖部分if分支。
    • 缺点:
      • 只有30天的免费试用期,之后需要付费使用。事实上点掉reminder后可以继续使用。
  • TestMe
    • 优点:
      • 简单易用,适合初学者或小型项目使用。
    • 缺点:
      • 生成的测试用例不够全面或深入, 需要手动填充输入参数、写分支逻辑。
  • EvoSuite
    • 优点:
      • 作为Maven插件使用,方便集成到Java项目中。
    • 缺点:
      • 社区支持相对较少,遇到问题时可能难以得到及时帮助。
      • 配置和使用可能相对复杂,需要一定的学习成本,如需要引入groovy等外部依赖
  • diffblue
    • 优点:
      • 与IntelliJ IDEA集成良好,使用方便。
      • 支持多种编程语言和框架。
    • 缺点:
      • 商用版本收费较高,对于个人用户或小型团队可能不太友好。
      • 生成单测较慢,一个类近1Min。

生成步骤

  1. 安装插件
    在这里插入图片描述

  2. 引入依赖

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.1.1.RELEASE</version></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.2</version></dependency>
  1. 编写业务代码
@Service
public class TestServiceImpl implements TestService {@Resourceprivate TestRepository testRepository;@Resourceprivate TestThird testThird;@Overridepublic void start(InputDTO inputDTO) {if (testRepository.select(inputDTO.getId())==null) {testRepository.insert(new InputEntity());}testThird.callThird(new InputEntity());}
}
  1. 生成单测
    在这里插入图片描述在这里插入图片描述
  2. 单测生成结果
class TestServiceImplTestSquaretest {@Mockprivate TestRepository mockTestRepository;@Mockprivate TestThird mockTestThird;@InjectMocksprivate TestServiceImpl testServiceImplUnderTest;@BeforeEachvoid setUp() {initMocks(this);}@Testvoid testStart() {// Setupfinal InputDTO inputDTO = new InputDTO();inputDTO.setName("name");inputDTO.setId(0);final InputDetail inputDetail = new InputDetail();inputDetail.setName("name");inputDTO.setInputDetail(inputDetail);// Configure TestRepository.select(...).final InputEntity inputEntity = new InputEntity();inputEntity.setId(0);inputEntity.setName("name");when(mockTestRepository.select(0)).thenReturn(inputEntity);// Run the testtestServiceImplUnderTest.start(inputDTO);// Verify the resultsverify(mockTestThird).callThird(any(InputEntity.class));}@Testvoid testStart_TestRepositorySelectReturnsNull() {// Setupfinal InputDTO inputDTO = new InputDTO();inputDTO.setName("name");inputDTO.setId(0);final InputDetail inputDetail = new InputDetail();inputDetail.setName("name");inputDTO.setInputDetail(inputDetail);when(mockTestRepository.select(0)).thenReturn(null);// Run the testtestServiceImplUnderTest.start(inputDTO);// Verify the resultsverify(mockTestRepository).insert(any(InputEntity.class));verify(mockTestThird).callThird(any(InputEntity.class));}
}
class TestServiceImplTestTestMe {@MockTestRepository testRepository;@MockTestThird testThird;@InjectMocksTestServiceImpl testServiceImpl;@BeforeEachvoid setUp() {MockitoAnnotations.initMocks(this);}@Testvoid testStart() {when(testRepository.select(anyInt())).thenReturn(new InputEntity());when(testRepository.insert(any())).thenReturn(Integer.valueOf(0));testServiceImpl.start(new InputDTO());}
}

接口的单测

mock步骤

mock外部依赖,启动容器,调用接口

  1. 编写外部依赖的mock类
@Service
public class TestThirdImpl implements TestThird {@Overridepublic void callThird(InputEntity entity) {System.out.println("TestThirdImpl callThird");}
}
//mock
public class TestThirdMockImpl implements TestThird {public void callThird(InputEntity entity) {System.out.println("TestThirdMockImpl callThird");}
}
  1. 替换容器中的beanDefinition
@Configuration
public class MockConfig {@Beanpublic BeanDefinitionRegistryPostProcessor beanDefinitionRegistryPostProcessor() {return new BeanDefinitionRegistryPostProcessor() {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {//移除依赖的beanregistry.removeBeanDefinition("testThirdImpl");//获取Mockbean的定义BeanDefinition beanDe = BeanDefinitionBuilder.rootBeanDefinition(TestThirdMockImpl.class).getBeanDefinition();//注册mockbeanregistry.registerBeanDefinition("testThirdImpl", beanDe);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}};}
}
  1. test模块中启动容器,并调用入口方法
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class TestApplicationTest {@Resourceprivate TestService testService;@Testpublic void start() {testService.start(new InputDTO());}}

部分依赖mock的方式

  1. 数据库。用h2数据库mock。
public class MockDataSource extends HikariDataSource {public MockDataSource() {this.setDriverClassName("org.h2.Driver");this.setJdbcUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=MySQL;");this.setRegisterMbeans(true);this.setPoolName("mock");}
}

场景的单测

将接口单测组合

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class TestApplicationTest {@Resourceprivate TestService testService;@Testpublic void start() {testService.start(new InputDTO());testService.end(new InputDTO());}}

参考

  • 告别加班/解放双手提高单测覆盖率之Java 自动生成单测代码神器推荐
  • 单元测试 - 单元测试集成方案
  • JUnit 5 User Guide
  • 关于testNG和JUnit的对比
  • JUnit 5 单元测试教程
  • Junit5文档
  • 单元测试自动生成工具EvoSuite的简单使用
  • 使用BeanDefinitionRegistryPostProcessor动态注入BeanDefinition
  • 使用h2数据库支持单元测试
  • 使用H2数据库来模拟进行单元测试
  • H2官网
  • 关于springboot项目(@SpringBootTest单元测试类)找不到配置文件问题
  • Run Both JUnit 4 and Junit5 With Maven Surefire Plugin
  • 解决 Spring Boot 启动时 TypeNotPresentExceptionProxy / ArrayStoreException 异常
  • Maven的单元测试没有执行的问题

相关文章:

  • 【Git】Java 使用 JGit 创建 Git 代码仓库
  • 【电路笔记】-LR串联电路
  • 天锐绿盾 | 办公终端文件数据\资料防泄密软件
  • 使用消息中间件实现系统间的异步通信和解耦
  • HttpClient:HTTP GET请求的服务器响应输出
  • 「算法」滑动窗口
  • 入门者拿捏 Java 的必备小秘诀
  • Linux:docker在线仓库(docker hub 阿里云)基础操作
  • VMwareWorkstation17.0虚拟机安装搭建Windows 11虚拟机(完整图文详细步骤教程)
  • Python学习路线图
  • ⭐北邮复试刷题103. 二叉树的锯齿形层序遍历 (力扣每日一题)
  • 模拟发送 Ctrl+Alt+Del 快捷键
  • 【简洁的代码永远不会掩盖设计者的意图】如何写出规范整洁的代码
  • 什么是tomcat?tomcat是干什么用的?
  • [SWPUCTF 2021 新生赛]crypto8
  • Angular 4.x 动态创建组件
  • express如何解决request entity too large问题
  • HTTP那些事
  • LeetCode算法系列_0891_子序列宽度之和
  • scrapy学习之路4(itemloder的使用)
  • yii2中session跨域名的问题
  • 闭包,sync使用细节
  • 多线程事务回滚
  • 缓存与缓冲
  • 开发基于以太坊智能合约的DApp
  • 如何学习JavaEE,项目又该如何做?
  • 使用 QuickBI 搭建酷炫可视化分析
  • 掌握面试——弹出框的实现(一道题中包含布局/js设计模式)
  • 找一份好的前端工作,起点很重要
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • 宾利慕尚创始人典藏版国内首秀,2025年前实现全系车型电动化 | 2019上海车展 ...
  • 京东物流联手山西图灵打造智能供应链,让阅读更有趣 ...
  • # Pytorch 中可以直接调用的Loss Functions总结:
  • ###项目技术发展史
  • ${factoryList }后面有空格不影响
  • $L^p$ 调和函数恒为零
  • (12)Linux 常见的三种进程状态
  • (4) PIVOT 和 UPIVOT 的使用
  • (4.10~4.16)
  • (java)关于Thread的挂起和恢复
  • (八)五种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (利用IDEA+Maven)定制属于自己的jar包
  • (十五)devops持续集成开发——jenkins流水线构建策略配置及触发器的使用
  • (五)网络优化与超参数选择--九五小庞
  • (一)Java算法:二分查找
  • (一)UDP基本编程步骤
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • .bat批处理(七):PC端从手机内复制文件到本地
  • .bat批处理(一):@echo off
  • .Net MVC4 上传大文件,并保存表单
  • .NET 设计模式初探
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .NET高级面试指南专题十一【 设计模式介绍,为什么要用设计模式】
  • .NET与java的MVC模式(2):struts2核心工作流程与原理