专栏首页Teobler的开发日记敏捷技术实践之TDD
原创

敏捷技术实践之TDD

至此,生命之环的外圈和中间的一圈已经介绍完了,现在开始的就是内圈的技术实践,也是敏捷最为关键的实践,技术实践能否有效执行关乎着外围实践能否成功,可以说是敏捷最为重要的支撑。

技术实践要求开发人员进行大量的分钟级甚至秒级的,深刻的、充满仪式感的行为。以至于大部分团队尝试将这些实践去掉,但是去掉之后你就会发现什么叫做伪敏捷。这些技术实践才是敏捷的核心。没有测试驱动开发、重构、简单设计以及结对编程的敏捷注定没法成功。

需要明确的是,这些技术实践的每一项单独拎出来都能写一本书。所以文章中不会介绍详细内容,只会粗略讲解,欲知后事如何,请直接去看经典书籍。我们先从测试驱动开发开始讲起。

life_cycle.png

测试驱动开发

red_green_refactor

程序员的职责是什么?显而易见 - 写程序。程序由什么构成?是的,代码。一行行代码组成了代码文件,一个个代码文件组成了程序,这些代码不能出错,哪怕一个字符的错误,也可能导致完全相反的结果,给用户造成不可估量的损失。那么程序员应该怎么保证自己写的代码是没错的呢?

我们先来看看存在了很久的另一个职业 - 会计,是怎么做的。会计的职责跟程序员很像,他们也要通过一行行记账信息组成账本,同时信息也不能发生任何错误。

早在 1000 年前,他们就发明了复式记账。简单来说,每一笔交易会写入账本两次 - 在一组账户中记一笔贷项,在对应的另一组账户中记为借项。两个账户最终汇总到收支平衡表中,差额为零,如果不为零,那大概是出错了。

会计同学别打我,这只是简化

从一开始,会计师被要求一笔笔地记录交易并在每一笔交易记录后立即平衡余额。这样做的好处是可以立即发现错误,如果同时记录多次交易再去平衡余额会导致定位错误变得复杂。

测试驱动开发这是实践就是程序员界的同一实践。它要求程序员每次只添加一个行为,先写一个失败的测试,然后写出恰好能使这个测试通过的生产代码。这可以立即发现错误。同样地,如果先写一大堆生产代码,再来补测试,你很难发现自己的代码有什么问题。

这两种实践的目的只有一个,在一个重要的文本中避免出现错误。

TDD 三原则

TDD 的规则很简单,可以归纳为下面三条:

  • 先编写一个因为缺乏生产代码而运行失败的测试,然后编写生产代码。
  • 只允许编写一个刚好失败的测试 - 编译失败也算失败。
  • 只允许编写刚好能使当前失败测试通过的生产代码。

看起来蠢吗?蠢,是的,我第一次接触 TDD 也觉得这个规则蠢透了。为了写一个一加一等于二,我得先断言一加一的确等于二,再开始写真正的一加一。

如果一个程序员严格遵守三规则,他的工作状态是什么样的?

先为不存在的生产代码编写测试,因为测试调用了不存在的元素,编译失败。在生产代码中补上这个元素后,测试通过。回到测试文件接着写测试...这意味着程序员的工作周期只有几分钟甚至几秒钟,他们需要在极短的时间内在测试代码和生产代码之间反复切换。

这意味着你不可能一次写完一个完整的函数,甚至不能写完一个完整的 if 语句。因为测试需要刚好失败,代码需要刚好通过测试。大多数程序员认为这三条规则彻底打乱了他们写代码的思路,以至于无法容忍这看起来荒谬的规则。

所以,这样做真的能带来好处么?

调试

debugging

严格遵守 TDD 三原则,意味着任何程序员写出的代码,在一分钟前都是总是可工作的。这意味着什么?意味着你现在遇到的错误都发生在一分钟以内,调试一个一分钟以内发生的错误,对于程序员来说还不是信手拈来?你甚至不需要动用调试器,靠脑子就能想出来哪里出了问题。

测试驱动开发的程序员都不擅长使用调试器,因为他们不经常使用调试器,他们经常打交道的是自己刚刚写好的测试。那么是不是这样写出来的代码就一定没有 bug,完全不需要调试呢?

当然不是,TDD 的目的是为了让你的代码质量更好,但谁也没法保证写出来的代码没有 bug,但是 TDD 大大降低了 bug 的发生率和严重性

文档

Document

有多少程序员在写文档的工作中挣扎浮沉?程序员什么时候最喜欢文档?当然是集成/接手别人代码库的时候。但是就算是集成,你会仔细看那个又长又臭的 pdf 文件吗?还不是直接跳到代码示例看代码,毕竟文档会骗人,代码可不会。

如果你遵循三原则,你写出的每一个测试都是一份代码示例,如何调用 API,如何创建某个对象。测试已经有了超过 90% 使用场景覆盖。

