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站最全最易学】十年大佬终于将测试开发路线整理出来了,小白一学就会,拿走不谢,允许白嫖!!

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

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

相关文章

macOS Ventura 13.5.1(22G90)发布(附黑/白苹果系统镜像地址)

系统镜像下载&#xff1a;百度&#xff1a;黑果魏叔 系统介绍 黑果魏叔 8 月 18 日消息&#xff0c;苹果今日向 Mac 电脑用户推送了 macOS 13.5.1 更新&#xff08;内部版本号&#xff1a;22G90&#xff09;&#xff0c;本次更新距离上次发布隔了 24 天。 本次更新重点修复了…

Redis 缓存过期及删除

一、Redis缓存过期策略 物理内存达到上限后&#xff0c;像磁盘空间申请虚拟内存(硬盘与内存的swap),甚至崩溃。 内存与硬盘交换 (swap) 虚拟内存&#xff0c;频繁I0 性能急剧下降&#xff0c;会造成redis内存急剧下降&#xff1b; 一般设置物理内存的3/4&#xff0c;在redis…

内存不足V4L2 申请DMC缓存报错问题

当内存不足时,V4L2可能存在申请DMA缓存报错,如下日志: 13:36:54:125 [15070.640862] rkcifhw fdfe0000.rkcif: swiotlb buffer is full (sz: 1843200 bytes) 13:36:54:125 [15070.640891] rkcifhw fdfe0000.rkcif: swiotlb: coherent allocation failed, size=1843200 13:3…

超分辨率地震速度模型

文献分享 1. Multitask Learning for Super-Resolution 原题目&#xff1a;Multitask Learning for Super-Resolution of Seismic Velocity Model 全波形反演&#xff08;FWI&#xff09;是估算地下速度模型的强大工具。与传统反演策略相比&#xff0c;FWI充分利用了地震波的…

typedef

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

C++ 面向对象三大特性——多态

✅<1>主页&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;C 继承 ☂️<3>开发环境&#xff1a;Visual Studio 2022 &#x1f4ac;<4>前言&#xff1a;面向对象三大特性的&#xff0c;封装&#xff0c;继承&#xff0c;多态&#xff…

30W IP网络有源音箱 校园广播音箱

SV-7042XT是深圳锐科达电子有限公司的一款2.0声道壁挂式网络有源音箱&#xff0c;具有10/100M以太网接口&#xff0c;可将网络音源通过自带的功放和喇叭输出播放&#xff0c;可达到功率30W。同时它可以外接一个30W的无源副音箱&#xff0c;用在面积较大的场所。5寸进口全频低音…

RNN模型简单理解和CNN区别

目录 神经网络&#xff1a;水平方向延伸&#xff0c;数据不具有关联性 ​ RNN&#xff1a;在神经网络的基础上加上了时间顺序&#xff0c;语义理解 ​RNN: 训练中采用梯度下降&#xff0c;反向传播 ​ 长短期记忆模型 ​输出关系&#xff1a;1 toN&#xff0c;N to N 单入…

Spring三级缓存

目录 循环依赖问题 三级缓存 三级缓存创建Bean的流程&#xff08;解决循环依赖问题&#xff09; 三级缓存的局限性 Spring的三级缓存是为了解决单例Bean的循环依赖问题而存在的。 循环依赖问题 简单来说就是A依赖B&#xff0c;而B又依赖A。即创建A的时候&#xff0c;需要先…

【HarmonyOS】【DevEco Studio】ohpm安装失败该如何解决?

【关键词】 HarmonyOS、DevEco Studio、ohpm安装失败 【问题背景及解决方案】 最近遇到很多DevEco Studio安装ohpm失败的问题&#xff0c;下面给大家介绍几种出现的问题以及解决方案&#xff1a; 1、ohpm not set up&#xff0c;报错截图如下&#xff1a; ​ 解决方案&…

一百六十、Kettle——Linux上安装的Kettle9.2.0连接Hive3.1.2

