TDD(测试驱动开发)?
01、前言
很早之前,曾在网络上见到过 TDD 这 3 个大写的英文字母,它是 Test Driven Development 这三个单词的缩写,也就是“测试驱动开发”的意思——听起来很不错的一种理念。
其理念主要是确保两件事:
- 确保所有的需求都能被照顾到。
- 在代码不断增加和重构的过程中,可以检查所有的功能是否正确。
但后来很长一段时间里,都没再听过 TDD 的消息。有人说,TDD 已经死了,给出的意见如下:
1)通常来说,开发人员不应该在没有失败的测试用例下编写代码——这似乎是合理的,但是它可能导致过度测试。例如,为了保证一行生产代码的正确性,你不由得写了 4 行测试代码,这意味着一旦这一行生产代码需要修改,你也得修改那 4 行测试代码。
2)为了遵循 TDD 而写的代码,容易进入一个误区:代码是为了满足测试用的,而忽略了实际需求。
02、TDD 到底是什么?
不管 TDD 到底死了没有,先让我们来回顾一下 TDD 到底是什么。
TDD 的基本思想就是在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循环进行添加其他功能,直到完成全部功能的开发。
TDD 的基本过程可以拆解为以下 6 个步骤:
1) 分析需求,把需求拆分为具体的任务。
2) 从任务列表中取出一个任务,并对其编写测试用例。
3) 由于没有实际的功能代码,测试代码不大可能会通过(红)。
4) 编写对应的功能代码,尽快让测试代码通过(绿)。
5) 对代码进行重构,并保证测试通过(重构)。
6) 重复以上步骤。
可以用下图来表示上述过程。
03、TDD 的实践过程
通常情况下,我们都习惯在需求分析完成之后,尽快地投入功能代码的编写工作中,之后再去调用和测试。
而 TDD 则不同,它假设我们已经有了一个“测试用户”了,它是功能代码的第一个使用者,尽管功能代码还不太完善。
当我们站在“测试用户”的角度去写测试代码的时候,我们要考虑的是,这个“测试用户”该如何使用功能代码呢?是通过一个类直接调用方法呢(静态方法),还是构建类的实例去调用方法呢(实例方法)?这个方法如何传参呢?方法如何命名呢?方法有返回值吗?
有了测试代码后,我们开始编写功能代码,并且要以最快地速度让测试由“红”变为“绿”,可能此时的功能代码很不优雅,不过没关系。
当测试通过以后,我们就可以放心大胆的对功能代码进行“重构”了——优化原来比较丑陋、臃肿、性能偏差的代码。
接下来,假设我们接到了一个开发需求:
汪汪队要到小镇冒险岛进行表演,门票为 99 元,冒险岛上唯一的一个程序员王二需要开发一款可以计算门票收入的小程序。
按照 TDD 的流程,王二需要先使用 Junit 编写一个简单的测试用例,测试预期是:销售一张门票的收入是 99 元。
public class TicketTest {private Ticket ticket;@Beforepublic void setUp() throws Exception {ticket = new Ticket();}@Testpublic void test() {BigDecimal total = new BigDecimal("99");assertEquals(total, ticket.sale(1));}}
为了便于编译能够顺利通过,王二需要一个简单的 Ticket 类:
public class Ticket {public BigDecimal sale(int count) {return BigDecimal.ZERO;}}
测试用例运行结果如下图所示,红色表示测试没有通过:预期结果是 99,实际结果是 0。
那接下来,王二需要快速让测试通过,Ticket.sale()
方法修改后的结果如下:
public class Ticket {public BigDecimal sale(int count) {if (count == 1) {return new BigDecimal("99");}return BigDecimal.ZERO;}}
再运行一下测试用例,结果如下图所示,绿色表示测试通过了:预期结果是 99,实际结果是 99。
绿了,绿了,测试通过了,到了该重构功能代码的时候了。99 元是个魔法数字,至少应该声明成常量,对吧?
public class Ticket {private final static int PRICE = 99;public BigDecimal sale(int count) {if (count == 1) {return new BigDecimal(PRICE);}return BigDecimal.ZERO;}}
重构完后再运行一下测试用例,确保测试通过的情况下,再增加几个测试用例,比如说门票销量为负数、零甚至一千的情况。
public class TicketTest {private Ticket ticket;@Beforepublic void setUp() throws Exception {ticket = new Ticket();}@Testpublic void testOne() {BigDecimal total = new BigDecimal("99");assertEquals(total, ticket.sale(1));}@Test(expected=IllegalArgumentException.class)public void testNegative() {ticket.sale(-1);}@Testpublic void testZero() {assertEquals(BigDecimal.ZERO, ticket.sale(0));}@Testpublic void test1000() {assertEquals(new BigDecimal(99000), ticket.sale(1000));}}
销量为负数的时候,王二希望功能代码能够抛出异常;销量为零的时候,功能代码的计算结果应该为零;销量为一千的时候,计算结果应该为 99000。
有两个测试用例没有通过,那么王二需要继续修改功能代码,调整如下:
public class Ticket {private final static int PRICE = 99;public BigDecimal sale(int count) {if (count < 0) {throw new IllegalArgumentException("销量不能为负数");}if (count == 0) {return BigDecimal.ZERO;}if (count == 1) {return new BigDecimal(PRICE);}return new BigDecimal(PRICE * count);}}
再运行一下测试用例,发现都通过了。又到了重构的时候了,销量为零、或者大于等于一的时候,代码可以合并,于是重构结果如下:
public class Ticket {private final static int PRICE = 99;public BigDecimal sale(int count) {if (count < 0) {throw new IllegalArgumentException("销量不能为负数");}return new BigDecimal(PRICE * count);}}
重构结束后,再运行测试用例,确保重构后的代码依然可用。
04、最后
从上面的实践过程可以得出如下结论:
TDD 想要做的就是让我们对自己的代码充满信心,因为我们可以通过测试代码来判断这段代码是否正确无误。
也就是说,TDD 流程比较关键的一环在于如何写出有效的测试代码,这里有 4 个原则可以参考:
1)测试过程应该尽量模拟正常使用的过程。
2)应该尽量做到分支覆盖。
3)测试数据应该尽量包括真实数据,以及边界数据。
4)测试语句和测试数据应该尽量简单,容易理解。
注意,这 4 个原则不仅适用于 TDD,同样适用于任何流程下的单元测试。
最后,我想说的是,不管 TDD 有没有死,TDD 都不是银弹,不可能适合所有的场景,但这不应该成为我们拒绝它的理由。
【B站最全最易学】十年大佬终于将测试开发路线整理出来了,小白一学就会,拿走不谢,允许白嫖!!
相关文章:

