扫码阅读
手机扫码阅读

聊聊测试驱动开发

123 2024-01-31

这是鼎叔的第六十四篇原创文章。行业大牛和刚毕业的小白,都可以进来聊聊。

本文观点参考自Lasse Koskela,他是《测试驱动开发的艺术》的作者。

软件缺陷通常是由低质量的代码引起的,但是在复杂项目中,要维护这些代码简直就是噩梦。新加入的开发者想对它进一步修改,更是举步维艰。测试驱动开发,或许能解决这个问题,利用测试构建出高维护性和满足客户需求的软件,它也是XP(极限编程)的核心实践。

我们常说的TDD,通常指细节层面的UTDD(单元测试驱动开发),以测试驱动的方式编写开发。行业还有一个概念- ATDD(验收测试驱动开发),指在较高层次(特性功能层),以测试驱动的方式构建系统。前者保证内部质量,后者保证可见的外部质量。

TDD带来高质量和高效率

提倡短周期的TDD从一开始就能保证较高的代码质量,它完全颠倒了软件开发的旧方式(即先设计,然后编码实现,再测试),即先写测试描述目标,然后写代码达成目标,最后重构改进设计

完全可测试的代码和不断演化的简洁设计,能避免开发陷入代码越写越糟的恶性循环。TDD保证了所有代码都是可用,且被测试覆盖的。由于不用实现考虑实现细节,我们可以从更健壮的角度思考函数接口行为和外部调用方式。

写代码要基于测试先行的小步前进,增量式构建系统,在发生错误时很容易定位代码行,这样可以大幅节约后期的调试时间。我们也就有时间做更有意义的事,如清理代码,学习工具及技术。

而ATDD则是把客户需求全部转换成可执行的具体功能测试,拉近了客户和开发者的距离。在ATDD中我们更关注系统行为的测试,而非对象行为的测试。

TDD和ATDD非常互补,ATTD能驱动开发过程(做正确的事),而每个功能点上则使用TDD(正确地做事)。它们的组合让我们对交付的代码更有信心。

TDD三步曲(测试-编码-重构):正确地做事

三步曲代表着红(测试失败)-绿(测试通过)-重构代码的状态迁移。

写恰好足够的代码,仅仅是为了修复当前失败了的测试,不要一下子写出整个功能的代码再来花很长时间让测试通过。

所谓增量式开发(小步快跑),就是在“实现新功能”和“调整设计”两件事中来回切换,采用更经济的演进式设计方法。我们需要为可能的变化进行准备,但又要避免做无用功。最后的重构则是“在不改变外在软件行为的基础上,改进程序的内部结构”。

TDD整个过程要遵守严格的纪律,每次修改后运行自动化回归测试,用“测试PASS的信心”来交换开发速度。

ATDD-做正确的事

增量式开发模式中,客户有权决定哪些功能优先开发,从而被激发对项目的热情,而验收测试(AT- Acceptance Testing)则是整个团队(开发,测试,产品,客户)沟通的共同语言。“以需求文档为沟通媒介”很难清晰地表达出意图,而“以测试为规约”则更加精准、可靠和直接,缩短反馈周期。

测试驱动开发的工具支持

针对单元测试级别的TDD工具统称为xUnit。而ATDD的测试框架类型就更多了。前些年软件公司使用的最热门的验收测试工具是Fit/Fitness,表格形式的工具可以让非技术背景的客户和产品经理一起参与测试。Fit表格关联了测试夹具,可以自动执行表格内容的测试,并显示对用户友好的、多彩的测试结果。

此外还有纯文本的测试工具,比如通过关键字驱动或者利用日志来测试。

持续集成基础设施也是至关重要的保障,因为采用TDD的团队会共享代码,任何人都可以修改代码。

静态代码分析工具和代码覆盖率分析工具,对刚采用TDD的团队可以具体指出代码测试的不足,这个帮助很有必要。

TDD实践步骤详解

第一步:从需求到测试

开发者首先要把需求划分为要做的事(任务),用测试的形式来表达任务有利于我们记住要“完成”的定义,避免脱离了用户需求。一个好的“测试”应该是原子化的,独立的。

我们如何为一段还不存在的代码写测试呢?这需要我们想象产品代码应该如何易用,这种想象可以称之为“意图编程”。我们把注意力集中在“能有”的,而不是“已经有”的东西上,把需求分解为一系列小的,紧凑的测试。

第二步:用TDD开发模版引擎

第一步得到的测试列表是一个活文档,可以根据我们的进展而不断添加,接下来要让它们挨个通过。用自己认为合理的方式设计模板引擎的工作方式,把模板文本作为参数传给构造函数,验证结果与期望是否一致。

当然,编译器一开始会报错,因为某些类根本不存在,我们添加类,继续补充相应的方法。然后运行测试,测试必然会失败(因为我们还没有实现这些方法),继续补充最基本的产品代码,直到第一个测试通过,这个过程要尽可能简单快捷。

对于复杂逻辑的实现,我们可以采用广度优先或者深度优先的方式。如果是广度优先,我们会集中实现高层的功能,低层功能暂时用伪实现。若采用深度优先,我们会先实现底层功能,在所有底层功能都实现后才会组合来实现高层功能。

第三步,清除伪实现(尤其是硬编码),重构代码。

验证添加的测试确实被执行了,测试列表都通过了(包括特殊情况的数据测试),我们通过重构避免代码的“腐坏”,清理重复和冗余的代码,移除多余的测试。利用夹具使测试更加紧凑,用测试替身替代真实对象。

第四步,添加错误处理,最后让代码尽量精简。注意验证异常中的详细信息,保持方法中的代码抽象层次的一致性,这样会提高代码的可读性。

