徐昊 · TDD 项目实战 70 讲
徐昊
Thoughtworks 中国区 CTO
18159 人已学习
新⼈⾸单¥98
登录后,你可以任选4讲全文学习
课程目录
已完结/共 88 讲
实战项目二|RESTful开发框架:依赖注入容器 (24讲)
实战项目三|RESTful Web Services (44讲)
徐昊 · TDD 项目实战 70 讲
15
15
1.0x
00:00/12:01
登录|注册

开篇词|为什么TDD是当前最具工程效能的研发流程?

讲述:徐昊大小:10.98M时长:12:01
你好,我是徐昊,欢迎和我一起学习测试驱动开发(Test-Driven Development,TDD)。
对于测试驱动开发,稍有了解而全无实践的人,会认为是天方夜谭,甚至无法想象为什么需要这样的方式来开发:
为什么要开发人员来写测试?这难道不是测试人员的工作吗?难道开发写了测试,测试人员就不用再测了嘛?
又要写测试,又要写生产代码,效率是不是太低了?只写生产代码效率应该更高吧?
不写测试我也能写出可以工作的软件,那么写测试能给我带来什么额外的好处呢?
的确,从直觉上来看,测试驱动开发相当令人困惑:它将我们通常认为的辅助性工作——测试,作为程序员编码的主要驱动力;它主张通过构造一系列自动化测试(由程序员编写),为编写生产代码(Production Code)做指引;它甚至建议,如果不存在失败的测试,就不要编写生产代码。
看起来,似乎测试驱动开发有些过分强调测试对于程序员的重要性了。
那么我们就需要仔细思考,“测试”在所谓的“正常软件开发模式”中,到底发挥着怎样的作用。当明晰了测试驱动开发的这个核心逻辑之后,我们才能讨论是不是过分强调了。

隐式程序员测试(Implicit Developer Testing)

直觉和经验告诉我们,在所谓的“正常软件开发模式”中,貌似测试只是最后的验收步骤,程序员很少直接参与。但事实却不是这样,就算是所谓的“正常软件开发模式”,也蕴含着非常多“程序员测试”的步骤。只不过这些“程序员测试”并不表现为自动化测试,而是由“测试应用”、“跑一下”和“调试”等隐含手段体现的。

“测试应用”(Testing Application)

