感恩联想公司的匠友喻泽高帮忙为本次活动提供场地!
* 时间:2014.07.20, 2:00-6:00pm
* 地点:联想北京研究院
* 参加人数:7人
* 活动主题:手写Stub来为Tire Pressure Monitoring System已有C++代码编写单元测试
* 伍斌完成该题目的源代码:请点击本微信左下角“阅读原文”链接
https://github.com/wubin28/tire-pressure-exercise-cpp-dojo/tree/my-solution
* Tire Pressure Monitoring System题目描述:该题目是ThoughtWorks Studio的培训师和教练Luca Minudel在两年前设计的编程操练系列题目TDD with Mock Objects中的一个,实现了汽车轮胎胎压报警的功能。该题目有两个类Alarm和Sensor,其中Sensor类负责获取轮胎气压值,而Alarm类则检查Sensor传来的气压值,若在正常范围之外则报警。本操练要求为Alarm类编写单元测试。最后在测试的保护下实现一个新功能:每次检查胎压前,都需要将状态重置到非报警状态。
活动过程
1. 各位匠友自我介绍与编程中最大的问题收集
2. 伍斌现场编程演示用接口提取的方法编写Stub来为Alarm添加单元测试,并实现新特性
2. 各位匠友定时自由结对编程操练上述编程题目(每对7分钟)
4. 回顾与问题解答
回顾要点
Well
* 活动场地高大上、各位匠友水平高。
* 编程道场中各个环节的内容安排比较合理。
* 讲师讲解很细致。
* Ben的引导功力很强大,期待在这方面分享一下。
* 结对编程的环节很有乐趣。
* 编程操练实践中学习了如何编写mock/stub。
* 重温了C++。
* 编程时用编译、链接和运行的错误来驱动生产代码的编写的做法很新颖。
Less Well
* 对于NetBeans这个IDE不大熟悉,影响操练速度。
* 对Google Test测试框架不大熟悉,影响操练。
* 周末写字楼不提供空调,较热。
Puzzles(含Ben个人的解答)
1)如何做单元测试?是做语句覆盖,还是条件覆盖?
答:就如同编写代码时,要针对抽象,而不是针对具体实现编程一样,编写单元测试也要针对用户意图,而不是针对已有生产代码的具体实现来编写测试代码。这里用户意图就是抽象。如果测试代码能做到用户意图的全覆盖,由于用户意图的可变性比生产代码的具体实现来说要小很多,那么用户意图覆盖测试的健壮性比语句或条件覆盖测试要好很多,同时也能更有利于和用户沟通。
2)在代码无单元测试的情况下如何写mock?
答:本次编程操练就是操练这个问题的解决方案:首先找出用户意图,写下能想到的所有用户意图TODOs,然后依次对这些TODO编写单元测试,并编写使用mock的意图代码,并用编译、链接、运行的错误来驱动生产代码的重构。最后让所有用户意图的TODO测试运行通过。
3)代码烂到一定地步了,难以找到修改点怎么办?
答:用查看log或debug调试来找到问题的发生点,然后阅读代码,必要时手绘草图。最后根据这块代码的用户意图来编写测试,并在测试中编写意图代码,必要时使用接口提取或子类化并重写的方法来把已有代码解耦纳入单元测试,并用编译、链接、运行的错误来驱动生产代码的重构。最后让所有用户意图的TODO测试运行通过。
4)系统很大,感觉编写单元测试无从下手,该怎么办?
答:软件开发顾问姚若舟说:“在没有任何业务价值驱动的前提下进行重构是没有意义的。一边增加新功能(产生业务价值),一边给修改涉及到的遗留代码重构和添加单元测试 (偿还技术债 务),这样做更加经济合 理。”(http://www.infoq.com/cn/news/2014/03/garbage-code-discuss/)我个人赞同以上观点。换句话说,从新功能和修bug入手,来为与这两者相关的已有生产代码编写单元测试。
5)每次实现一个新需求都要造成代码的很多修改,该怎么办?
答:要确保每次代码重构都能做到单一职责原则(Single Responsibility Principle)和开闭原则(Open-Closed Principle),使得新增功能能够通过扩展代码,而不是修改代码来实现。这需要了解设计模式和刻意的编程操练。
6)公司强制要求编写单元测试,并要求100%行覆盖率,但此时单元测试的性价比能高吗?
答:100%行覆盖率会诱使程序员针对代码具体实现,而不是针对用户意图来编写测试,这样的测试如前所述,会很脆弱,导致单元测试的性价比很低。
7)虽然公司要求写单元测试,但是单元测试慢慢就没有人写了,此时单元测试还有必要吗?
答:需要让程序员通过实践体会到使用TDD编写单元测试能如下面那样节省开发时间:
a) 专注。即在开发中专注于测试代码所描述的产品特性范围。用TDD开发,好比男人逛超市,掏出纸片上的购买清单(即测试代码),然后拿货、掏钱、走人,精益适用,绝不做无用功。而不用TDD开发,就好比女人逛超市,看到有什么打折的、新款的、促销的好东西都想买,采购(编写)了不少多余的东西(生产代码)而造成浪费。
b) 复用。在TDD开发中编写的自动化测试代码,将来可以复用,能节省将来找bug的时间;而手工做debug,将来无法复用,以后找bug会手工做很多诸如设置断点、单步运行、检查变量等重复的工作而花费大量时间。如果你上网去淘宝买东西,付账时肯定要输入你的姓名、地址、银行卡号等个人信息。用TDD开发,好比你首次使用淘宝,填写上述个人信息来创建账户,然后将来就可以直接输入账户名和口令来复用这个账户里的个人信息一样,节省以后的时间。而不用TDD开发,就好比淘宝没有创建账户的功能,每一次使用淘宝买东西,都需要你填写繁琐的个人信息(好比debug代码)一样,浪费以后的时间。
c) 反馈早。命名富有表达力的单元测试能起到文档的作用,能让程序员在阅读测试时得到代码行为的更早的反馈。如果程序员能够利用TDD中的测试能够方便地自动重复执行这个特点来频繁运行测试,那么就能使软件的绝大多数的bug在流入下游测试工程师之前被快速地发现和修复。这种反馈会远远早于那种软件只由测试工程师来测试的情况,能省却下面这些行为所需花费的可观的时间成本:测试工程师发现、描述、报告和跟踪bug、程序员阅读测试工程师报告的bug并加以重现和修复、项目经理在各种会议中检查梳理这些bug。
8)如何让团队新人保持代码质量?
答:让团队新人和老员工结对编程来用TDD进行开发,来阅读和编写单元测试,并频繁变换结对搭档和运行单元测试。
9)在实际工作中,结对编程效率如何保证?当驾驶员在敲代码时,副驾驶要做什么呢?
答:结对编程的最大好处是让团队成员彼此相互传递知识,最终使每位成员都成为全栈开发者,能够掌握产品管理、开发和测试等多种技能,从而消除瓶颈和排队,让团队所接到的任务能够无等待地流动下去,加快开发步伐。可以体验敏捷扑克牌的游戏来感受这一点。当驾驶员在敲代码时,副驾驶要观察、思考、提问,并随时准备接替驾驶员的工作,最终完成知识的传递。
10)下次编程道场时间?
答:2014年8月24日下午在北京东直门ThoughtWorks北京办公室举办,编程语言为C#,编程环境为Ubuntu Linux,IDE会在Eclipse C# plugin Emonic和MonoDevelop之中选择一个。编程题目我打算使用Luca Minudel 的Telemetry System的C#版本,来操练将两个代码职责从一个类中分离出来,代码参见:https://github.com/lucaminudel/TDDwithMockObjectsAndDesignPrinciples/tree/master/TDDMicroExercises/CSharp/TelemetrySystem
--------------------------------
操练成就匠艺。全栈开发者的公益编程操练社区:bjdp.org北京设计模式学习组。微信订阅号:bjdp_org