为什么程序员一定要写单元测试?

大家好,我是鱼皮,很多初学编程的同学都会认为 “程序员的工作只有开发新功能,功能做完了就完事儿”。但其实不然,保证程序的正常运行、提高程序的稳定性和质量也是程序员的核心工作。

之前给大家分享过企业项目的完整开发流程,其中有一个关键步骤叫 “单元测试”,这篇文章就来聊聊程序员如何编写单元测试吧。

什么是单元测试?

单元测试(Unit Testing,简称 UT)是软件测试的一种,通常由开发者编写测试代码并运行。相比于其他的测试类型(比如系统测试、验收测试),它关注的是软件的 最小 可测试单元。

什么意思呢?

假如我们要实现用户注册功能,可能包含很多个子步骤,比如:

  1. 校验用户输入是否合法
  2. 校验用户是否已注册
  3. 向数据库中添加新用户

其中,每个子步骤可能都是一个小方法。如果我们要保证用户注册功能的正确可用,那么就不能只测试注册成功的情况,而是要尽量将每个子步骤都覆盖到,分别针对每个小方法做测试。比如输入各种不同的账号密码组合来验证 “校验用户输入是否合法” 这一步骤在成功和失败时的表现是否符合预期。

同理,如果我们要开发一个很复杂的系统,可能包含很多小功能,每个小功能都是一个单独的类,我们也需要针对每个类编写单元测试。因为只有保证每个小功能都是正确的,整个复杂的系统才能正确运行。

单元测试的几个核心要点是:

  1. 最小化测试范围:单元测试通常只测试代码的一个非常小的部分,以确保测试的简单和准确。
  2. 自动化:单元测试应该是自动化的,开发人员可以随时运行它们来验证代码的正确性,特别是在修改代码后。而不是每次都需要人工去检查。
  3. 快速执行:每个单元测试的执行时间不能过长,应该尽量做到轻量、有利于频繁执行。
  4. 独立性:每个单元测试应该独立于其他测试,不依赖于外部系统或状态,以确保测试的可靠性和可重复性。

为什么需要单元测试?

通过编写和运行单元测试,开发者能够快速验证代码的各个部分是否按照预期工作,有利于保证系统功能的正确可用,这是单元测试的核心作用。

此外,单元测试还有很多好处,比如:

1)改进代码:编写单元测试的过程中,开发者能够再次审视业务流程和功能的实现,更容易发现一些代码上的问题。比如将复杂的模块进一步拆解为可测试的单元。

2)利于重构:如果已经编写了一套可自动执行的单元测试代码,那么每次修改代码或重构后,只需要再自动执行一遍单元测试,就知道修改是否正确了,能够大幅提高效率和项目稳定性。

3)文档沉淀:编写详细的单元测试本身也可以作为一种文档,说明代码的预期行为。

鱼皮以自己的一个实际开发工作来举例单元测试的重要性。我曾经编写过一个 SQL 语法解析模块,需要将 10000 多条链式调用的语法转换成标准的 SQL 语句。但由于细节很多,每次改进算法后,我都不能保证转换 100% 正确,总会人工发现那么几个错误。所以我编写了一个单元测试来自动验证解析是否正确,每次改完代码后执行一次,就知道解析是否完全成功了。大幅提高效率。

所以无论是后端还是前端程序员,都建议把编写单元测试当做一种习惯,真的能够有效提升自己的编码质量。

如何编写单元测试?

以 Java 开发为例,我们来学习如何编写单元测试。

Java 开发中,最流行的单元测试框架当属 JUnit 了,它提供了一系列的类和方法,可以帮助我们快速检验代码的行为。

1、引入 JUnit

首先我们要在项目中引入 JUnit,演示 2 种方式:

Maven 项目引入

在 pom.xml 文件中引入 JUnit 4 的依赖:

<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope>
</dependency>
Spring Boot 项目引入

如果在 Spring Boot 中使用 JUnit 单元测试,直接引入 spring-boot-starter-test 包即可:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>

