单元测试就是常说的Unit Test
而TDD其实是uTDD - unit Test Driven Development
在我现在的日常工作中,其中一个很重要职责就是保证新人快速成长。这里的“成长”不是应试教育下的快速成长,更不是通过填鸭式教育、题海战术来达到的成长。而是以理解公司文化的为前提,“素质教育”为基础的健康成长。
我们公司的创始人是《敏捷宣言》提出者之一,哪怕在这样一家敏捷绝对拥趸的科技公司里,也一样会有关于TDD的一些常见问题。我在和新人Pair、带团队和
客户参观的过程中都无一幸免的被问到了如下TDD的问题:
- 为什么我们要TDD ?
- 任何时候都要TDD吗?什么情况下不用TDD?
- TDD怎么衡量她的价值?实践她的都是开发人员,她到底有没有价值因该开发人员说了算。
这些问题的提出者以及提出时间,总结起来看还挺有意思。有的完全没有接触实践过敏捷;有的相信敏捷,但还没有付之实践;还有的拥抱后坚持实践了一段时间,思考然后,最终提出反问。
有这些问题其实很正常,回答起来也不难。我们大可以把TDD的好处罗列一翻,把一些正确的大道理再叙述一遍,但我们前面可是标榜过我们自己是“素质教育”,我们更希望对一些基础知识认真打磨,让大家根据这些基础知识结合自身的环境,给出自己的判断。学以致用,融汇贯通。
我们这里说的TDD(测试驱动开发),实际上更准确的描述为单元测试驱动开发。先不谈怎么驱动开发,我们一起来掀开单元测试的外衣,看看到底本尊长什么样?怎么就可以驱动开发了?他能解决什么问题?又对什么问题有心无力?
Unit testing is when you write test code to verify units of code.
翻译起来就是:单元测试就是用来验证代码单元正确性所写的测试代码
我们下面站在 先实现后测试 的原生视角来回答几个问题
“单元”到底怎么定义?
- 在尺寸上并没有清晰的定义。
- 一小段展现出某些“有用的行为”的代码
- 一个单元自身 通常 不表示完整的端对端(end-to-end)行为,它只表示这个端对端行为中的一个子部分。
可以看出这里的“单元”,并没有固定和清晰的定义,比较容易接受的定义是把“单元”和函数划上等号。项目管理中所有的实践,最终都是为了保证项目的按时交付及
质量保证。而且明确说明单元测试不因该表示完整的端对端行为,因为在项目的测试体系里,比如金字塔原理,越接近用户的测试往往是最费时的,所以从成本和质量平衡的角度来看,因该是越精简越好,这其实就是对单元测试提出了明确的要求-集成测试和端到端测试没有覆盖到的,那么单元测试你就给我保证好,至于你用函数分还是以行为分,没有统一的标准。这里借助绝对正确的一句废话-对项目合适的才是最好的。
何时、何故需要写单元测试?
- 你 刚刚 完成了一个特性(Feature)的编码并且想确保它能够按照预期工作。
- 你想用文档记录一个代码中的修改,以便你或其他人能够在以后明白相关的意图。
- 你需要修改代码,同时希望确保这些即将来临的修改不会破坏任何已经存在的行为。
- 你希望了解当前系统中的行为。
- 你希望知道第三方代码的行为在什么时候会与你的期望不符。
简单来说也就是对测试特性的另一种描述,通常我们可以把测试当成文档,因为单元测试要足够的简单,不然就会有人问这样的问题:我们用什么来保证测试本身的正确性呢?是不是得为我们的测试再写测试呢,显然不是,不然不就死循环了嘛。但这不意味着我们就不用保证测试本身的正确性了,简洁性其实就是这个目的,你一眼就能看出来正确与否,那么我们大家都有信心相信我们测试代码的质量,以及功能模块的稳定性也可以通过测试得到了因有的保障。
推荐的测试结构
- AAA (Arrange - Act - Assert)
- GWT (Given - When - Then)
需要注意的是,通常我们建议从结果开始(Then/Assert),明确我们希望得到的结果,再设置好测试环境并触发相应的动作。
推荐的测试原则 - FIRST
- Fast : 好的测试因该足够快 - 因为单元测试的价值在于持续、全面、快速的反馈系统的健康程度
- Isolated: 好的测试因该相互隔离 - 你应当能够在任何时间,以任何顺序,运行任何一个测试。
- Repeatable:好的测试应该可复验 - 每一个测试应当在每一次运行时产生与之前一样的结果。
- Self-Validating - 好的测试应该能够自认证!- 写测试是为了节省时间而不是为了花费更多的时间。所以,测试应当能够自排列、及时、恰到好处的自动化运行,并且能够快速、准确的确认结果,尽可能的不需要手动操作或设置。
- Timely - 好的测试应该足够及时!- 单元测试是一个好习惯,你越是推迟利用单元测试来验证你的代码,就越是需要付出更多的代价。同理,一旦你将代码提交进了代码库,你专门找时间回过头来补测试的机会也就很小了。
推荐的测试边界原则 - CORRECT
- Conformance - 一致性 (值是否符合预期的格式?)
- Ordering - 有序性 (一组值的顺序是否符合预期?)
- Range - 区间性 (值是否在一个合理的最大值和最小值的范围内?)
- Reference - 引用性 / 耦合性 (代码是否引用了一些不受其直接控制的外部因素,由这些外部因素所引入的前置条件或后置条件所造成的影响是否符合预期?)
- Existence - 存在性 (值是否存在(例如:非 null,非零,存在于某个集合中等)? )
- Cardinality - 基数性 : 计数性 (是否恰好有足够的值?(重点关注 0-1-n 原则,问题往往发生在这三个边界上。))
- Time - 时间性(绝对时间及相对时间)- (所有事情是否按顺序发生?是否在正确的时间?是否及时?)
结束语
从单元测试的基本概念可以看出来,更多的情况下,并没有严格意义上的定义,更应该坚持原则,看清形势,找到最适合项目团队的。
TDD也可以简单的看作基于单元测试,所采用的敏捷实践之一,持续改进也是他们不谋而合的地方,也是我推荐TDD的原因之一。
下一篇会以TDD实例从代码实践来讲解因该怎么做TDD。
欢迎更多的讨论,更浩渺的思维!
可通过以下方式联系我们(You can contact us the ways below):