你写出的测试已经成为了被测系统的文档。它以熟悉的语言编写,可运行,永远与生产代码保持同步。这份文档是完美的,因为它本身就是代码。

而且,测试本身并不能互相组合成一个系统。这些测试彼此之间并不了解,也并不互相依赖。每个测试都是一小段独立的代码单元,用于描述系统一小部分行为的方式。

完备性

completeness

看到这,可能有人会说,那我能不能不 TDD,先把生产代码写完,最后再来补测试?那么我们来看看这样做会发生什么。

首先你已经写完了所有生产代码,你现在开始补测试,不出意外的话,你编写的所有测试都会通过。一切都很开心,直到你遇到了一个不太好测试的函数,因为你没有先写测试,你也没有考虑可测试性。

现在你需要先去改变生产代码,然后再来补上这个不太好写的测试。那么问题来了,你怎么能保证你已经写好并且正确运行的生产代码在经过你的二次修改后行为不被改变呢?同时为了让其易于测试,你可能要打破耦合、添加抽象、增加函数...太烦了,明明它现在可以工作啊!

好,不测了,时间紧任务重,改起来太烦了,反正它现在可以工作,挂了再说,接着补其他测试。

测试补完了,运行一遍测试,全是过的,开心,交差!

你的测试不完整!这里通过的所有测试只给出了一个信息:被测到的功能没有被破坏。那么,那些没有被测试的功能呢?你有没有信心说我的测试全过了,我的代码可以部署了?

如果遵循 TDD 三原则,意味着你的每一行生产代码都是有测试保证的 - 先有的测试,才有的你那一行恰好可以通过的生产代码。你的测试是完备的,你有信心部署你测试全过的代码,这些测试告诉我们,我们的系统是可靠、可部署的。

但是是不是这样的代码就是 100% 覆盖了呢?显然不是,甚至三原则在某些情况下是不适用的(要了解更多的情况建议看书),这意味着就算你严格 TDD,也不太可能写出 100% 覆盖的代码。但是它依旧能给你带来 90% 以上的覆盖率,这已经足以支撑我们接下来的部署了。

乐趣

你又说了,既然你提到了测试完备性,那我可以使用测试覆盖率工具来追踪我的测试覆盖率,然后慢慢往上补呀。

如果你真的这么做过,你应该不会这么乐观地说出这句话。先不说难易程度,就这个过程来说实在是太枯燥了。代码已经可以工作了,你还在考虑各种场景,补上各种场景的测试。如果不信,你可以去试试这个过程有多无聊。还不说这路上会遇到的那些你没法写测试的代码。

设计

decoupling

TDD 的全称是什么?是的,Test-Driven Development。但是在公司里,我听到了他的另一个名字 - Test-Driven Design。这里的设计如果展开来讲又可以讲一个长篇了,这里只聊聊代码的可测试性。

我们回头想想为什么之前提到后补测试会出现没法测的代码?因为它与别的行为耦合在一起,你没有将它们设计成易于测试的代码。如果事后补测试,可测性应该是在你脑海中最不可能出现的词语。

而如果你先写测试,那么你的代码不可能出现不可测试的代码。这迫使你将生产代码设计成易于测试的样子,怎么样方便测试?是的,解耦,你的代码将是耦合度很低的代码,TDD 强迫你写出高度解耦的代码。

勇气

courage

上面提到了一系列 TDD 所带来的的好处,而我们最终需要的,其实是这些好处给我们的勇气,修改旧代码的勇气。

假如你在代码库里看到烂代码,你的第一个念头是“清理”一下,但转念一想,现在它是工作的,万一我改动以后不工作了怎么办,还是随它去吧。说白了这是一种恐惧心理,恐惧来源于没有安全感,没有安全感来源于未知。

如果团队中每一个人都抱有同样的心理,那么这个代码库一定会腐烂。在每次添加新的功能时,为了不改坏已有的功能,大量引入耦合和重复。于是大量的 if else 出现,代码越来越不可读,开发效率越来越慢,管理者越来越绝望。

可是假如所有代码都是 TDD 出来的,意味着它拥有一个完备的测试套件,这个保护网能给我们足够的勇气去修改旧代码 - 只要测试不挂,我的修改就是没有问题的!你不再恐惧修改代码,也不再堆积屎山, TDD 使我们表现得像一个专业的程序员 - 我们对我们的代码有完全的掌控。

总结

总有人说,我单纯写测试,不采用 TDD 的方式也能带来 TDD 的那些好处。但其实 TDD 所带来的的好处远远不是测试那么简单。

