两个问题
- 要解决的问题是什么?
- 要解决这个问题,是否有更好的方式?
我发现这时不时使用这两个问题可以很好的避免陷入“为了XX而XX”,把手段误当为目的的思维陷阱中。
那么,对于TDD,我们可以问:
TDD需要解决的问题是什么?
假设说:提高代码质量。
那么,提高代码质量是否有更好的手段?当然,TDD也是提高代码质量的手段之一。
TDD的迷思
很多TDD的鼓吹者,会宣称TDD能够帮助我们做更好的设计;我认为:
- 是的,某些情况下可以
- 但更多的情况下没有影响
- 有的情况下,TDD甚至会伤害代码设计
DHH的著名文章TDD is dead核心观点便是有一些程序为了让代码可以被测试,而修改了原有的代码结构以及使用各种mock,这样的行为,会反过来伤害代码设计/质量。
关于这方面的更多讨论,可以参考马丁·福勒的总结
我们应该避免使用mock,能够直接连数据库进行测试,那么就直接连:
- 不要为了测试而测试
- 不要为了“方便测试”而去修改代码设计
- 让设计来适应代码,而不是代码去适应设计
那么,当前的情况究竟是适合还是不适合?
自己判断
自己判断
自己判断
教条教条- 万物皆对象
- 万物皆资源
- 必须先写测试再写实现
- 每个函数都必需带有单元测试
教条教条测试案例的实践
在Go语言的实践中,测试编写非常方便;具体到TDD,可以参考LondonGophers 20/03/2019: Dave Cheney - Absolute Unit (Test)的建议:
模块函数实现单元测试Acceptance test–driven development覆盖率测试覆盖率
覆盖率gogo test -coverprofile [filename]覆盖率情况profile它会记录我们代码中具体行是否已经被测试代码覆盖;亦可以使用网页模式对代码覆盖做更方便的查阅。
go testcoverprofilep.out打开浏览器来对代码覆盖做浏览:
被测试的代码文件覆盖率,未覆盖的代码一目了然以便我们考虑是否要增加/修改测试案例来覆盖更多的代码。
提醒
100%覆盖- 有可能是测试以及实现都错了
- 有可能测试仍不完备,存在实现没有测试到的场景
100%覆盖api.Exec100%覆盖代码覆盖率虽然上升了,但代码质量实际上却是下降了。
案例
下面我们来考虑一个模拟的案例,以及一个真实的案例;看看在实践中可以如何使用测试,或者说TDD来提高我们的代码质量。
怒气值 FuryCounter
HitFuryBlockFuryCounterFuryBonusCountBonusLevelHitFuryBonusCountBlock简单业务
BonusLevel怒气值100%覆盖测试代码中,实际上是编译、启动了整个程序,跑其了web服务器,然后真实调用web接口来进行测试;并且它可以根据被调用的web接口,来判断覆盖率;而且,测试过程非常快,基本是秒完成。
开发实践中,工程师不愿意写测试的一个主要原因是因为测试代码麻烦、运行慢;但这点在Go语言中相对不是问题。
复杂业务
BonusLevel怒气值怒气值Bonus = Fury * BonusLevel业务瞬间变得复杂起来,在这样的情况下,我就建议先考虑编写测试案例,然后再写实现。
实际上,对于复杂的业务,我相信在确定测试案例时,就可能会发现需求的一些潜在不合理性(真的要支持负值?),那么,就应该立刻跟策划/产品经理进行沟通,以明确业务需求。
对于复杂的业务,要期盼某个人可以一下子就想清楚所有逻辑,是不实际的;实践中,我们更可能需要借用测试案例来帮助我们理清业务,然后再写实现代码。
调用命令参考
购物车
购物车需要支持:
- 优惠卷
- 立减
- 满减
- 定金
- 订金膨胀
- 叠加
- 加购指定商品
如果我们需要实现这样的购物车,应当如何实践TDD呢?