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

如何进行单元测试

大话单测

代码写完了,是不是就万事大吉,只差上线了?我不知道大家有没有写单测的习惯,有的公司要求写,有的公司根本就不要求写。

写单元测试确实是一个体力活,一点都不比写代码轻松,所以很多程序员比较排斥单元测试,更不用说集成测试了。

如果不写单元测试,那代码的质量如何保证呢,咱不是有QA吗,靠QA呗,出了事儿算QA的,但研发一定是幸免于难的,必定这个事儿是你干的,至少有你一半的责任。

单元测试是质量的第一道把关,至关重要.

那什么样的单元测试是好的呢,干一个事儿肯定得有衡量的方法,我给你一一介绍。

环境搭建

单元测试还需要搭建环境吗?我们的脚手架不都已经搭建好了吗?如何搭建Spring Boot脚手架 - 掘金 (juejin.cn)但我想给你介绍一下另外一种好玩的方式。

在目前分布式的体系架构下,我们是不是要依赖很多中间件 ,比如Redis、DB、Kafka等,如果做单元测试的话,依赖的中间件怎么办呢?

使用dev环境的中间件不就可以了吗?但会遇到一个问题是,dev环境大家共用,万一不小心破坏了你的数据,不就game over了吗?总不能不让别人用吧。

除了环境的问题,还需要考虑每个case之间的数据是隔离的,这样才能保证case的准确性。

什么叫准确性呢,这个case执行1次和执行100次的结果是一样的,不会因着其他因素而改变,这也是科学中的可重复性。

在介绍脚手架的文章中,我们提到了内存版的Redis、kafka、Db, 接下来我们来看看怎么使用的,直接上代码

导入pom

<dependency> <groupId>it.ozimov</groupId> <artifactId>embedded-redis</artifactId> <version>0.7.2</version> <scope>test</scope> </dependency> <dependency> <groupId>ch.vorburger.mariaDB4j</groupId> <artifactId>mariaDB4j</artifactId> <version>2.4.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mariadb.jdbc</groupId> <artifactId>mariadb-java-client</artifactId> <version>2.5.2</version> <scope>test</scope> </dependency> <dependency> <artifactId>junit-platform-launcher</artifactId> <groupId>org.junit.platform</groupId> <scope>test</scope> </dependency> <dependency> <artifactId>junit-vintage-engine</artifactId> <groupId>org.junit.vintage</groupId> <version>5.9.0</verison> </dependency>

ApplicationTests 测试启动类 :该类主要职责是启动redis Server,DbServer.

@SpringBootTest(classes = {LifeCycleManagement.class, KafkaTemplateConfig.class}) @ActiveProfiles({"unit"}) @Slf4j @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public abstract class ApplicationTests { public static RedisServer redisServer; @BeforeAll public static void beforeAll() throws ManagedProcessException { log.info("================mariadb start================"); LifeCycleManagement.initDB(); log.info("================redis server start================"); redisServer = RedisServer.builder().setting("maxmemory 200m").port(8697).build(); redisServer.start(); } @AfterAll public static void afterAll() throws ManagedProcessException { log.info("================mariadb close================"); LifeCycleManagement.closeDB(); log.info("================redis server stop================"); redisServer.stop(); } }

LifeCycleManagement 该类职责是初始化DB库,启动kafka Server