然后会自动引入 JUnit Jupiter,它是 JUnit 5(新版本)的一部分,提供了全新的编写和执行单元测试的方式,更灵活易用。不过学习成本极低,会用 JUnit 4,基本就会用 JUnit Jupiter。

2、编写单元测试

编写一个单元测试通常包括三个步骤:准备测试数据、执行要测试的代码、验证结果。

一般来说,每个类对应一个单元测试类,每个方法对应一个单元测试方法。

编写 JUnit 单元测试

比如我们要测试一个计算器的求和功能,示例代码如下:

import org.junit.Test;
import org.junit.Assert;public class CalculatorTest {// 通过 Test 注解标识测试方法@Testpublic void testAdd() {// 准备测试数据long a = 2;long b = 3;// 执行要测试的代码Calculator calculator = new Calculator();int result = calculator.add(2, 3);// 验证结果Assert.assertEquals(5, result);}
}

上述代码中的 Assert 类是关键,提供了很多断言方法,比如 assertEquals(是否相等)、assertNull(是否为空)等,用来对比程序实际输出的值和我们预期的值是否一致。

如果结果正确,会看到如下输出:

如果结果错误,输出如下,能够清晰地看到执行结果的差异:

Spring Boot 项目单测

如果是 Spring Boot 项目,我们经常需要对 Mapper 和 Service Bean 进行测试,则需要使用 @SpringBootTest 注解来标识单元测试类,以开启对依赖注入的支持。