TDD(测试驱动开发)?
01、前言 很早之前,曾在网络上见到过 TDD 这 3 个大写的英文字母,它是 Test Driven Development 这三个单词的缩写,也就是“测试驱动开发”的意思——听起来很不错的一种理念。 其理念主要是确保两件事: 确保所有的需求都能被照…...

C/C++
const 作用 修饰变量,说明该变量不可以被改变;修饰指针,分为指向常量的指针(pointer to const)和自身是常量的指针(常量指针,const pointer);修饰引用,指向…...

CCF C³ 走进百度:大模型与可持续生态发展
2023年8月10日,由CCF CTO Club发起的第22期C活动在百度北京总部进行,以“AI大语言模型技术与生态发展”主题,50余位企业界、学界专家、研究人员就此进行深入探讨。 CCF C走进百度 本次活动,CCF秘书长唐卫清与百度集团副总裁、深…...

Vue使用html2canvas将DOM节点生成对应的PDF
要通过Vue使用html2canvas将DOM节点生成对应的PDF,您需要安装html2canvas和jspdf这两个库。html2canvas用于将DOM节点转换为Canvas,而jspdf用于将Canvas转换为PDF。以下是一个简单的示例代码,展示了如何使用html2canvas和jspdf生成PDF文件&am…...

专访阿里云席明贤,视频云如何运用大模型与小模型来破茧升级2.0
不久前,LiveVideoStack与阿里云视频云负责人席明贤(花名右贤)展开一场深度的对话,一个是圈内专业的社区媒体,一个是20年的IT老兵,双方有交集、有碰撞、有火花。 面对风云变幻的内外环境,阿里云…...