第五步,增加更多的系统测试,如耗时的性能测试。测试替身可以提高测试速度,降低依赖性。

TDD的指导原则

总之就是:绝不跳过重构,尽快变绿,犯错后减慢速度

为了提高可测试性,设计上要尽量使用组合而非继承,掌握参数化测试手段(数据驱动测试),正确恰当地隔离依赖。

如果我们是在糟糕的遗留代码上进行TDD,首先要进行代码分析,确定变更点代码,进而确定测试点,从近距离测试,也从远距离寻找合理的测试点(如网络和日志),小心地移除某些依赖,并暴露依赖的接缝。一旦有了足够的测试覆盖,就可以放心地引入变更。

集成测试中的TDD

TDD并非只对应单元测试,集成测试也是用来做TDD的。和单元测试的不同在于,集成测试会真实地访问数据库,访问文件系统,花费的时间更多,需要更完整的基础设施,可能需要改变数据库模式,进行针对性的重构。不足之处是集成测试难以模拟特定的异常场景。建议我们充分利用单元测试和集成测试两者的优点来实践TDD。

多线程并发是TDD实践的难点,代码中的任何同步都会对相邻线程的并发性产生影响,因此并行编程出错的可能性更大,还可能遇到“死锁”和“饥饿”等现象。我们需要针对“线程安全”进行编码,在测试中尽量避免用“钩子”来控制产品代码的执行过程,以及等待验证异步调用的结果。

ATDD的进一步阐述

验收测试是用业务问题领域的语言来描述的测试用例,描述简洁准确,无歧义,侧重于“做什么”和原因,而非“如何做”。最终的所有权属于客户(利益相关方)。测试角色在团队中既属于领域专家,也属于技术专家。

验收测试的格式,可能是声明式的表格结构,因此验收测试自动化工具可能和系统实现的语言不同,强调客户容易理解,简单易懂。ATDD要在功能层面保证“软件做了我想要的事情”,而不是从技术上保证。

一个ATDD过程周期非常简单,分为:挑选一个用户故事(从需求中拆解)、写测试用例、自动化测试、实现功能,这四大步骤。

建议一步步实现验收测试,而不是一下都实现。大部分团队都会自己实现验收测试,不依赖于专门的验收测试人员。

注意,实现功能这个“第四步”,就可以扩展为一个或一系列的TDD小周期(测试-编码-重构),这样ATDD和TDD就在不同层次上形成紧密协作的闭环,相辅相成。

ATDD给出了需求完成的“定义”,通过有意义的例子,而不是复杂模糊的描述来表达需求。每个人都会贡献自己特有的知识和技能来解决问题。客户能看到自己的需求被真正满足了;开发人员看到客户参与验收测试,并认可了自己代码的价值。

验收测试是否应该操作真实用户的外部界面?答案是“看情况”,如果真实界面难以访问,或者其反馈成本高、性能慢,我们也可以绕过界面,通过API或者内存数据库来进行验收测试。但是在这么做之前,我们先要确认替换后的被测系统和替换之前是否足够相近,或者不得不这么做。

验收测试不必测试所有东西,而是聚焦用户故事的本质特质,同时避免波动频繁带来的维护性问题,选择技术障碍最小的地方越过它。

实现ATDD的方式主要有:

1 端到端。理想情况下,应该把被测系统当成一个整体,端到端的视角,和客户观察系统的角度一样,最能体现系统的真实情况,以及对系统的广泛覆盖。但是这种测试太脆弱,尤其UI变更频繁,同时速度也慢。

2 绕过UI的测试,即绕过系统的壳,避免了不必要的改动,调用抽象UI或者API来访问系统内部。这样易于实现且执行速度快,但会让客户困惑,也需要手工测试弥补图形界面的测试质量。

3 直接测试内部逻辑。利用验收测试工具,把业务逻辑隔离再几个精准的测试中。它和单元测试的不同在于,前者是用客户领域的语言编写的,后者是为开发者编写的。

写在最后,在ATDD技术实践上可以用到的技巧还包括:

1 把系统的一些非关键构件替换成测试桩或者仿真器,利用测试后门(替代性接口)等。

2 加快测试执行速度。比如检查所有的测试是否真的需要这么多的初始化,以及用一次初始化完成一批用例的前置准备。

比如把测试套件分为两组,一组是有副作用的(如写数据库),一组是没有副作用的,先执行后者。

还有,减少磁盘I/O访问的动作,或者分布式执行任务,利用好负载均衡。

3 减少测试的复杂度。如消除代码重复,优化命名,利用公共函数把验收测试组织成有机的整体,利用缓存环境对象。

4 管理好测试数据。在保持好自动化验收测试的代码干净整洁的同时,提高测试数据的可管理性。如把测试数据小块化,或动态产生测试数据,以及对测试数据做好版本控制。

原文链接: http://mp.weixin.qq.com/s?__biz=MzkzMzI3NDYzNw==&mid=2247484254&idx=1&sn=f48dd00832d9fb7da108f4ceeb63942b&chksm=c24fb63cf5383f2a817fcb8ec3b971e82fc21ce29e3c8260e487b340ab3274c522d9e423f3f4#rd

《无测试组织-测试团队的敏捷转型》主题探讨。从打造测试的组织敏捷,到敏捷测试技术的丰富实践,从一线团队的视角来聊聊我们是怎么做的。面向未来,拥抱敏捷原则,走向高效能组织。

81 篇文章
浏览 15.1K
加入社区微信群
与行业大咖零距离交流学习
软件研发质量管理体系建设 白皮书上线