@TestConfiguration @Slf4j public class LifeCycleManagement { private static final int NUMBER_OF_BROKERS = 1; public static DB db; @Value("${spring.cloud.sentinel.enabled}") private static boolean sentinel; public static void initDB() throws ManagedProcessException { DBConfigurationBuilder configBuilder = DBConfigurationBuilder.newBuilder(); configBuilder.setPort(3308); // OR, default: setPort(0); => autom. detect free port configBuilder.setDataDir("./data"); // just an example configBuilder.addArg(" --user=root"); db = DB.newEmbeddedDB(configBuilder.build()); db.start(); db.createDB("test"); db.source("script/source_table_str.sql", "test"); } public static void closeDB() throws ManagedProcessException { if (db != null) { db.stop(); } } public static int[] setupPorts() { return new int[NUMBER_OF_BROKERS]; } @Bean public EmbeddedKafkaBroker initKafka() { log.info("================kafka server start================"); boolean CONTROLLER_SHUTDOWN = true; int NUMBER_OF_PARTITIONS = 1; EmbeddedKafkaBroker embeddedKafkaBroker = new EmbeddedKafkaBroker(NUMBER_OF_BROKERS, CONTROLLER_SHUTDOWN, NUMBER_OF_PARTITIONS, new String[]{}) .kafkaPorts(setupPorts()).zkPort(0) .zkConnectionTimeout(EmbeddedKafkaBroker.DEFAULT_ZK_CONNECTION_TIMEOUT) .zkSessionTimeout(EmbeddedKafkaBroker.DEFAULT_ZK_SESSION_TIMEOUT); Properties properties = new Properties(); properties.put("listeners", "PLAINTEXT://127.0.0.1:9091"); properties.put("port", "9091"); properties.put("auto.create.topics.enable", true); embeddedKafkaBroker.brokerProperties((Map<String, String>) (Map<?, ?>) properties); return embeddedKafkaBroker; } }

BaseTest 该类主要初始化mockMvc

public abstract class BaseTest extends ApplicationTests { public static MockMvc mockMvc; @Autowired WebApplicationContext webApplicationContext; @AfterEach void afterEach() { } @BeforeEach public void beforeEach() throws ManagedProcessException { MiddleWareLifeCycleManagement.db.source("script/clean.sql", "test"); // 在这初始mock所有的过滤器都不会加载 mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) .addFilter(webApplicationContext.getBean(ContentCachingTestFilter.class)).build(); } protected ResultActions doAction(String content, String uri) throws Exception { return mockMvc .perform(MockMvcRequestBuilders.post(uri).contentType(MediaType.APPLICATION_JSON) .header("requestTime", System.nanoTime()).content(content)); } protected ResultActions doAction(MultiValueMap<String, String> map, String uri) throws Exception { return mockMvc.perform(MockMvcRequestBuilders.post(uri).params(map).header("bizId", UUID.randomUUID().toString().replace("-", ""))); } }

在你的工程中导入以下几个类,基本上就可以work了

单元测试如何写

直接看案例

@Test void should_return_expired_when_status_isCorrect() throws Exception { ReceiveAwardRequest request = givenStatusExpired(); //given assertResponseCodeEquals(request,ResultCode.EXPIRED); //when and then }

单元测试基本上遵循这样的结构体

given : 封装请求参数

when:执行请求

then:assert断言验证

方法名如何进行命名呢?这两种方式我在项目中都用了,但我更喜欢第二种方式。

  1. givenXXX_whenXXX_thenXXX, 从方法命名上一目了然
  2. should_XXX_when_XXX

说完了结构,接下来我给大家介绍一下不同类型的单元测试该如何写

1. 断言http接口返回值

spring的MockMvc可以帮我发起http 请求

get请求

mockMvc .perform(MockMvcRequestBuilders.get(uri).queryParams(map));

post请求

mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/receive") .contentType(MediaType.APPLICATION_JSON) .header("requestTime", System.nanoTime()) .content(content)) .andExpect(MockMvcResultMatchers.jsonPath("$.code").value(resultCode.getCode()));

如果你想mock第三方服务的返回值,可以这么做

Mockito.doReturn(false).when(xxx).xxxFunction(Mockito.any());

有时候我们需要mock spring中的bean,该bean仍然需要spring去管理,而不是mock,可以这样做

@SpyBean protected xxxService xxservice 或者 Mockito.spy(bean)

2. 断言数据库中的某个值

在有些情况下,我们验证该case的是否成功的条件是验证数据库中的某个值是不是改变了,比如验证一下奖券状态修改从a->b,判断该case是否成功就需要看数据库的值是否是b

Awaitility.await().pollDelay(100, TimeUnit.MILLISECONDS) .pollInterval(Duration.ofMillis(500)) .until(() -> eventService.lambdaQuery() .eq(MsgEvent::getRequestId, 112212) .eq(MsgEvent::getState, MsgEventState.FAILED.getCode()).exists());

3. 断言某个方法是否被执行到

Mockito.verify(xxxx, Mockito.never()).xxxFunction(Mockito.any());

4. 直接mock某个对象

Mockito.mock(Clazz class)

除此之外还可以断言某个日志关键字是否被打印等等

如何执行单元测试

执行一个单元测试,我们都知道,直接在单个case上右键点击run就行,如果要执行整个项目的单元测试该如何做呢?

配置完成之后,直接点击run。

执行单元测试必须要依赖于IDE吗,当然不是了,通过命令的方式也可以。

单测覆盖率

单元测试写完了,如何衡量单测写的好不好呢?单测覆盖率,我们一般使用分支覆盖率来衡量

什么是分支覆盖率呢?你理解成对if else的覆盖情况,如果只覆盖到了if,那覆盖率就是50%

单测覆盖率如何执行呢?

1、在pom.xml中添加jacoco插件

<plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.6</version> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>test</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin>

2、执行命令

mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent test surefire:test surefire-report:report -Dsurefire.reportsDirectory=./target/site --settings=D:/IdeaProjects/settings.xml

3、结果

每个包的分支覆盖率,指令覆盖率都赫然显示出来,点击包就能看到某个类的覆盖率以及代码执行情况

总结

单元测试需要花很长时间写,导致很多程序员不想写也不愿意写,但单元测试的收益是非常大的,在我们下次修改代码的时候,通过执行单元测试对软件进行第一道把关。除此之外,我们做重构也会比较放心,真的是 write once ,run any time。

最后:下方这份完整的软件测试 视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

​​​件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

http://www.jsqmd.com/news/700680/

相关文章:

  • 告别pip install报错:手把手教你修复Windows/macOS上的Python SSL证书验证问题
  • 加入真实细节和案例改写降AI怎么做?配合工具把AI率降到10%
  • 曙光超算实战:手把手教你用sbatch脚本在DCU队列上部署训练任务
  • 2026年现阶段,西安超市货架可靠供应商深度解析:甘肃三阳货架的综合实力与专业服务 - 2026年企业推荐榜
  • 3步搭建音乐聚合神器:music-api跨平台解析实战指南
  • 2026年近期江苏高速公路护栏采购:为何河北昊宇丝网制品有限公司是实力之选? - 2026年企业推荐榜
  • 【VSCode 2026远程同步终极指南】:3大底层协议重构+毫秒级差异检测,98.7%开发者尚未启用的隐藏同步加速模式
  • VSCode实时协作权限失控危机(2026 Beta用户实测:83%团队遭遇越权编辑),这份ACL策略清单请立刻保存
  • 嘎嘎降AI不达标退款怎么申请?完整流程手把手教你操作
  • WorkTool企业微信自动化:基于无障碍服务的智能机器人解决方案
  • 量子纠错解码器:BP算法与光束搜索技术解析
  • 2026年4月新发布:大城县新兴伟业防腐保温工程有限公司螺旋保温钢管实力解析 - 2026年企业推荐榜
  • LunaTranslator视觉小说翻译神器:打破语言壁垒的终极指南
  • C工程师年薪跃迁关键帧:掌握这11个C11/C17内存模型原子操作边界案例,直通华为/寒武纪安全岗终面
  • 面阵相机 vs 线阵相机:堡盟与Basler选型差异全解析 + Python实战演示
  • 2026年Q2成都蓄电池采购:权威厂家技术选型推荐 - 优质品牌商家
  • R语言在统计计算与数据分析中的核心优势与应用
  • 3个颠覆性体验:APKMirror客户端如何重新定义你的应用下载方式
  • 2026年微环滤波器测试仪厂家TOP5客观排行 - 优质品牌商家
  • UnityFigmaBridge解决方案:重塑设计开发协作的战略价值
  • [具身智能-446]:灰度图片是如何存储的?
  • 使用PINN替代牛顿-拉夫逊法求解TLM有限元传输线迭代方程并集成到C++工程
  • 2026年当下,天津汽车租赁服务深度**:天津鑫淼汽车租赁有限公司为何脱颖而出? - 2026年企业推荐榜
  • Keras图像处理全流程:从加载到保存的实战指南
  • CSS如何减少对HTML结构依赖_利用BEM命名保持样式的逻辑独立
  • 多语言跨境外贸商城系统源码|支持TK内嵌+独立站双模式|商家入驻+一键铺货提货|全开源可二次开发
  • 灵巧手抓取优势是什么?2026年专业正规的灵巧手生产厂商 - 品牌2026
  • Spring Boot + Configuration2 实现配置的实时双向更新
  • 量子-经典混合计算框架在PDE求解中的应用
  • 2026年4月河北地区花车订购攻略:专业厂家盘点与选择建议 - 2026年企业推荐榜