更少的调试,高质量的完备文档,极高程度的代码保护测试,高解耦程度的代码。以及这些好处带给你修改旧代码的勇气!

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 「首席架构师看敏捷数据」核心实践:测试驱动开发(TDD)简介

    测试驱动开发(TDD) (Beck 2003;,是一种渐进的开发方法,它结合了测试优先的开发,即在编写足够的产品代码以完成测试和重构之前编写测试。TDD的主要目...

    首席架构师智库
  • 软件敏捷开发 TDD 方案

    现在开发软件都讲敏捷开发,何为敏捷开发?敏捷开发是一种应对快速变化的需求的一种软件开发能力。它们的具体名称、理念、过程、术语都不尽相同,相对于"非敏捷",更强调...

    拾贰
  • 【敏捷实践】推行TDD的思考

    目前来看,推行TDD的障碍大约有如下几点: 开发人员的质量意识; 分析需求并进行任务分解的能力; 将测试作为开发起点的开发习惯; 开发人员的重构能力,包括如何识...

    张逸
  • 我在ThoughtWorks中的敏捷实践

    E项目是一个在线的物资跟踪监控系统。由ThoughtWorks团队为客户提供的一套完善的软件交付服务。

    袁慎建@ThoughtWorks
  • 让我们再聊聊TDD|洞见

    最近几年“TDD已死”的声音不断出现,特别是David Heinemeier Hansson那篇文章——《TDD is dead. Long live test...

    ThoughtWorks
  • 「首席架构师看敏捷建模」纪律:敏捷设计理念

    本文概述了敏捷软件开发团队的设计策略。这些策略对于扩展敏捷软件开发以满足现代IT组织的实际需求至关重要。敏捷的设计方法与传统方法截然不同,显然也更有效。重要的是...

    首席架构师智库
  • 极限编程技术实践

    为了统一语言,我想有必要在开始讲重构前聊聊到底什么是重构。很多人讲到重构时甚至讲的是“将已有代码全删掉,重新写一遍这件事”,很显然这是重写不叫重构。

    Teobler
  • 从零到一,构建你的持续交付(终):从零到一,易;从零到一,难

    如果从这个系列的第一篇看起,直至这一篇为止,我相信一个共识应该是:在技术上实现它非常简单

    御剑
  • 开发人员看测试之TDD和BDD

    前言:   已经数月没有来社区了,写博客贵在坚持,一旦松懈了,断掉了,就很难再拾起来。但是每每看到自己博客里的博文的浏览量每天都在增加,都在无形当中给了我继续写...

    JackieZheng
  • 不一样的入职之旅

    不一样的旅程 在ThoughtWorks,新加入的应届生将获得为期五周的出国留学机会(ThoughtWorks University,简称TWU),社招加入的...

    ThoughtWorks
  • ThoughtWorks给你不一样的入职之旅

    ThoughtWorks是一家极具创造力的公司,在这里,人才是最重要的资产。如果你以应届生的身份加入TW,你将获得5周的出国留学机会(ThoughtWorks ...

    袁慎建@ThoughtWorks
  • 简单设计落地三板斧

    如果你认同 简单设计的价值观,我相信 解析简单设计原则 对你来说很容易理解并接受,它不像面向对象设计原则(比如:SOLID)那么晦涩难懂,它给你指明了一条明朗...

    袁慎建@ThoughtWorks
  • ThoughtWorks的敏捷开发 | 洞见

    ThoughtWorks的敏捷开发方法一直是一种神秘存在。在敏捷开发还没有主流化的年代,为了让外界理解ThoughtWorks全球团队怎么做敏捷,我们商定了一个...

    ThoughtWorks
  • Java单体应用 - 常用框架 - 03.JUnit

    原文地址:http://www.work100.net/training/monolithic-frameworks-junit.html

    光束云
  • 「敏捷测试」敏捷方法论:理解敏捷测试的完整指南

    事实上,根据VersionOne的敏捷状态报告,截至2018年,97%的组织以某种形式实践敏捷。 然而,受访者表示,这种采用在其组织中并不总是很普遍,这意味着在...

    首席架构师智库
  • 你确定懂什么是敏捷测试?

    早在2009年,Lisa Crispin和Janet Gergory就写了一本书《Agile Testing: A practical Guide for te...

    小老鼠
  • TW洞见 | TDD随想录

    2014年我一直从事在敏捷实践咨询项目,这也是我颇有收获的一年,特别是咨询项目的每一点改变,不管是代码质量的提高,还是自组织团队的建设,都能让我们感到欣慰。涉及...

    ThoughtWorks
  • 软件匠艺

    从敏捷宣言发布,敏捷如同在雪山顶滚下的一个小雪球,迅速发展并很快席卷了整个软件业。但是如同传话游戏那样,最初的敏捷思想被扭曲和简化,最终到管理者耳朵里变成了是承...

    Teobler
  • Java架构师之路:Java程序员必看的15本书的电子版下载地址

      作为Java程序员来说,最痛苦的事情莫过于可以选择的范围太广,可以读的书太多,往往容易无所适从。我想就我自己读过的技术书籍中挑选出来一些,按照学习的先后顺序...

    Java团长

扫码关注云+社区

领取腾讯云代金券