以测试用户注册功能为例,示例代码如下:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;@SpringBootTest
public class UserServiceTest {@Resourceprivate UserService userService;@Testvoid userRegister() {// 准备数据String userAccount = "yupi";String userPassword = "";String checkPassword = "123456";// 执行测试long result = userService.userRegister(userAccount, userPassword, checkPassword);// 验证结果Assertions.assertEquals(-1, result);// 再准备一组数据,重复测试流程userAccount = "yu";result = userService.userRegister(userAccount, userPassword, checkPassword);Assertions.assertEquals(-1, result);}
}

3、生成测试报告

如果系统的单元测试数量非常多(比如 1000 个),那么只验证某个单元测试用例是否正确、查看单个结果是不够的,我们需要一份全面完整的单元测试报告,便于查看单元测试覆盖度、评估测试效果和定位问题。

测试覆盖度 是衡量测试过程中被测试到的代码量的一个指标,一般情况下越高越好。测试覆盖度 100% 表示整个系统中所有的方法和关键语句都被测试到了。

下面推荐 2 种生成单元测试报告的方法。

使用 IDEA 生成单测报告

直接在 IDEA 开发工具中选择 Run xxx with Coverage 执行单元测试类:

然后就能看到测试覆盖度报告了,如下图:

显然 Main 方法没有被测试到,所以显示 0%。

除了在开发工具中查看测试报告外,还可以导出报告为 HTML 文档:

导出后,会得到一个 HTML 静态文件目录,打开 index.html 就能在浏览器中查看更详细的单元测试报告了:

这种方式简单灵活,不用安装任何插件,比较推荐大家日常学习使用。

使用 jacoco 生成单测报告

JaCoCo 是一个常用的 Java 代码覆盖度工具,能够自动根据单元测试执行结果生成详细的单测报告。

它的用法也很简单,推荐按照官方文档中的步骤使用。

官方文档指路:https://www.eclemma.org/jacoco/trunk/doc/maven.html

首先在 Maven 的 pom.xml 文件中引入:

<plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.11</version>
</plugin>

当然,只引入 JaCoCo 插件还是不够的,我们通常希望在执行单元测试后生成报告,所以还要增加 executions 执行配置,示例代码如下:

<plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.11</version><configuration><includes><include>com/**/*</include></includes></configuration><executions><execution><id>pre-test</id><goals><goal>prepare-agent</goal></goals></execution><execution><id>post-test</id><phase>test</phase><goals><goal>report</goal></goals></execution></executions>
</plugin>

然后执行 Maven 的 test 命令进行单元测试:

测试结束后,就能够在 target 目录中,看到生成的 JaCoCo 单元测试报告网站了:

打开网站的 index.html 文件,就能看到具体的测试报告结果,非常清晰:

通常这种方式会更适用于企业中配置流水线来自动化生成测试报告的场景。

实践

编程导航星球的用户中心项目详细讲解了如何使用 JUnit 编写规范的单元测试。

👉🏻 编程导航原创项目教程系列:https://yuyuanweb.feishu.cn/wiki/SePYwTc9tipQiCktw7Uc7kujnCd

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

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

相关文章

Visual Studio Code安装和设置中文

文章目录 Visual Studio Code安装Visual Studio Code设置中文 步骤如下: Visual Studio Code安装 1.下载安装包 VS Code的官网 下载链接中的“az764295.vo.msecnd.net” 替换为国内镜像地址“vscode.cdn.azure.cn”&#xff0c;下载速度直接飙升至几十 Mb/s。(在官网下载速度…

reticulate | R-python调用 | 安装及配置 | conda文件配置

reticulate | R-python安装及配置 | conda文件配置 1. 基础知识2. 安装reticulate from CRAN3. 包含了用于Python和R之间协同操作的全套工具&#xff0c;在R和Rstudio中均可使用4. 配置python环境4.1 4种环境配置方式4.2 miniconda 环境install_miniconda()报错一install_minic…

大模型时代的机器人研究

机器人研究的一个长期目标是开发能够在物理上不同的环境中执行无数任务的“多面手”机器人。对语言和视觉领域而言&#xff0c;大量的原始数据可以训练这些模型&#xff0c;而且有虚拟应用程序可用于应用这些模型。与上述两个领域不同&#xff0c;机器人技术由于被锚定在物理世…

【C++11】shared_ptr智能指针使用详解

系列文章目录 【C11】智能指针与动态内存 文章目录 系列文章目录简介一、头文件二、初始化1. make_shared2. 拷贝和赋值 三、实例 简介 shared_ptr 是一个类的模板&#xff0c;它使用引用计数的方式来管理一个动态分配的内存资源。shared_ptr 需要一个动态分配的对象时&#…

专访|OpenTiny 社区 Mr 栋:结合兴趣,明确定位,在开源中给自己一些技术性挑战

前言 OpenTiny 开源之夏项目终于迎来了圆满的结局。借此机会&#xff0c;我们采访了 TinyReact 的共建者 Mr 栋同学。 Mr 栋同学是一位热衷于前端技术的开发者&#xff0c;对前端开发充满了激情和热爱。同时他也是一位即将毕业的大四在校生。在 OpenTiny 开源项目中&#xff0…

【Android】画面卡顿优化列表流畅度四之Glide几个常用参数设置

好像是一年前快两年了&#xff0c;笔者解析过glide的源码&#xff0c;也是因为觉得自己熟悉一些&#xff0c;也就没太关注过项目里glide的具体使用对当前业务的影响&#xff1b;主要是自负&#xff0c;还有就是真没有碰到过这样的数据加载情况。暴露了经验还是不太足够 有兴趣的…

从理论到实践:深度解读BIO、NIO、AIO的优缺点及使用场景

文章目录 BIO优缺点示例代码 NIO优缺点示例代码 AIO优缺点示例代码 总结 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 BIO、NIO和AIO是Java编程语言中用于处理输入输出&#xff08;IO…

编程的简单实例,编程零基础入门教程,中文编程开发语言工具下载

编程的简单实例&#xff0c;编程零基础入门教程&#xff0c;中文编程开发语言工具下载 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#xff0c;而且可以开发大型的软件&…

写的代理对象注入Spring的小基类,用于注入远程接口代理对象,算是对上篇文章的一个小改进

整来整去有点像一个微型openFeign&#xff0c;虽然简陋了点&#xff0c;但在改善代码质量和提高代码复用、可靠性、功能定制方面有着不错的效果。 public abstract class RemotingBean<T> implements ResourceLoaderAware, FactoryBean<T> {private ResourceLoade…

登录框渗透思路总结

0x00 前言 最近遇到几个靶机都是登录框&#xff0c;简单写下我的渗透思路吧~~ 0x01 渗透思路 1、用户名枚举爆破 用户名的查找&#xff0c;像工号可以尝试通过互联网搜索引擎去收集 爆破&#xff1a;burp字典 2、万能密码 测试方法&#xff1a; (1)用户名输入&#xff…

飞天使-django创建一个初始项目过程

创建django项目 运行项目 运行命令 pyhont manage.py runserver 然后访问 http://127.0.0.1:8000/&#xff0c; 则可以打开本地新建的项目 虚拟环境的部署-mac 在一台计算机上可以通过虚拟环境实现多个版本Django的开发环境 安装虚拟环境工具&#xff1a;如果你的系统中没有安…

探索人工智能领域——每日30个名词详解【day3】

目录 前言 正文 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x1f4e3;如需转载&#xff0c;请事先与我联系以…

Skywalking流程分析_7(构造/实例方法的增强流程)

前言 this.enhance protected DynamicType.Builder<?> enhance(TypeDescription typeDescription, DynamicType.Builder<?> newClassBuilder,ClassLoader classLoader, EnhanceContext context) throws PluginException {//静态方法的增强newClassBuilder thi…

QT使用Socket与安卓Socket互发消息

背景:安卓设备通过usb网络共享给Linux,此时安卓设备与linux处于同一网络环境,符合使用socket的条件,linux做客户端,安卓做服务端 1.QT使用Socket (1).在工程文件中加入 QT network (2).导包以及写一些槽函数用做数据传输与状态接收 #ifndef MAINWINDOW_H #define MAINWINDOW…

【算法】算法题-20231116

这里写目录标题 一、合并两个有序数组&#xff08;力扣88 &#xff09;二、剑指 Offer 39. 数组中出现次数超过一半的数字三、移除元素&#xff08;力扣27&#xff09;四、找出字符串中第一个匹配项的下标&#xff08;28&#xff09; 一、合并两个有序数组&#xff08;力扣88 &…

在 HarmonyOS 上实现 ArkTS 与 H5 的交互

介绍 本篇 Codelab 主要介绍 H5 如何调用原生侧相关功能&#xff0c;并在回调中获取执行结果。以“获取通讯录”为示例分步讲解 JSBridge 桥接的实现。 相关概念 Web组件&#xff1a;提供具有网页显示能力的 Web 组件。 ohos.web.webview&#xff1a;提供 web 控制能力。 …

洋葱架构、三层架构及两者区别

前言 洋葱架构它的名称来源于洋葱的层次结构&#xff0c;即软件代码的各层次之间的关系。在这种架构中&#xff0c;应用程序的各个组件通过一系列层次结构被逐层包裹在一起&#xff0c;形成一个类似于洋葱的结构。 一、经典三层架构 三层架构是一种软件设计模式&#xff0c;…

【机器学习5】无监督学习聚类

相比于监督学习&#xff0c; 非监督学习的输入数据没有标签信息&#xff0c; 需要通过算法模型来挖掘数据内在的结构和模式。 非监督学习主要包含两大类学习方法&#xff1a; 数据聚类和特征变量关联。 1 K均值聚类及优化及改进模型 1.1 K-means 聚类是在事先并不知道任何样…

合肥中科深谷嵌入式项目实战——基于ARM语音识别的智能家居系统(二)

目录 基于ARM语音识别的智能家居系统 练习一 一、程序编译 练习二&#xff1a; 二、文件IO 三、文件IO常用API接口函数 1、打开文件 open&#xff08;&#xff09; 2、将数据内容写入文件 write&#xff08;&#xff09; 3、关闭&#xff08;保存&#xff09;文件 四、…

WPF中Dispatcher对象的用途是什么

在WPF (Windows Presentation Foundation) 中&#xff0c;Dispatcher 对象的主要用途是提供一个与UI线程关联的消息循环系统&#xff0c;这允许开发者在UI线程上安排和执行任务。由于WPF的UI元素不是线程安全的&#xff0c;因此任何对UI元素的访问都必须从创建该元素的线程&…