单元测试之道

标签: 单元测试 前言 系列


1. 前言

在一个项目当中,开发者常常要做大量的测试工作,如单元测试,集成测试,回归测试,压力测试 .etc。当然,依据项目情况大小和开发者人员水平不同,测试涵盖的方面自然也是不一样的。一些测试需要相应的硬件和人力资源,一些需要专门的测试小组,另一些需要提供细致处理和长时间不间断运行的环境。

但是今天说的单元测试则不同,它是一种看起来十分廉价和基础的技术。它由后台程序开发人员创建运行,单机运行,刨除代码量以外,对一个完整的项目开发成本而言,所需的人力物力都是相对较小的。而长久以来的事实也已经证明,单元测试对于代码规范性和高效性,以及项目Bug的捕获和解决都有很大的帮助。大多数开发者其实了解这样的事实,只是因为一些内在和外在的因素(通常是不重视,时间紧和嫌麻烦),往往不愿意进行这些测试,或者只在项目快要结束时才想起来,只是已经为时已晚。

所以诸如TDD(测试驱动开发)的项目开发方式,都提倡一个核心道理:单元测试应该早做,多做,这样既避免了过度设计,对有效编码,项目依赖解耦也有好处。而且我们始终要明确,单元测试的第一受益者,永远是程序员。接下来,就让我们来看看单元测试的一些相关情况,之后再在.NET项目中实际运行单元测试吧。

2. 单元测试总览

让我们根据3W+1H原则,先对单元测试有个系统性认识吧

2.1 什么是单元测试(WHAT)

当我们在谈论单元测试的时候,我们在谈些什么。——村上春树

按照维基百科上的说法,单元测试(Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在面向对象编程中,最小单元就是方法,包括基类、抽象类、或者派生类(子类)中的方法。按照通俗的理解,一个单元测试判断某个特定场条件下某个特定方法的行为,如斐波那契数列算法,冒泡排序算法。

单元测试将被测试应用程序细分为一个个足够小的基本单元,各个单元间相互独立,互不影响。开发者能通过单元测试,证明被测试函数的行为确实和开发者期望的一致。为了满足这个最基本的愿望,书写单元测试前,我们不用考虑太多关于性能上的事情,这是之后优化重构该做的事情。

2.2 为什么使用单元测试(WHY)

爱做单元测试的程序员,代码都不会太差。——古龙

上文说到,常常由于项目工期紧张,抑或是程序员自身原因的问题,团队往往在项目接近完成时才进行测试。这其实是非常不提倡的一种做法。就好像我们雇了一批人给我们造房子,从地基开始,造到十几层了,才用悬垂线来测房子倾斜度一样不靠谱(假如这个比喻靠谱的话)。到时候高层依赖底层,高层调试时发现bug,又得让我们返回底层查找问题,即便修改之后仍然通过了,但是想必很多朋友也遇到过项目代码覆盖度比较高,一改基础方法影响一大片的问题吧。

所以当我们从一开始就进行正确的单元测试时,这些问题都是可以解决的。以下罗列出了几个简单的作用,以供参考

2.2.1 快速定位

单元测试最基本的一个功能,就是快速定位代码中的错误。从项目一开始,开发者便对所有的单元模块进行测试的话,,除了能尽早发现问题,另一方面对我们项目的持续开发无疑也是提供了极大的保障。

2.2.2 文档记录

当我们设计出一个良好的单元测试环境,我们势必会对所有的基本单元进行测试,这时候,单元测试相对于为我们编写了一份api文档,我们随时可以查阅方法相关参数和返回值,以及运行情况。

2.2.3 适应变更

单元测试允许程序员在之后的开发工作中重构代码,并且确保单元依然工作正确。这个过程就是为所有函数和方法编写单元测试。在连续的单元测试环境中,只要设计出了良好的验证手段,单元测试可以延续用于准确反映当任何变更发生时可执行程序和代码的表现,帮助开发者优化代码逻辑和代码结构。

2.2.4 规范设计

进行单元测试时,开发者其实站在了一个观察调试的上帝角度。无论是开发先于测试,还是测试先于开发,单元测试都可以帮助我们将模块设计成易测试,易调试,易重构。在这个过程中,开发者的编码能力和对业务的理解能力也将得到锻炼

2.3 什么时候决定进行单元测试(WHEN)

早。——鲁迅

单元测试这东西,就跟戒烟一样。每个烟民都知道吸烟的坏处(bug),一开始吸烟的时候也会有人提醒你赶快戒烟吧,但是你往往并不在意,等到年限一长(项目开发迭代多次),因为吸烟身体出现的问题越来越严重,你可能在这之前做过几次体检(集成测试),但是依然于事无补了,等这时候再怀念当初戒烟,乃至不抽烟的好,也是为时已晚。所以单元测试,就该在项目一开始的时候进行测试,在你起了“编写单元测试太麻烦了,还是算了”的念头的时候就该开始。博主代码水平有限,无止尽的debug和bug提交已经耗费了我很大的精力,所以这才下定决心开始单元测试之旅。

确实不可否认,刚开始就编写单元测试常常要多花费几倍的代码量,但是随着项目进行,当你把基础方法都测试过以后,高层功能需要的代码量反而会大大减少。这时候单元测试也在往集成测试迁移,这是一个顺其自然的过程,同时为集成测试的简化也提供了极大的便利。

2.4 怎么进行有效的单元测试(HOW)

单元测试最终呈现出来的效果还是一个或多个测试方法而已,编写这些测试方法时,应该注意以下原则

  • Arrange 用于初始化一些被测试方法需要的参数或依赖的对象。

  • Act方法 用于调用被测方法进行测试。

  • Assert 用于验证测试方法是否按期望执行或者结果是否符合期望值

在这之前,我们当然需要区分出应用程序的每个基本单元,这里有个讨巧的方法,就是对项目依赖进行自底而上的遍历即可,我们并不需要多在意单元测试和集成测试的依赖关系。

其实在传统的DDD驱动开发中,我们已经见识了很多IAPPService和IRepository,以及IDomainService的依赖关系了,对于多个基本单元测试组装的集成测试,我们这里也统一当做同一种东西来对待了,毕竟我们关注的还是测试本身。所谓工欲善其事必先利其器,.NET 平台上强大的工具也是必不可少的,下文中将用XUnit和NSubstitute来进行所有的测试用例展示。

——未完待续

参考资料

posted @ 2016-09-21 01:37 白细胞 阅读(...) 评论(...) 编辑 收藏