所谓“测试应用”并不是某个技巧正式的名字,但是所有人都熟知这一技巧:
构造一个简单的控制台应用(Console Application),并提供 main 入口函数(Entry point);
在 main 函数中,调用所编写的代码,并通过与控制台的交互(各种 println、writeline 之类的函数),将结果输出在控制台上;
再通过观察控制台的输出,判断结果正确与否。
让我们看一个具体的例子。假设我们需要将某个对象存储到数据库中,以 Java 中的 JPA(Jakarta Persistence API)为例,那么我们大概可以构造出这样的“测试应用”:
00:00 / 00:00
    1.0x
    • 2.0x
    • 1.5x
    • 1.25x
    • 1.0x
    • 0.75x
    • 0.5x
    网页全屏
    全屏
    00:00
    如视频中展示的,这个测试应用符合我们对于验证测试的一切认知:有需要被测试的行为,有明确的执行结果,以及针对结果的验证。那么我们实际上可以很容易地将它改写为自动化测试:
    00:00 / 00:00
      1.0x
      • 2.0x
      • 1.5x
      • 1.25x
      • 1.0x
      • 0.75x
      • 0.5x
      网页全屏
      全屏
      00:00
      对比这两种做法,从意图上,我们可以粗略地认为,它们是对于同一种意图的两种不同实现:无计划的手动验证有计划的自动化验证。所以如果你曾经使用过“测试应用”,那么你就曾经在项目中做过“程序员测试”。

      “跑一下”(Run it in a local testing environment)

      同样,“跑一下”也不是某个技巧的正式名字。从严谨的角度出发,“跑一下”甚至不能算是它真正的名字。它真正的名字应该叫“在我本地的测试环境中跑一下”。同样,所有人也都熟知这一技巧,就真的是“在我本地的测试环境中跑一下”。因为当代应用通常都在受控环境中运行(Managed Environment),所以当验证某个功能时,需要连通其所在的受控环境一起执行。
      让我们再看一个具体的例子。假设我们需要实现 REST API,以 Java 中的 JAX-RS(Jakarta Restful WebService)为例,那么我们大概会这样来跑一下:
      00:00 / 00:00
        1.0x
        • 2.0x
        • 1.5x
        • 1.25x
        • 1.0x
        • 0.75x
        • 0.5x
        网页全屏
        全屏
        00:00
        如视频中展示的,无论是通过浏览器直接观测结果,还是通过 Postman 验证,都符合我们对于验证测试的一切认知:有需要被测试的行为,有明确的执行结果,以及针对结果的验证。那么我们实际上可以很容易地将它改写为自动化测试:
        00:00 / 00:00
          1.0x
          • 2.0x
          • 1.5x
          • 1.25x
          • 1.0x
          • 0.75x
          • 0.5x
          网页全屏
          全屏
          00:00
          对比这两种做法,从意图上,我们可以粗略地认为,它们是对于同一种意图的两种不同实现:无计划的手动验证有计划的自动化验证。所以如果你曾经也类似这样“跑过一下”你的应用,那么你就曾经在项目中做过“程序员测试”。

          “调试”(Debug)

          我想你已经发现了模式,你肯定要猜测“调试”也是一种验证测试,但并不是这样!“测试应用”和“跑一下”这两种技巧更多地关注在发现问题上,可以看作是“验证测试”。而“调试”通常发生在已经明显知道有错误的代码中,是一个定位错误的过程。让我们来看个例子:
          00:00 / 00:00
            1.0x
            • 2.0x
            • 1.5x
            • 1.25x
            • 1.0x
            • 0.75x
            • 0.5x
            网页全屏
            全屏
            00:00
            如视频中展示的,“调试”是一种启发式过程(Heuristic Procedure),更像是探索测试(Exploratory Testing),根据出现的错误寻找可能出现错误的位置,然后设置断点,判断该断点处的状态是否正确。
            除了调试之外,我们还可以将代码划分成更小的单元,逐一排查以定位错误。那么我们就可以将对于某段代码的调试过程,转化成对于一组更小粒度单元的验证测试:
            00:00 / 00:00
              1.0x
              • 2.0x
              • 1.5x
              • 1.25x
              • 1.0x
              • 0.75x
              • 0.5x
              网页全屏
              全屏
              00:00
              在软件开发中,一直都存在验证性测试定位性测试两种测试。这也很好理解,我们既要知道代码有没有错误,还要知道当错误发生时,错误发生在哪里。
              从定位性测试的角度出发,对比这两种做法,从意图上,我们可以粗略地认为,它们是对于同一种意图的两种不同实现:手动的启发式定位有计划的逐模块自动化排查所以如果你曾经也类似这样“调试”过你的应用,那么你就曾经在项目中做过“程序员定位测试”。

              测试驱动开发的核心逻辑

              除去我们讨论的三种,在所谓的“正常软件开发模式”中,还存在很多其他常用的手段,也都可以看作是自发性的“程序员测试”。任何有过严肃编程经验的从业者,都能根据自己过往的经历,回想起这些年所做过的“程序员测试”。
              我们构造软件的过程,就是通过一系列验证测试(测试应用、跑一下等),证明我们在朝着正确的方向前进;如果验证的过程中发现出了错误,那么再通过一系列定位测试(调试等),找到问题的根源,加以改进。
              如此往复,直到完成全部功能(如下图所示)。
              现在让我们回到最初的问题:测试驱动开发过分强调测试对于程序员的重要性了吗?答案是:并没有!
              验证测试与定位测试,本身就贯穿了整个软件构造的过程。测试构成了整个开发流程的骨架,功能开发可以看作填充在测试与测试之间的血肉这就是测试驱动开发的核心逻辑:以测试作为切入点,可以提纲挈领地帮助我们把握整个研发的过程。
              一个个测试就像一个个里程碑点(Milestone),规划着研发活动的进度。围绕这些里程碑点,我们就可以持续对成本和效率进行管理和改进。也就是说,测试驱动开发将个体的开发活动,变成了工程化的研发过程。这也是为什么,三十年以来,测试驱动开发在敏捷方法族中,都扮演着工程实践基石的角色。
              因为测试是如此重要,我们需要非常高效地实现它们。那么“无计划的手动验证”与“手动的启发式定位”都是无法容忍的低效手段,必须将它们替换为“有计划的自动化验证测试”和“有计划的逐模块自动化排查”。从而才有了我们熟知的测试驱动开发(红 / 绿 / 重构循环),以及令没有做过测试驱动开发的人费解的对于自动化的偏执。
              到这里,我想你应该就会明白了,测试驱动开发并不是关于“怎么写测试”、“怎么消除测试人员”、“怎么让开发人员多干一份活”的编码技巧。它是以测试为里程碑点的工程化研发过程;同时,测试驱动开发将软件流程中无时无处不在的低效测试手段,以可重复的、高效的自动化测试代替,以获得更高的工程效能。
              这就是隐藏在测试驱动开发反直觉的工程实践背后的核心逻辑。

              为什么要学习测试驱动开发?

              测试驱动开发最直接的收益,就是可以提高开发者的工程效能
              工程效能不仅仅是开发功能的效能,还包含发现问题、定位问题以及修复问题的效能。从理论上来说,后面三个并不是根本的复杂性问题,但在实际中却大量存在,甚至占据一半以上的有效工作时间。因而高效地完成这些非根本性问题,就可以显著地提高效率。
              其中发现错误,并准确定位错误,通过发现问题的测试和定位问题的测试可以高效实现。而如果说发现问题的测试,还有后置或外包于他人的可能,那么定位问题的测试,无论如何都没有办法了。所以实际上高效能的研发过程至少需要我们提供可工作的代码,以及一组可用于定位问题的测试
              从这个角度出发,那么测试驱动开发仍是时至今日最具有工程效能的研发流程,没有之一。

              学习测试驱动开发的难点在哪里?

              学习测试驱动开发是困难的,很多信服于测试驱动开发理念而自发实践的人也会被各种问题困扰:
              测试从哪里来?为什么我写了很多测试,功能却没有进展?
              写什么样的测试既能驱动功能进展,又不会在重构中被破坏?
              社区里很多人都非常推崇单元测试,但我就是要测一段 SQL,单元测试怎么测?
              测试驱动开发从来都不是一种即插即用的技能,它是一种工作习惯和思维方式,背后还对深层的胜任力(Competency)——分析性思考有极高的要求。某种程度上讲,测试驱动开发有点像物理,定理写出来很简单,但需要我们在不同的场景下练习,才能应用得得心应手。
              正是因为这样的特点,我们的课程以视频展示为主,文字讲解为辅。希望你能在具体的场景下,体会使用 TDD 和平时开发的差异。具体而言,我们的课程是这样设计的。
              首先我们将从一个编码练习(Code Kata)级别的小例子入手,展示使用 TDD 开发的全过程。因为大多数人,对于 TDD 没有一个感性、直观的认识。因而在任何讲解之前,我需要让你亲眼看一看,如何通过 TDD 的方法实现一个非常简单的功能。
              然后我们会围绕这个例子,详细讲解 TDD 的核心理念与方法。我们将深入讨论 TDD 中的测试到底是什么样的测试,TDD 是如何驱动我们的开发。我们会介绍 TDD 的经典学派(芝加哥学派)与伦敦学派不同的切入点。
              在这部分的最后,我将会总结 TDD 作为工程方法的核心优势在什么地方。如果你需要说服周围的同事、朋友、领导开始采用 TDD 方法,这将给你提供足够的弹药。
              然后我们将进入实战项目环节。我将以几个技术框架为例(IoC 容器、RESTful 框架、DB Mapper 框架等),展示如何使用 TDD 的方式从头来实现它们,TDD 实战的细节将一览无遗。

              开篇寄语

              作为中国最早一批测试驱动开发的实践者,从 2003 年开始,我就将测试驱动开发作为主要工作方式了。
              在加入 Thoughtworks 之后,对内对外我讲述了大量测试驱动开发的课程。曾经有一段时间,每一位新入职的 Thoughtworker 我都会通过 6 周的时间,教会他们进行测试驱动开发。
              当我主持 Thoughtworks 委培生计划——小巨人项目(Small Giant Program)时。测试驱动开发与学习管理,是最早也是最重要的工作习惯。近年我研发的高效能工程方法 SEELE(Scalable Engineering Experience for Large Enterprise)也是将测试驱动开发作为核心流程,从而简化知识传递成本并提高杠杆率。
              测试驱动开发伴随了我职业生涯的每一个阶段。我相信,我掌握了测试驱动开发那天,我才成为了可靠、高效的职业程序员。如果你对程序员这个职业抱有严肃的态度,那么测试驱动开发是必须要掌握的。
              最后,我也很想听听你对 TDD 的看法,以及实践 TDD 过程中的体会。欢迎你分享在留言区,我们下节课再见!
              确认放弃笔记?
              放弃后所记笔记将不保留。
              新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
              批量公开的笔记不会为你同步至部落
              公开
              同步至部落
              取消
              完成
              0/2000
              荧光笔
              直线
              曲线
              笔记
              复制
              AI
              • 深入了解
              • 翻译
                • 英语
                • 中文简体
                • 中文繁体
                • 法语
                • 德语
                • 日语
                • 韩语
                • 俄语
                • 西班牙语
                • 阿拉伯语
              • 解释
              • 总结

              测试驱动开发(TDD)是一种工程化的研发流程,以测试为里程碑点,帮助开发者把握整个研发过程。文章通过讨论“隐式程序员测试”(Implicit Developer Testing)的概念,揭示了在正常软件开发模式中,程序员实际上一直在进行各种形式的测试。验证测试与定位测试贯穿整个软件构造过程,构成了整个开发流程的骨架。TDD强调以自动化测试取代低效的手动验证和定位测试,将软件流程中的低效测试手段替换为可重复、高效的自动化测试,从而提高工程效能。TDD并非仅仅关于“怎么写测试”,而是关于工程化的研发过程,以测试为里程碑点,提高工程效能。文章深入浅出地解释了TDD的核心逻辑,为读者提供了对TDD的全面理解。 在学习测试驱动开发时,可能会遇到一些困难,如测试从哪里来、写什么样的测试能够驱动功能进展、以及如何在不同场景下应用TDD。作者以视频展示为主,文字讲解为辅,通过编码练习和实战项目环节,帮助读者深入理解TDD的核心理念与方法。作为TDD的实践者,作者强调TDD对于提高工程效能的重要性,并鼓励读者分享对TDD的看法和实践体会。 总之,本文通过深入讨论TDD的核心概念和方法,以及作者的实践经验,为读者提供了全面的TDD理解和学习指导。文章内容丰富,适合对测试驱动开发感兴趣的技术人员阅读。

              2022-03-1680人觉得很赞给文章提建议

              仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
              《徐昊 · TDD 项目实战 70 讲》
              新⼈⾸单¥98
              立即购买
              登录 后留言

              全部留言(38)

              • 最新
              • 精选
              • 🐑
                置顶
                TDD专栏福利大合集: 1、打卡赢好礼(4月23日-5月10日):正在进行中,学习专栏第1-10讲并在留言区打卡,结束后奖励; 2、代码亲手评(5月底):预计打卡结束后启动,完成前10讲的打卡,即可提交代码练习作业,徐昊老师会亲自点评; 3、线上带你练:根据专栏更新节奏和老师时间安排确定,徐昊老师会线上带四个同学手把手地改代码,敬请期待! 具体活动介绍见 👉 http://gk.link/a/11jPi
                2022-04-28
                1
              • 俊伟
                之前拜读《测试驱动开发》觉得这种方法真了不起,之后在工作中也是一直坚持实践。实践TDD真的可以让开发效率变快。现在写代码,早就把Postman之类的工具抛到脑后了,完全通过单元测试来构建可靠的应用。个人感觉使用TDD是一个先慢后快的过程,开头可能会慢一点,但是越往后测试越多,代码写的越快,真的很神奇。

                作者回复: good for you

                2022-03-17
                16
              • 术子米德
                🤔☕️🤔☕️🤔 * TDD带给我的最大改变,那就是先去设定验收的形式和手段,即确定验收的边界,然后整个实现都在趋近这个边界。这种方法刚开始用,很难受,原因在于自己经验不足,不知道怎么验收自己的成果。随着经验的增加,知道如何准确验收自己的成果,就会自然而然TDD。如果是分派任务给大家,个人实践下来,TDD也是优质武器,就是在分配前,对将来交付的内容有验收的预期,会极大提高交付的质量,更不会在交付时带来惊吓级别的惊喜。 * 所以说,TDD在开发初级,会有难受,在开发中级,会自然形成。如果某个所谓的资深,还在说TDD的优劣势,尤其各种不对付,那么大概率他没有体验过一把交付无缺陷的代码,更大概率他卖下过巨量技术债务,要么他本人还在还债,要么已经把债务转嫁给某些无辜开发。

                作者回复: you got it

                2022-03-27
                6
              • CV
                对于tdd的理念非常认可,但实际工作中接触的很多都是老项目,一个方法几百行起步,各种反射、静态方法,想写测试感觉都无从下手,更别说tdd落地。希望老师可以讲讲老项目如何开展测试工作

                作者回复: 可从任务分解入手

                2022-03-20
                4
              • tdd学徒
                很硬核 还配有字幕 感觉可以不用录音 文字加视频就好了

                编辑回复: 嗯录音是专栏的交付形式之一,在这个专栏里,我感觉可以不用听录音,看文字和视频就够了

                2022-03-17
                2
                2
              • 钱忠飞
                请问课程中有springboot下tdd落地的内容吗?

                作者回复: 没有 有自己实现一个简单的spring的内容

                2022-03-17
                2
              • Sarah
                内容非常的不错,在工作中也一直在实践,并尝试跟测试用例结合,以达到减少手动测试的目的。 对于这个学习形式,个人认为音频加视频的形式,有点打断学习的体验了,感觉可以都用视频的形式就好,不然要一直音频和视频之间切换,体验感不佳

                编辑回复: 我觉得,音频都可以不用听,看文字稿➕视频就好咯

                2022-03-16
                2
              • 爱乐之城
                老师讲的这三类场景日常前端开发中也会碰到,甚至更频繁,比如页面的展示就可以算是「跑一下」验证。请问老师对于前端该写哪些范围的测试,有建议或者单独的章节来讲吗?

                作者回复: 后面会有建议

                2022-03-18
                1
              • Geek_fa4b49
                非常高兴徐老师能给大家一个如此基础又如此重要的课程,几乎所有公司都在强调单元测试的重要性,但是我看过很多代码,基本都没有单元测试,好一点的团队是刚开始有单元测试,后面就没有继续维护了。大家都这么重视单元测试,但是为什么都没有坚持下来呢?主要是单元测试成为了项目开发的累赘,没有用TDD。如果是先写代码,再写单元测试,很容易给人一种多余的感受,而且为了测试而测试,就会导致测试无群无尽,甚至连整数的边界都纠结要不要一块测试了。如果用TDD,从test case的角度去设计测试,就不会存在这样的问题。 文章的一个观点非常重要,我们并不是写测试想取代测试人员,而是我们换了一种高效的测试方法而已。我们让测试粒度更细,测试自动化,提高测试的效率。无论如何程序员自己的白盒测试都是要做的,那我们使用TDD这种好的工程实践方法,可以使我们变成高效可靠的程序员。

                作者回复: 定位测试 白盒还是别做了

                2022-03-17
                2
                1
              • 临风
                工作快一年了,刚开始就知道了TDD,但是一直没有得其要领。后面也接触到了alpha测试,有了自己对开发测试的思考,但始终没有找到一个最佳实践,而自己对测试的理解也在一次次的开发中不断的变化。一开始初生牛犊不怕虎,看到质量较差的代码就想重构、重写,到后面逐渐不敢去碰了,保证自己的代码质量就行了。看着那些老代码,有种心有余而力不足的感觉。自己开发的代码一般都会有测试代码的覆盖,但并不是按照TDD的流程,交付时间又紧张,如果不写测试用例,就能更早是转测代码,有时也会怀疑自己写这些测试用例真的有意义吗,是不是只是自己的自我感动罢了?这次课程,感觉是打了瞌睡来了枕头,非常感谢老师和极客时间这个平台,希望未来自己能真正用好TDD,提高代码质量。

                作者回复: 早转测试意义不大 减少返工是目的。毕竟做的极端,可以一行代码都不写就转测试

                2022-03-20
              收起评论
              大纲
              固定大纲
              隐式程序员测试(Implicit Developer Testing)
              “测试应用”(Testing Application)
              “跑一下”(Run it in a local testing environment)
              “调试”(Debug)
              测试驱动开发的核心逻辑
              为什么要学习测试驱动开发?
              学习测试驱动开发的难点在哪里?
              开篇寄语
              显示
              设置
              留言
              38
              收藏
              68
              沉浸
              阅读
              分享
              手机端
              快捷键
              回顶部