一、目标 Kettle9.2.0在Linux上安装好后&#xff0c;需要与Hive3.1.2数据库建立连接 之前已经在本地上用kettle9.2.0连上Hive3.1.2 二、各工具版本 &#xff08;一&#xff09;kettle9.2.0 kettle9.2.0安装包网盘链接 链接&#xff1a;https://pan.baidu.com/s/15Zq9w…

C++中class嵌套时构造函数,析构函数调用的顺序

#include<iostream> using namespace std; class Phone { public:Phone(string pname){m_pnamepname;cout<<"phone的构造函数调用"<<endl;}~Phone(){cout<<"Phone的析构函数调用"<<endl;}string m_pname; }; class Person {…

网安周报|Monti Ransomware团伙推出了一个新的Linux加密器

Monti Ransomware团伙推出了一个新的Linux加密器 经过两个月的休息&#xff0c;Monti 勒索软件运营商带着新的 Linux 版本的加密器返回。该变体被用于针对政府和法律部门组织的攻击。研究人员注意到两个团伙的TTP之间有多个相似之处&#xff0c;Monti运营商还基于Conti泄露的源…

2023 Robocom 游记+题解

Robocom赛前一天熬夜打了一场edu,全程眯眯眼&#xff0c;三题滚粗了&#xff0c;前三题花了一小时才写完&#xff0c;第四题写了一小时也没写明白&#xff0c;好像预示着Robocom的结局&#xff1f; 早上七点醒了&#xff0c;感觉自己浑身无力&#xff0c;想睡觉但是又睡不着的…

AutoSAR配置与实践(基础篇)3.3 BSW的通信功能

传送门 -> AUTOSAR配置与实践总目录 AutoSAR配置与实践&#xff08;基础篇&#xff09;3.3 BSW的通信功能 一、收发过程概览1.1 发送过程概览1.2 接收过程概览 二、BSW的通信功能模块组成三、收发过程解析3.1 发送过程3.2 发送后的结果确认3.3 接收过程 一、收发过程概览 1…

Airbnb开源数据可视化工具Visx

一、什么是visx visx 是用于 React 的富有表现力的底层可视化组件集合,结合了 d3 的强大功能来生成可视化,以及 React 更新 DOM 的诸多优势。 在 Airbnb 内部,visx 的目标是统一整个公司的可视化堆栈,在此过程中,创建了 visx 项目,从而有效的将 D3 的强大功能与 React …

内核调试之devmem直接读写寄存器

今天分享一个内核调试实用工具——devmem。 相信很多做底层驱动的人都会经常用到。 什么是devmem&#xff1f; 在Linux系统&#xff0c;如果我们想要访问某个寄存器&#xff0c;就需要写一个驱动程序&#xff0c;在驱动中映射寄存器地址&#xff0c;转为虚拟地址后就可以访问…

windows电脑系统自带的画图工具如何实现自由拼图

1.首先选中你要拼接的第一张图片&#xff0c;右键选着编辑&#xff0c;会自动打开自带的画图工具 然后就是打开第一张图片&#xff0c;如下图所示 接着就是将画布托大&#xff0c;如下图所示。 然后点击选择&#xff0c;选择下面的空白区域&#xff0c;选着区域的范围要比准备拼…

05-微信小程序常用组件-表单组件

05-微信小程序常用组件-表单组件 文章目录 表单组件button 按钮案例代码 form 表单案例代码 image 图片支持长按识别的码案例代码 微信小程序包含了六大组件&#xff1a; 视图容器、 基础内容、 导航、 表单、 互动和 导航。这些组件可以通过WXML和WXSS进行布局和样式设…

jQuery第一次接触

jQuery是一个轻量级js库 1.下载jquery库&#xff0c;网址Download jQuery | jQuery npm i jquery 2.还可以从cdn中载入jquery <script src"https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"> 3.j代表js&#xff0c;query代表查询&#xff0c;jQu…