HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

TDD 实践之实用主义

  • 2008-10-20
  • 本文字数:4572 字

    阅读完需:约 15 分钟

TDD 的概念和实践依然在被怀疑和争议。然而,质疑者驻足不前,实践者却不再理会争论的口水,而是脚踏实地的实践,并获取高效回报。在这个过程中,我们发现 TDD 和敏捷倡导的理念,帮助我们解决了一个又一个的工程问题。

1. 为沟通选择语言

我们在一个海员管理系统的开发中遇到了问题,这个领域的专业术语我们很难翻译。即使勉强翻译出了,也感觉辞不达意,无论是初看上去,还是过一段时间再看都一头雾水。比如,我们写出了下面的测试用例:

public void test_should_return_NOT_pass_if_duty_higher_than_second_mate_or_second_engineer_and_education_level_is_secondary_and_guraduated_after_2002_02_01() {<br></br> ……<br></br>}但其中second mate/second engineer是什么意思呢? secondary 的 education level 具体又是什么?

还有:

public void test_should_return_third_mate_course_for_jianxi_third_mate() {<br></br> ……<br></br>}``jianxi_third_mate是什么? 等等。

当然,我们可以制定一个术语表,请专业人士先帮我们翻译好,然后在代码中遵循这个术语表。然而随着需求的增加,术语层出不穷,并且有特定中国特色的名词根本就没有对应的翻译,于是这个问题就一直困扰着我们。

而直到有一天,在一次重构中我们把上面的第一个测试用例重命名了一下,一切似乎突然间开朗了:

public void test_ 应该算未通过 _if_ 职务高于二副二管轮 _ 而 _ 学历只是中专 _ 并且 _ 毕业时间晚于 2002 年 2 月 1 日 () {<br></br> ……<br></br>}Team 里的人纷纷围过来,看着这个跟需求描述里的验收条件几乎一模一样的测试用例名称,感受到一种前所未有的清澈。大家几乎在几秒钟之内就做出了选择:这种形式是可以接受的,而且表达能力更强,交流效果不错。

什么? 使用中文作为函数名? 这似乎只是那些被主流舆论鄙视的"汉语编程"研究者才搞的东西,我们一直就被教育离这些东西远点,甚至汉语拼音都不推荐使用,一个经常拿来做反面教材的例子就是数据库表的列名使用汉语拼音,这被看作不专业的表现。又或者,以后团队中加入外国开发者怎么办?

幸运的是,我们是软件工程师,不是计算机科学家。学术理论可以极端,而工程一定是某种折衷。定理由自然界精确遵守,而工程却是各种应力的人为平衡。

具体到这个案例,让我们正视现实:

  1. 团队成员并不善长本项目领域的专业英语。
  2. 任何翻译都会造成一定的信息损失,尤其在一些具有中国特色的领域,比如"中专"翻译为英语就很难像中文一样简洁直观。
  3. 在可预见的将来,不会有老外加入开发团队。

而选用中文却能够让我们更好的坚持以下原则:

  1. 代码除了完成功能, 另外一个重要的功能是交流。(我们选择了对团队来说最有效的交流方式)
  2. 用测试用例的名字来描述需求。(用中文描述更精确, 易于理解)

当然我们也会失去一些东西,比如对上面提到的"应该坚持使用英文"原则的放弃。在这里我们认为放弃这条原则的收益大于损失。一种损失就是失去了学习英文的机会,比如上面最后一个测试用例,用中文写出来就是:

public void test_ 见习三副应该参加三副的培训 () {<br></br> ……<br></br>}或者有人会说:“见习"的英语是"intern”,常用词啊。然而系统中还有另外一类角色,叫"实习三副"等,那才是"intern"。实习是实际动手,担当实际的职责;见习是只看不练,跟在后面观摩学习。见习的英文单词是"noviciate",并不为项目组所熟悉,而我们也不再关心它。

总之,在实践中应当权衡各种利弊,选择对你来说最有效的方式。

2. 用大量测试来驱动