Vue 2的计算属性与侦听器
计算属性 vs 方法 vs 侦听器 计算属性的出现是为了解决模板内表达式太过复杂而变得难以维护。 假设我们知道长和宽,要计算一个矩形的面积,如果没有计算属性,我们可能像下面这样处理: <div id"app"><input t…...

JavaScript基础:学习JavaScript语言的基本语法和常用操作,了解网页交互的基本原理
JavaScript是一种广泛应用于网页开发中的脚本语言,它可以与HTML和CSS一起使用,实现网页交互及动态效果。 以下是JavaScript的基本语法和常用操作: 变量声明:使用var、let或const关键字声明变量。 var name "John";let …...

网络每日一练
吴泽彬 C Ip 网络层 Tcp udp 传输层, Http 应用层 收起 1 回复 发布于 2019-10-11 12:07 举报 fighting2016 Java A类地址中的私有地址和保留地址: ①10.0.0.0到10.255.255.255是私有地址(所谓的私有地址就是在互联网上不使用,而被…...

asp.net core读取request内容
在Startup.cs中定义Middleware,设置缓存Http请求的Body数据。代码如下。自定义Middleware请放到Configure方法的最前面。 app.Use(next > new RequestDelegate(async context > {context.Request.EnableBuffering();await next(context);})); GET请求 HttpC…...

笔记:移植xenomai到nuc972(2)
接下来的测试,出现了两个问题 第一个问题是demo程序启动不了,这是上一篇文章忘记说的事,启动不了的原因是权限问题,提示需要root, 但我是用busybox搭的文件系统,直接就是root,不存在权限问题,所以问题出在应用上,经过一番调试后发现,问题出在xenomai的应用库上,具体位置在xen…...

记忆正则表达式的基本元件
正则常见的三种功能,它们分别是:校验数据的有效性、查找符合要求的文本以及对文本进行切割和替换等操作。 正则表达式,简单地说就是描述字符串的规则。在正则中,普通字符表示的还是原来的意思,比如字符 a,…...

低代码是什么?解决哪些问题?什么业务场景适合用低码开发?
目录 一、低代码是什么? 二、低代码解决方案的主要特点 01.可视化开发环境 02.预构建的组件和模板 03.集成的开发和测试工具 04.跨平台兼容性 05.可伸缩性和可扩展性 三、开发工具中的强者 四、你所在企业为什么要关心低代码开发? 五、什么业务场景适…...

SOA架构
SOA架构 Service-Oriented Architecture,SOA是一种软件架构模式,旨在将应用程序的不同功能划分为一组可重用的、自治的、可互操作的服务。 每个服务表示一个特定的业务功能,并通过定义明确的接口和协议来实现与其他服务的通信。 SOA的主要目…...

“深入探索JVM内部机制:解密Java虚拟机“
标题:深入探索JVM内部机制:解密Java虚拟机 摘要:本篇博客将深入剖析Java虚拟机(JVM)的内部机制,包括类加载、内存管理、垃圾回收、即时编译等关键组成部分。通过对JVM内部机制的解密,我们可以更…...

PostgreSQL空值的判断
PostgreSQL空值的判断 空值判断非空判断总结 空值判断 -- 查询为空的 is null,sql简写isnull select * from employees where manager_id isnull;select * from employees where manager_id is null;非空判断 -- 查询不为空的 is not null;sql简写notnull select * from empl…...

使用phpunit进行单元测试
使用phpunit进行单元测试 本教程假定您使用 PHP 8.1 或 PHP 8.2。您将学习如何编写简单的单元测试以及如何下载和运行 PHPUnit. PHPUnit 10 的文档 在这。 下载:可以用以下2种方法之一: 1.PHP 存档 (PHAR) 我们分发了一个 PHP存档(PHAR&…...

MongoDB 简介
什么是MongoDB ? MongoDB 是由C语言编写的,是一个基于分布式文件存储的开源数据库系统。 在高负载的情况下,添加更多的节点,可以保证服务器性能。 MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。 MongoDB 将数据存储为一个…...

Java从入门到高级的全面指南
快速入门 对于初学者,要快速入门Java,首先需要了解Java的基本语法和面向对象编程的概念。以下是一些关键步骤: 了解基础语法: Java的语法包括变量、数据类型、运算符、控制语句等。你可以通过阅读相关的书籍或者在线教程来学习这…...

linux 命令- systemctl
systemctl 参数说明 1、使用语法 用法:systemctl [OPTIONS…] {COMMAND} … 2 、参数说明 参数参数说明start立刻启动后面接的unitstop立刻关闭后面接的unitrestart立刻关闭后启动后面接的unit,亦即执行stop再start的意思reload不关闭后面接的unit的…...

自动驾驶,一次道阻且长的远征|数据猿直播干货分享
数据智能产业创新服务媒体 ——聚焦数智 改变商业 在6月的世界人工智能大会上,马斯克在致辞中宣称,到2023年底,特斯拉便可实现L4级或L5级的完全自动驾驶(FSD)。两个月之后,马斯克又在X社交平台上发言&am…...

大数据培训前景怎么样?企业需求量大吗
大数据行业对大家来说并不陌生,大数据行业市场人才需求量大,越早入行越有优势,发展机会和上升空间等大。不少人通过大数据培训来提升自己的经验和自身技术能力,以此来获得更好的就业机会。 2023大数据培训就业前景怎么样呢?企业需…...

redis — 基于Spring Boot实现redis延迟队列
1. 业务场景 延时队列场景在我们日常业务开发中经常遇到,它是一种特殊类型的消息队列,它允许把消息发送到队列中,但不立即投递给消费者,而是在一定时间后再将消息投递给消费者。延迟队列的常见使用场景有以下几种: 在…...

【日常积累】Linux之init系统学习
init系统简介: Linux 操作系统的启动首先从 BIOS 开始,接下来进入 boot loader,由 bootloader 载入内核,进行内核初始化。内核初始化的最后一步就是启动 pid 为 1 的 init 进程,这个进程是系统的第一个进程,它负责产生…...

Python功能制作之3D方块
介绍 用python写一个黑窗口,窗口里面有一个白色的3D方块,左键按下后移动可以旋转以各个视角来看方块。 当然有需要的话,可以自己在代码中去更改颜色,直接通过RBG的参数进行更改即可。 做了两个函数:init[初始化]和d…...

【0基础入门Python笔记】二、python 之逻辑运算和制流程语句
二、python 之逻辑运算和制流程语句 逻辑运算控制流程语句条件语句(if语句)循环结构(for循环、while循环)continue、break和pass关键字控制流程语句的嵌套以及elif 逻辑运算 Python提供基本的逻辑运算:不仅包括布尔运…...

python中的svm:介绍和基本使用方法
python中的svm:介绍和基本使用方法 支持向量机(Support Vector Machine,简称SVM)是一种常用的分类算法,可以用于解决分类和回归问题。SVM通过构建一个超平面,将不同类别的数据分隔开,使得正负样…...

typedef
t y p e d e f typedef typedef 声明,简称typedef,是创建现有类型的新名字。 比如: #include <bits/stdc.h> using namespace std; typedef long long ll; int main() {ll n;scanf("%lld",&n);printf("%lld"…...

校园跑腿市场行情分析
随着社会的发展和人们生活节奏的加快,校园跑腿市场逐渐兴起并呈现出蓬勃发展的态势。在这个快节奏的时代,越来越多的学生需要在繁忙的学业之外完成各种任务,而校园跑腿服务正是应运而生,为他们提供了便利和时效。本文将从需求方面…...

微服务相关面试题
👏作者简介:大家好,我是爱写博客的嗯哼,爱好Java的小菜坤 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦 📝社区论坛:希望大家能加入社区共同进步…...