软件测试技术之单元测试—工程师 Style 的测试方法
什么是单元测试?
Wikipedia 对单元测试的定义:
在计算机编程中,单元测试(Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。
在实际测试中,一个单元可以小到一个方法,也可以大到包含多个类。从定义上讲,单元测试和集成测试是有严格的区分的,但是在实际开发中它们可能并没有那么严格的界限。如果专门追求单元测试必须测试最小的单元,反而容易造成多余的测试并且不易维护。换句更严谨一点的说法,我们要考虑测试的场景再去选择不同粒度的测试。
单元测试和集成测试即可以手工执行,也可以是程序自动执行。但现在一般提到单元测试,都是指自动执行的测试。所以我们下面提到的单元测试,没有特别注明,都是泛指自动执行的单元测试或集成测试。
单元测试入门
下面我们先看两个案例,感受一下单元测试到底是什么样子的。
例子 1:生命游戏单元测试
我们先看一个很简单的例子,实现一个康威生命游戏。如果不了解康威生命游戏的话,可以看 Wikipedia 的介绍。假设我们实现时定义这样的接口:
public interface Game {
void init(int[][] shape) ; // 初始化游戏 board
void tick(); // 行进到在一个回合
int[][] get(); // 获取当前游戏 board
}
生命游戏有好几条规则,为了测试我们的实现是否正确,我们可以针对生命游戏的每个规则,写一个单元测试。下面测试的是复活的规则。
@Test
public void testRelive() {
int[][] shape = {{0, 0, 1}, {0, 0, 0}, {1, 0, 1}};
Game g = new GameImplSample(shape);
g.tick();
// 自己死亡,周围3个存活状态,复活
assertEquals(1, g.get()[1][1]);
}
例子 2:订单退款集成测试
我们在看一个稍微复杂一些的例子,测试的是订单退款的过程。
@Test
public void test300_doRefundItem() {
// 创建订单、支付,然后退款
Order order = createOrder(OrderSource.XR_DOCTOR);
order = fullPay(order, PayType.WECHAT_JS);
OrderItem item = _doItemRefund(order, 1, false);
// 检查退款中状态
OrderWhole orderWholeRefunding = findOrderWhole(order.getOrderNo());
isTrue(orderWholeRefunding.getRefundStatus().equals(
OrderRefundStatus.PARTIAL_REFUNDING));
isTrue(orderWholeRefunding.getRefunds().get(0).getStatus().equals(
RefundStatus.REFUNDING));
isTrue(orderWholeRefunding.getRefunds().get(0).getItemId().get().equals(
item.getId()));
// 构建退款的回调信息
List payments = findPayments(order.getId());
List refunds = findRefunds(order.getId());
wxRefundNotify(payments.get(0), refunds.get(0), WxRefundStatus.SUCCESS);
// 检查退款后状态
OrderWhole orderWholeFinish = assertRefund(order, FULL_PAID,
PARTIAL_REFUND_OK, RefundStatus.SUCCESS, RefundMode.ITEM, false);
isTrue(orderWholeFinish.getRefundFee() == item.getPaidPrice());
isTrue(orderWholeFinish.getIncomes().stream()
.filter(i -> i.getAmount() < 0).count() == 1);
}
单元测试执行
单元测试有很多种执行方式:
在 IDE 中执行
通过 mvn 或者 gradle 运行
在 CI 中执行
不论什么方式,单元测试都应该很容易就能运行,并给出一个测试结果。当然,单元测试运行速度得快,一般是在秒级的,太慢的话就不能及时获得反馈了。
为什么要写单元测试?
单元测试的好处
确保代码满足需求或者设计规格。 使用单元测试来测试代码,可以通过构造数据和前置条件,确保测试覆盖到需要测试的逻辑。而手工测试或 UI 测试则无法做到,并且往往更复杂。
快速定位并解决问题。 单元测试因为测试范围比较小,可以比较容易的定位到问题;而手工测试,常常需要耗费不少时间去定位问题。
确保代码永远满足需求规格。 一旦需要对实现进行修改,单元测试可以确保代码的正确性,极大的降低各种修改和重构的风险。特别是避免那些在意想不到之处出现的 BUG。
简化系统集成。 单元测试确保了系统或模块本身的正确性,集成时更不容易出错。
提高代码质量和可维护性。 不可测试的代码,其本身的抽象性、模块性、可维护性是有些问题的。例如不符合单一职责、接口隔离等设计原则,或者依赖了全局变量。可测试的代码,往往其质量相对会高一些。
提供文档和说明。 单元测试本身就是接口使用方法的很好的案例。
持续集成和持续交付
2010 年前后,大部分互联网公司的系统部署还是通过手工的方式进行的,往往要在半夜上线系统。但是之后持续集成、持续交付的理念不断推广,部署过程越来越灵活、顺畅。而单元测试则是持续集成和持续交付里重要的一环。
持续集成就是 Continuous Integration(CI),也就是指从开发上传代码、自动构建和测试、最后反馈结果的过程。
更进一步,如果自动构建和测试后,会自动发布到测试环境或预发布环境,执行更多测试(集成测试、自动化 UI 测试等),甚至最后直接发布,那这一过程就是持续交付(Continuous Delivery,CD)。业内有不少公司,比如亚马逊、Esty,可以做到每天几十甚至成百上千次生产环境部署,就是因为有比较完善的持续交付环境。
CI 已经是互联网行业必备标准,CD 也在互联网行业有了越来越多的实践,但是如果没有单元测试这一环节,CI 和 CD 的过程是有缺陷的。
怎么写单元测试?
JUnit 简介
基本上每种语言和框架都有不错的单元测试框架和工具,例如 Java 的 JUnit、Scala 的 ScalaTest、Python 的 unittest、JavaScript 的 Jest 等。上面的例子都是基于 JUnit 的,我们下面就简单介绍下 JUnit。
JUnit 里面每个 @Test 注解的方法,就是一个测试。@Ignore 可以忽略一个测试。@Before、@BeforeClass、@After、@AfterClass 可以在测试执行前后插入一些通用的操作,比如初始化和资源释放等等。
除了 assertEquals,JUnit 也支持不少其他的 assert 方法。例如 assertNull、assertArrayEquals、assertThrows、assertTimeout 等。另外也可以用第三方的 assert 库比如 Spring 的 Assert 或者 AssertJ。
除了可以测试普通的代码逻辑,JUnit 也可以进行异常测试和时间测试。异常测试是测试某段代码必须抛指定的异常,时间测试则是测试代码执行时间在一定范围内。
也可以对测试进行分组。例如可以分成 contractTest 、mockTest 和 unitTest,通过参数指定执行某个分组的测试。
这里就不做过多介绍了,想了解更多 JUnit 的可以去看 极客学院的 JUnit 教程 等资料。其他的单元测试框架,基本功能都是大同小异。
使用测试 Double
狭义的单元测试,我们是只测试单元本身。即使我们写的是广义的单元测试,它依然可能依赖其他模块,比如其他类的方法、第三方服务调用或者数据库查询等等,造成我们无法很方便的测试被测系统或模块。这时我们就需要使用测试 Double 了。
如果细究的话,测试 Double 分成好多种,比如什么 Dummies、Fakes 等等。但我认为我们只要弄清两类就可以了,也就是 Stub 和 Mock。
Stub
Stub 指那些包含了预定义好的数据并且在测试时返回给调用者的对象。Stub 常被用于我们不希望返回真实数据或者造成其他副作用的场景。
我们契约测试生成的、可以通过 spring cloud stubrunner 运行的 Stub Jar 就是一个 Stub。我们可以让 Stub 返回预设好的假数据,然后在单元测试里就可以依赖这些数据,对代码进行测试。例如,我们可以让用户查询 Stub 根据参数里的用户 ID 返回认证用户和未认证用户,然后我们就可以测试调用方在这两种情况下的处理逻辑了。
当然,Stub 也可以不是远程服务,而是另外一个类。所以我们经常说要针对接口编程,因为这样我们就可以很容易的创建一个接口的 Stub 实现,从而替换具体的类。
public class StubNameService implement NameService {
public String get(String userId) {
return ““Mock user name””;
}
}
public class UserServiceTest {
// UserService 依赖 NameService,会调用其 get 方法
@Inject
private UserService userService;
@Test
public void whenUserIdIsProvided_thenRetrievedNameIsCorrect() {
userService.setNameService(new StubNameService());
String testName = userService.getUserName(““SomeId””);
Assert.assertEquals(““Mock user name””, testName);
}
}
不过这样要实现很多 Stub 也是很麻烦的,现在我们已经不需要自己创建 Stub 了,因为有了各种 Mock 工具。
Mock
Mocks 指那些可以记录它们的调用信息的对象,在测试断言中我们可以验证 Mocks 被进行了符合期望的调用。
Mock 和 Stub 的区别在于,Stub 只是提供一些数据,它并不进行验证,或者只是基于状态做一些验证;而 Mock 除了可以做 Stub 的事情,也可以基于调用行为进行验证。比如说,Mock 可以验证 Mock 接口被调用了不多不少正好两次,并且调用的参数是期望的数值。
Java 里最常用的 Mock 工具就是 Mockito 了。我们来看一个简单的例子,下面的 UserService 依赖 NameService。当我们测试 UserService 的时候,我们希望隔离 NameService,那么就可以创建一个 Mock 的 NameService 注入到 UserService 中(在 Spring 里只需要用 @Mock 和 @InjectMocks 两个注解就可以完成了)
public class UserServiceTest {
@InjectMocks
private UserService userService;
@Mock
private NameService nameService;
@Test
public void whenUserIdIsProvided_thenRetrievedNameIsCorrect() {
Mockito.when(nameService.getUserName(““SomeId””)).thenReturn(““Mock user name””);
String testName = userService.getUserName(““SomeId””);
Assert.assertEquals(““Mock user name””, testName);
Mockito.verify(nameService).getUserName(““SomeId””);
}
}
注意上面最后一行,是验证 nameService 的 getUserName 被调用,并且参数为 ““SomeId””。
契约测试
契约测试会给每个服务生成一个 Stub,可以用于调用方的单元/集成测试。例如,我们需要测试预约服务的预约操作,而预约操作会调用用户服务,去验证用户的一些基本信息,比如医生是否认证等。
所以,我们可以通过传入不同的用户 ID,让契约 Stub 返回不同状态的用户数据,从而验证不同的处理流程。例如,正常的预约流程的测试用例可能是这样的。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest@AutoConfigureStubRunner(repositoryRoot=““http://<nexus_root>””,
ids = {"“com.xingren.service:user-client-stubs:1.0.0:stubs:6565"”})public class BookingTest {
// BookingService 会调用用户服务,获取医生认证状态后进行不同的处理
@Inject
private BookingService bookingService;
@Test
public void testBooking() {
BookingForm form = new BookingForm(
1,// doctorId
1// scheduleId
1001);// patientId
BookVO res = bookingService.book(form);
assertTrue(res.id > 0);
assertTrue(res.payStatus == PayStatus.UN_PAY);
}
}
注意上面的 AutoConfigureStubRunner 注解就是设置并启动了用户服务 Stub,当然在测试的时候,我们需要把服务调用接口的 baseUrl 设置为http://localhost:6565。关于契约测试的更多内容,请参考微服务环境下的集成测试探索一文。
TDD
简单说下 Test Driven Development,也就是 TDD。左耳朵耗子就写了一篇TDD并不是看上去的那么美,我就直接引用其介绍了。
其开发过程是从功能需求的test case开始,先添加一个test case,然后运行所有的test case看看有没有问题,再实现test case所要测试的功能,然后再运行test case,查看是否有case失败,然后重构代码,再重复以上步骤。
其实严格的 TDD 流程实用性并不高,左耳朵耗子本身也是持批判态度。但是对于接口定义比较明确的模块,先写单元测试再写实现代码还是有很大好处的。因为目标清晰,而且可以立刻得到反馈。
c
文章来源:网络 版权归原作者所有
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系小编,我们将立即处理
相关文章:
软件测试技术之单元测试—工程师 Style 的测试方法
什么是单元测试? Wikipedia 对单元测试的定义: 在计算机编程中,单元测试(Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。 在实际…...
C#学习....
1.基础 //引用命名空间using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;//项目名或者命名空间 namespace _01_MY_First_Demo {//Program类class Program{//程序的主入口或者Main函数static void Main(S…...
C语言暑假刷题冲刺篇——day2
目录 一、选择题 二、编程题 🎈个人主页:库库的里昂 🎐CSDN新晋作者 🎉欢迎 👍点赞✍评论⭐收藏✨收录专栏:C语言每日一练 ✨其他专栏:代码小游戏C语言初阶🤝希望作者的文章能对你…...
springcloud3 hystrix实现服务降级的案例配置2
一 服务降级的说明 1.1 服务降级说明 "服务器忙,请稍后在试"不让客户达等待,立即返回一个友好的提示。 1.2 服务降级的触发情况 1.程序运行异常; 2.超时; 3.服务熔断触发服务降级;4 .线程池/信号量打…...
第 3 章 稀疏数组和队列(1)
3.1 稀疏 sparsearray 数组 3.1.1先看一个实际的需求 编写的五子棋程序中,有存盘退出和续上盘的功能。 分析问题: 因为该二维数组的很多值是默认值 0.因此记录了很多没有意义的数据.->稀疏数组 3.1.2基本介绍 当一个数组中大部分元素为 0,或者为同…...
7-10 奇偶分家
分数 10 全屏浏览题目 切换布局 作者 陈越 单位 浙江大学 给定N个正整数,请统计奇数和偶数各有多少个? 输入格式: 输入第一行给出一个正整N(≤1000);第2行给出N个非负整数,以空格分隔。 输…...
使用词向量以数学方式查找具有相似含义的单词
摄影:Nika Charakova 一、说明 简而言之,词向量只不过是表示自然语言词含义的一系列实数。这项技术是有用的NLP功能的重要推动力,使机器能够“理解”人类语言。本文讨论如何使用词向量以编程方式计算文本的语义相似性,例如&#x…...
opencv实现以图搜图
这里写目录标题 1. 步骤1.1 导入OpenCV库:1.2 加载图像1.3 提取特征1.4 匹配特征1.5 显示结果 2. 完整代码3. 测试图片及效果 1. 步骤 1.1 导入OpenCV库: 在您的C代码中,首先需要导入OpenCV库。您可以使用以下语句导入核心模块:…...
爬虫工作中代理失效了怎么处理?
Hey!亲爱的爬虫小伙伴们,是不是经常在爬虫的工作中遇到代理IP失效的问题?别着急,今天我来分享一些应对代理失效的妙招!这些方法简单易行,让你爬虫顺利进行. 一、为什么代理会失效? 在爬虫过程…...
使用虚拟环境conda安装不同版本的cuda,cudnn,pytorch
背景:在学习深度学习时,我们不可避免的需要跑多个神经网络,而不同的神经网络环境都不一样,所以必须要使用到虚拟环境(如conda)去做环境隔离,安装属于自己的环境。在这环境中,大多神经网络都必须要用到cuda&…...
【24择校指南】华东师范大学计算机考研考情分析
华东师范大学(B) 考研难度(☆☆☆☆) 内容:23考情概况(拟录取和复试分数人数统计)、院校概况、23考试科目、23复试详情、各科目及专业考情分析。 正文2563字,预计阅读:3分钟。 2023考情概况…...
什么是LAXCUS分布式操作系统?
相较Linux、Windows,Laxcus是同时在多台计算机上运行的操作系统,处理大规模、高并发、高性能业务,其特点是资源共享和任务并行,并实现【数存算管】超融合一体化。环境中的资源:CPU、GPU、内存、硬盘、网络,…...
Redis数据结构——链表list
链表是一种常用的数据结构,提供了顺序访问的方式,而且高效地增删操作。 Redis中广泛使用了链表,例如:列表的底层实现之一就是链表。 在Redis中,链表分为两部分:链表信息 链表节点。 链表节点用来表示链表…...
[自学记录06|*百人计划]Gamma矫正与线性工作流
一、前言 Gamma矫正其实也属于我前面落下的一块内容,打算把它补上,其它的没补是因为我之前写的GAMES101笔记里已经涵盖了,而Gamma矫正在101里面确实没提到,于是打算把它补上,这块内容并不难,但是想通透的理…...
【数据结构】二叉树链式结构的实现及其常见操作
目录 1.手搓二叉树 2.二叉树的遍历 2.1前序、中序以及后序遍历 2.2二叉树的层序遍历 3.二叉树的常见操作 3.1求二叉树节点数量 3.2求二叉树叶子节点数量 3.3求二叉树第k层节点个数 3.3求二叉树的深度 3.4二叉树查找值为x的节点 4.二叉树的销毁 1.手搓二叉树 在学习…...
从零实战SLAM-第九课(后端优化)
在七月算法报的班,老师讲的蛮好。好记性不如烂笔头,关键内容还是记录一下吧,课程入口,感兴趣的同学可以学习一下。 --------------------------------------------------------------------------------------------------------…...
Python Opencv实践 - 图像金字塔
import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/pomeranian.png", cv.IMREAD_COLOR) print(img.shape)#图像上采样 #cv.pyrUp(src, dstNone, dstsizeNone, borderTypeNone) #参考资料:https://blo…...
Baumer工业相机堡盟工业相机如何通过BGAPI SDK设置相机的固定帧率(C++)
Baumer工业相机堡盟工业相机如何通过BGAPI SDK设置相机的固定帧率(C) Baumer工业相机Baumer工业相机的固定帧率功能的技术背景CameraExplorer如何查看相机固定帧率功能在BGAPI SDK里通过函数设置相机固定帧率 Baumer工业相机通过BGAPI SDK设置相机固定帧…...
计算机竞赛 python+大数据校园卡数据分析
0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于yolov5的深度学习车牌识别系统实现 🥇学长这里给一个题目综合评分(每项满分5分) 难度系数:4分工作量:4分创新点:3分 该项目较为新颖&am…...
DNNGP模型解读-early stopping 和 batch normalization的使用
一、考虑的因素(仅代表个人观点) 1.首先我们看到他的这篇文章所考虑的不同方面从而做出的不同改进,首先考虑到了对于基因组预测的深度学习方法的设计 ,我们设计出来这个方法就是为了基因组预测而使用,这也是主要目的&…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
