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 都不是银弹,不可能适合所有的场景,但这不应该成为我们拒绝它的理由。

TDD(测试驱动开发)死了吗?相关推荐

  1. tdd测试驱动开发课程介绍_测试驱动开发的实用介绍

    tdd测试驱动开发课程介绍 by Luca Piccinelli 通过卢卡·皮奇内利 测试驱动开发很难! 这是不为人知的事实. (Test Driven Development is hard! Th ...

  2. Java重构与TDD测试驱动开发实际案例一-陈勇-专题视频课程

    Java重构与TDD测试驱动开发实际案例一-2117人已学习 课程介绍         本课程将高深的重构与TDD理论埋藏在一个实际案例中,深入浅出地演示了重构与TDD的完整步骤. 在这个真实的案例中 ...

  3. 谈谈个人对 TDD (测试驱动开发) 的理解

    文章目录 介绍 我心中的 TDD 如何做 Tasking 举个例子 - Tasking 纵向拆分 Story 背景 Story -- 粗略版 Story -- 清晰版 Story -- 扩充 Task ...

  4. TDD 测试驱动开发工具、框架、快捷键和测试覆盖率

    文章目录 TDD 测试驱动开发工具.框架.快捷键和测试覆盖率 TDD 测试驱动开发工具与框架 资源链接 IntellijIdea 快捷键 Intellij 中查看测试覆盖率 Intellij 的 Li ...

  5. TDD测试驱动开发学习心得

    TDD测试驱动开发学习心得 1:一些名词 TDD:Test-Driven Development 测试驱动开发 BDD:Behavior Driven Development 行为驱动开发 黑盒测试: ...

  6. TDD 测试驱动开发与精益

    1「引子」 2000年的时候,开始学软件工程,听到极限编程 (eXtreme Programming) 里面强调要测试驱动开发TDD (Test Driven Development). 后面在做培训 ...

  7. 【架构 Flutter实践 Clean架构 TDD测试驱动开发---1.0】

    ----------- 2022-11-12 补充 ----------- 最近在开发中尝试用了clean架构,感觉就是 麻烦...太多模板代码,很容易过度开发. 我认为了解这些理念是很重要的,但应该 ...

  8. TDD测试驱动开发入门实践

    最近和团队同学们分享了测试驱动开发实践,把分享的内容同步在博客上,主要分为三个方面,简单介绍软件测试的发展历程,为什么需要使用TDD,TDD的在编码中的实践. 一,软件测试的发展历程 调试为主:  怎 ...

  9. TDD 测试驱动开发笔记

    文章目录 测试驱动开发 TDD(Test Driven Development) 是什么 有什么用 执行步骤 例子 先写测试 运行测试:测试失败 修复:编写必要代码使测试通过 重构 参考 测试驱动开发 ...

  10. TDD测试驱动开发的基础

    ★ 如果您需要软件并且需要快速,那么测试驱动开发(TDD)可能是解决方案.TDD致力于快速将软件从计算机推向市场,是当今顶级软件开发和软件测试公司正在使用的最有效方法之一. " 什么是测试驱 ...

最新文章

  1. python 线程等待_详解python多线程之间的同步(一)
  2. shell脚本命令set
  3. 加入阿里技术团队三年,哪些习惯让我在工作上持续受益?
  4. isinstance_Java类class isInstance()方法及示例
  5. CentOS操作系统keepalived安装步骤
  6. 【VSCode】SSH远程连接服务器
  7. 首字母筛选 java_【Java习作】提取汉字拼音首字母(Java版)
  8. Linux 修改 host
  9. 格林公式求圆并的面积及重心
  10. Ubuntu 使用firefox插件下载百度云文件
  11. SQLite主键自动增长
  12. 机器学习(八):样本分布不均衡问题的处理
  13. “不限流量卡”真的不限量,但是却限制了这些!
  14. win7电脑设置自动关机
  15. turtle的函数及使用
  16. 【记录十九】JCR2.0 节点类型
  17. 安卓app开发工具_最新app制作软件汇总:从零开始教你完成app开发
  18. Why We're Breaking Up with CSS-in-JS?
  19. pyinstaller打包逆向分析,顺便免杀Windows Defender
  20. 惠普台式计算机型号怎么看,如何查看惠普笔记本型号?惠普笔记本型号以及小型号查看方法...

热门文章

  1. Java网络编程——Socket 编程
  2. 使用电脑浏览器获取狗东账号cookie
  3. 获取Synopsys coverage report excel 文件单元格RGB值
  4. 30道SQL经典笔试题及其答案解析
  5. 黑客工具之AppScan下载安装,超详细使用教程
  6. 消毒碗柜行业市场经营管理及未来前景展望分析
  7. 极智AI | 昇腾 CANN ATC 模型转换
  8. 行业动态 - Zhaga-D4i 首批认证授予户外照明灯具产品
  9. Protues使用教程
  10. 三菱Q系列PLC编程TCP Socket套接字程序