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;
@Before
public void setUp() throws Exception {
ticket = new Ticket();
}
@Test
public 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;
@Before
public void setUp() throws Exception {
ticket = new Ticket();
}
@Test
public void testOne() {
BigDecimal total = new BigDecimal("99");
assertEquals(total, ticket.sale(1));
}
@Test(expected=IllegalArgumentException.class)
public void testNegative() {
ticket.sale(-1);
}
@Test
public void testZero() {
assertEquals(BigDecimal.ZERO, ticket.sale(0));
}
@Test
public 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 都不是银弹,不可能适合所有的场景,但这不应该成为我们拒绝它的理由。
TDD(测试驱动开发)死了吗?的更多相关文章
- TDD(测试驱动开发)培训录
2014年我一直从事在敏捷实践咨询项目,这也是我颇有收获的一年,特别是咨询项目的每一点改变,不管是代码质量的提高,还是自组织团队的建设,都能让我们感到欣慰.涉及人的问题都是复杂问题,改变人,改变一个组 ...
- TDD(测试驱动开发)培训录(转)
本文转载自:http://www.cnblogs.com/whitewolf/p/4205761.html 最近也在了解TDD,发现这篇文章不错,特此转载一下. TDD(测试驱动开发)培训录 2015 ...
- TDD(测试驱动开发)学习一:初识TDD
首先说一下名词解释,TDD,英文名称Test-Driven Development,中文名称测试驱动开发,简单的断下句“测试/驱动/开发”,简单的理解一下,就是测试驱动着开发,大白话就是说用一边测试一 ...
- TDD(测试驱动开发)
TDD(测试驱动开发)培训录 2014年我一直从事在敏捷实践咨询项目,这也是我颇有收获的一年,特别是咨询项目的每一点改变,不管是代码质量的提高,还是自组织团队的建设,都能让我们感到欣慰.涉及人的问题都 ...
- TDD(测试驱动开发)学习二:创建第一个TDD程序
本节我们将学习一些测试驱动开发环境的搭建,测试驱动开发概念和流程.所涉及的内容全部会以截图的形式贴出来,如果你也感兴趣,可以一步一步的跟着来做,如果你有任何问题,可以进行留言,我也会很高兴的为你答疑. ...
- (译)TDD(测试驱动开发)的5个步骤
原文:5 steps of test-driven development https://developer.ibm.com/articles/5-steps-of-test-driven-deve ...
- 基于SOA架构的TDD测试驱动开发模式
以需求用例为基,Case&Coding两条线并行,服务(M)&消费(VC)分离,单元.接口.功能.集成四层质量管理,自动化集成.测试.交付全程支持. 3个大阶段(需求分析阶段.研发准备 ...
- TDD(测试驱动开发)的推广方法论
- 测试驱动开发(TDD)的思考
极限编程 敏捷开发是一种思想,极限编程也是一种思想,它与敏捷开发某些目标是一致的.只是实现方式不同.测试驱动开发是极限编程的一部分. 1.极限编程这个思路的来源 Kent Beck先生最早在其极限编程 ...
- 测试驱动开发 TDD
一.详解TDD 1.1.TDD概念 :Test Drived Develop 测试驱动开发是敏捷开发中的一项核心实践和技术,也是一种方法论.TDD的原理是在开发功能代码之前,编写单元测试用例代码,测试 ...
随机推荐
- Android音频输入通道的底层硬件和软件开发分析
Android潜在的发展音频输入通道的软硬件分析 我们都知道耳机Mic集成在一直的那种四段耳机Mic插头是Android设备上比較经常使用.可是也会有分开的情况,比較假设在普通的PC机中装Androi ...
- 如何用JS获取“ul”下边的“li”的个数
<script type="text/javascript"> function OnbtnClick() { var txtCount=document.getEle ...
- QT之圆形头像(使用PNG的Mask达到的效果)
废话不多说!直接上代码. 我们在很多UI设计应用中,需要用到自定义形状头像,在这里,我对圆形头像的设计做简单的阐述,其它形状头像可参考本文做相应的更改即可.如下图所示为设计的圆形头像: 上代码: Se ...
- WPF 的毛玻璃效果
原文:WPF 的毛玻璃效果 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/koloumi/article/details/76917519 其实很简 ...
- Emoji:搜索将与您找到表情符号背后的故事
眼下.秉已经开始支持emoji搜索,这意味着,你可以插入或粘贴系列emoji表情,让我们的爱.微笑.食品等..些表情随意组合,必应总会带给你非常多有趣的但却没有不论什么实际用途的搜索结果. 这是一项非 ...
- Linux性能测试 ss命令
ss即socket state,也就是说,是可以查看系统中socket的状态的.我们可以用netstat,但为什么还要用ss这个工具呢,当然ss也是有好处的.当我们打开的socket数量很多时,net ...
- Linux 获得了其首款基于 RISC-V 的多核开源处理器
去年,硅谷创业公司 SiFive 发布了首款开源 SoC(片上系统 System on a Chip),命名为 Freeform Everywhere 310.现在,该公司从嵌入式系统领先一步,发布了 ...
- [Hibernate系列—] 3. 映射文件和使用SchemaExport制作自己主动Schema
自己定义映射文件 这里的映射文件指的是相应到数据库表的xml 的定义文件. 相应的每一个数据库表栏位, 能够定义的属性有: 属性名 类型 Description length number 栏位的长度 ...
- Matlab Tricks(二十四)—— 将一副图像逆时针旋转 180°
function I2 = rot180(I) I2 = I(end:-1:1, end:-1:1); % 上下颠倒,左右颠倒:
- Angular常用指令
安装Node 先去Node官网下载并安装Node.js Install the Angular CLI(安装Angular CLI) npm install -g @angular/cli Creat ...