在项目组中曾经发生过自以为完成了某个特性,后来却发现漏掉了某些验收条件,甚至是比较重要验收条件的事情。而这也是 TDD 经常被质疑的一点,就是如何保证测试的完备性。因为总是想一点写一点,不经过深入的思考,不可避免的会漏掉某些测试条件。

然而,TDD 并不妨碍你深入思考,只是劝你在实现时步子不要太大,小步前进获得对问题的进一步认识以随时调整设计和实现。不过今天不争论 TDD 的哲学问题,我们只是关注用什么样的实践来消除对于 TDD 测试完备性的疑虑。

事实上,完全可以根据需求文档,验收条件,进行"深入思考",从一开始就写下所有能想到的单元测试用例,就跟测试人员在产品出来前就对着需求准备测试用例一样。

哦,等等,这还叫 TDD 吗? TDD 所强调的小步前进,随时应变哪里去了?为全面的测试用例所花费的大量努力岂不是有浪费的风险?

嗯,不错,TDD 强调小步前进的原因就是要避免浪费。如果我们能找到一种方法,既能够提醒我们不要忘记需求,又让我们在需求变化时不致浪费太多,岂不是皆大欢喜?

想想,我们用什么来描述需求? 是测试用例名称,而不是测试用例的函数体,而名称的书写几乎是没有成本的。从需求文档中把验收条件抠出来即可。如:

public void test_ 会被认为不服从调配 _if_the_seaman_ 在当前职位上曾经旷工 () {<br></br> // TODO<br></br> }<p> public void test_ 会被认为不服从调配 _if_the_seaman_ 在当前职位上曾经请过病假 () {</p><br></br> // TODO<br></br> }<p> public void test_ 会被认为不服从调配 _if_the_seaman_ 在当前职位上曾经请过事假 () {</p><br></br> // TODO<br></br> }<p> public void test_ 会被认为不服从调配 _if_the_seaman_ 在当前职位上曾经被遣返 () {</p><br></br> // TODO<br></br> }<p> public void test_ 不会被认为不服从调配 _if_the_seaman_ 在当前职位上从未旷工 _ 请病假 _ 请事假 _ 和遣返 () {</p><br></br> // TODO<br></br> }两分钟,我们就把这个用户故事的测试用例按照验收条件里说的全部描述出来了。函数体全部都是空的,因此所有的测试都是通过的,不会强迫你一次性把所有的测试都实现。

每次你流览或修改这段代码,空的函数体或里面的// TODO都会提醒你还有测试没有完成。即使你不在这个特性上工作了,切换过来的 Pair 也会从你遗留的测试用例名称中迅速的了解需要做什么。

这种方法是怎么解决问题的呢?

第一,还是让我们正视现实:如果需求描述不和代码放在一起,开发人员很少会在开发过程中去翻阅需求文档,甚至是特性编码结束后。这在成熟的开发团队中会有改善,但仍然不可避免。把需求描述以测试用例名称的方式放进代码,便会无时无刻不在提醒开发者,还有这个这个这个验收条件没满足。

第二,我们依然坚持了以下原则:

  1. 用测试用例的名字来描述需求。
  2. 小步前进,编写一个测试用例,实现一段产品代码,编写下一个测试用例,实现下一段产品代码。(因为所有的未完成测试都是通过的,不妨碍你运行测试,提交代码和持续集成)
  3. 当实现过程中发现事情并不是当初想的那样时,随时更改或删除之前写的测试用例,不会造成大的浪费。(因为只是函数名加空的函数体,成本很低)

在开始写下"所有"的测试用例名称并不意味着一劳永逸。中间当需求发生变化,我们需要对应的添加或删除一部分测试用例。在实现的过程中,发现某些条件不在验收范围内,或许是之前没考虑到,那么跟 BA/QA 确认后,需要添加到用例列表中。

(细心的读者可能发现,这里面存在着重复。就是以普通文本形式存在的验收条件和以测试用例名称存在的验收条件。或许应该有类似 SVN2Wiki 的工具,来消除这类重复)

当然,完备性还有其它的含义和检测条件,不是说你提前多写几个用例就是完备了。这里只是用一种成本最低的方式来解决问题。

3. 一个环境,多个断言

随着时间的推移,项目组发现测试运行的越来越慢。当然,这是一个普遍现象,也已经有很多方法来加速测试的运行速度。但很多需要新工具的支持,而项目组暂时没时间去切换工具。有没有其它更方便的做法?

像其它性能问题一样,我们首先需要确定瓶颈在哪里。我们发现主要是每个测试用例运行前搭建测试所需的环境相对较为耗时,尤其是 Selenium 测试,它需要启动和关闭浏览器。并且,很多测试用例其实使用相同的环境设置,只是每个用例仅仅去断言其中一个需求。

我们可以修改 Runner,让一组测试使用同一个浏览器实例,每次环境的清理通过清理 Session 来完成。而我们也可以采取另外一种方法,就是合并使用相同环境设置的测试用例,把它们的断言都放进同一个用例。

哦,这又违反了 Kent Beck 为 TDD 制定的原则:每个测试用例最好只有一个断言。

好,让我们再一次分析原则背后的理念。一个用例一个断言,是为了让测试更清晰,更精确的描述需求,测试失败更容易定位。那么有没有一种方法,既能让多个断言共享相同的环境设置,又能清晰精确的描述需求呢?

想想,我们用什么来描述需求? 是测试用例的名称,确切的说,是函数的名称。只要我们把一组组相关的断言封装到一个个函数里,给它们一个能够清晰精确的描述这组断言对应的需求的名称,然后在测试用例里面调用这些函数就可以了。这样我们只需为多个断言设置一次环境,而同时又保留了清晰精确的表达需求的能力。

@Test<br></br> public void test_should_show_step_details_info_in_todo_item_page() throws Exception {<br></br> TodoItemPage page = navigator.gotoTodoItemPage( );<p> should_show_step_name_as_page_title(activeStepOfNonStartedInstance, page);</p><br></br> should_show_start_processing_button_if_current_step_status_is_waiting(page);<br></br> should_show_transition_buttons(activeStepOfNonStartedInstance, page);<br></br> should_NOT_ask_user_to_input_his_opinion_if_current_step_status_is_NOT_processing(page);<br></br> should_show_comment_box_after_click_start_process(page);<br></br> }<p> private void should_show_step_name_as_page_title(FlowStep step, TodoItemPage page) {</p><br></br> assertEquals(step.getName(), page.title());<br></br> }<p> private void should_show_start_processing_button_if_current_step_status_is_waiting(TodoItemPage page) {</p><br></br> assertTrue(page.isStartProcessingButtonVisible());<br></br> }<p> private void should_show_comment_box_after_click_start_process(TodoItemPage page) {</p><br></br> page。clickStartProcessingButton();<br></br> assertTrue(page.isCommentBoxAppear());<br></br> }<p> private void should_ask_user_to_input_his_opinion_if_current_step_status_is_processing(TodoItemPage page) {</p><br></br> assertTrue(page.isCommentBoxVisible());<br></br> assertTrue(page.isActionButtonsVisible());<br></br> }<p> private void should_ask_user_to_select_next_step_operators(FlowTransitionDefinition nextTransitonOfStep,</p><br></br> TodoItemPage page) {<br></br> assertTrue(page。isUserGroupVisible(nextTransitonOfStep.getId()));<br></br> }<br></br> ……## 小结

回过头来我们看看上面的三个实践,它们如出一辙的,一次又一次的"违反"了某种原则。它们分别是"不能用汉语",“不能一次编写多个测试用例”,和"不能在一个用例里面使用多组断言",而实际上,我们违反的只是这些原则的外在形式,但却坚持了这些原则背后的思想,如最有效的沟通,注重实效而不是形式。以此为基石,我们可以在出现新的约束的情况下,灵活运用,发明各种实践,并享受由此带来的效率提升。


作者简介:李光磊,软件工程师,同时还是一位敏捷教练,就职于 ThoughtWorks。他还是活跃的 blog 作者,了解他最新的想法,请访问 http://blog.csdn.net/chelsea

2008-10-20 01:023462

评论

发布
暂无评论
发现更多内容

必看!文档版本管理工具大比拼

爱吃小舅的鱼

文档管理

基于“日志审计应用”的 DNS 日志洞察实践

阿里巴巴云原生

阿里云 云原生 sls

6 个Spring tx 事务注解:4种隔离&7种事务传播业务案例(必须收藏)

肖哥弹架构

Java spring 注解

Serial for Mac v2.0.17激活版 全功能串行终端管理软件

Rose

极简接入|七牛云 QPlayer2 播放器再升级

七牛云

音视频开发 播放器

小程序技术为什么是轻量级前端架构?

Geek_2305a8

香城档案利用 NocoBase 快速实现智能档案管理

NocoBase

低代码 无代码 档案管理

新手指南:轻松选择生产工时管理系统

爱吃小舅的鱼

工时管理

Parallels Desktop 19完美破解版 附PD虚拟机永久密钥

Rose

Parallels Desktop 19 Parallels虚拟机下载 Mac虚拟机安装 PD19密钥

锐起安全会议室方案:提升涉密会议效率与安全级别!

上海锐起科技

信息安全 文件管理 涉密会议

如何挑选符合你需求的任务管理工具

爱吃小舅的鱼

待办事项管理

小小的引用计数,大大的性能考究

bin的技术小屋

Netty Java' netty

如何做好API安全

德迅云安全杨德俊

倒计时3天!数智时代下大数据应用的“道”与“术”闭门会议即将开幕

望繁信科技

数字化转型 流程挖掘 流程智能 智能化应用

IntelliJ IDEA中文版安装教程 附IntelliJ IDEA永久激活码2024最新

Rose

代码编辑 IntelliJ IDEA中文版 IntelliJ IDEA2024安装 intellij idea激活码2024

扬帆蓝海,智起未来!和鲸科技助力第十三届全国海洋航行器设计与制作大赛智能感知赛道精彩收官!

ModelWhale

TON链上游戏项目开发基本要求及模式创建与海外宣发策略

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

如何将MySQL迁移到TiDB,完成无缝业务切换?

NineData

MySQL 迁移 TiDB 迁移复制 一键迁移

mac防火墙软件Radio Silence 完整激活版 支持M1/M2

Rose

MacDroid Pro:Mac电脑和Android设备之间的文件传输和数据管理

Rose

数据传输 MacDroid pro 安卓手机数据传输助手

币价与数据持续低迷,比特币和以太坊能否从低谷中恢复?

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

简历,需要和求职岗位匹配

老张

裁员 职场成长 求职面试

监理单位项目管理系统选型必看指南

爱吃小舅的鱼

项目管理

fxfactory视觉特效下载 FxFactory 8 Pro mac破解资源

Rose

FxFactory Pro 8 fxfactory视觉特效

企业数据怎么定义?包含哪些?如何保护企业数据?

行云管家

数据安全 企业数据安全 企业数据

泉州等保测评机构电话是多少?在哪里?

行云管家

等级保护 等保测评 泉州

Word 2021 LTSC for Mac永久破解版 含word激活工具 支持M1/M2

Rose

Word 2021 许可证 Word 2021破解版 Word 2021 mac

中文汉化版Bartender 5 mac下载 菜单栏图标管理软件

Rose

Bartender 5中文版 Bartender 5破解版 Mac菜单栏管理工具

大数据处理与智慧营销系统性能优化

鲸品堂

大数据 营销 流程化 企业号2024年8月PK榜

coconutBattery Plus:苹果mac电脑 电池健康检测工具

Rose

一站式系统清理维护工具MacBooster 8 Pro Mac中文版

Rose

苹果电脑系统优化 MacBooster 8 Pro 系统清理维护 MacBooster 破解版

TDD实践之实用主义_研发效能_李光磊_InfoQ精选文章