【IT168 技术文章】
一、什么是UT
UT测的是方法,检验的是一个类对外界的承诺。因此,大多数情况下,我们测的应该是公共方法,除非不得已才对私有方法进行测试。 方法是程序设计的最小单位。UT的局限也体现在这里,它并没有针对类之间的交互做检验。所以,不能指望单元测试做完了,就没有问题了。在这个方面的欠缺,我们可以通过自动化的功能/组件测试来完成。这也是开发者测试的一部分。
二、UT的任务
1. 尽早的发现问题 说白了,就是不要让问题流出去。让我们的缺陷率降低,把我们的产品做的漂亮。另一方面,一些细类度的问题在这里也确实更容易发现,同时也为进行更大粒度的测试做好集成准备。
2. 编织一层保护网
给新的代码建立有效的保护。 保证对代码每一份改动,都不会对现有系统造成伤害。避免了引入问题。
3.写出优雅的代码
编写单元测试的过程,实质是使用我们自己代码的过程。我们成了第一个真正意义上的体验者。在这个过程中,我们为了使代码易用会进行不断的重构。最终的交付代码必然会更优雅。
4. 建立程序员的自信
我们习惯于两眼一抹黑,不管三七二十一就把代码写完了。Code阶段结束,然后不断的调试,修修补补跑起来就过了TR4。没人敢说,他代码一写完了,就能跑起来。这种做法是很没人性的,系统搞挂几次后就心里发虚,一点底气都没有。但是,这是我们的职业,我们为着自己的荣耀而战。在任何时候,都需要信心满盈。
三、UT的基本原则
1.一个类、一个方法、一条路径 我们一次只测一个类的一个方法。刚开始做单元测试的时候,很多人会自然而然的做成了功能测试。因为,前一部分执行的结果恰好为后一部分准备好了输入。而另一方面,连续的执行过程组成了一个明确的场景,让具体的功能变得完整可见,这正是我们期待的,让我们变得有信心。那为什么不顺其自然呢?
原因在于:
1)、单元测试要保证一定的微粒度。从单元测试到功能测试,这之间的粒度会越来越大,越往后,我们会越少的关注细节。如果直接跳跃到功能测试上,会让我们遗漏掉一些问题,在以后的粗粒度测试中,它们会转变为很难重现或者不可重现的致命问题。
2)、上述场景之所以会出现,是因为先写代码后写测试导致的。相当于代码已经集成,具备了做功能测试的一定条件。这个时候让再走回头路做单元测试,当然不如直接就做功能测试来的顺当。所以,应该一个测试、一个方法,一个方法一个测试,这样不断的一步一步的循环迭代集成来的好。
另一方面,为了将一个方法的多个不同的执行路径分开,我们必须保证一次只测一个方法的一个路径。这样,前置条件和后置条件就会很明确,容易准备测试环境。
2.重构以便于测试
面对着一个方法,你感到一筹莫展。并不是你的错,而是因为这个方法很烂。测一个方法就是在使用这个方法,你自己都这样无奈,将来真正使用的人岂不是要骂娘?雁过留声,人过留名。这个时候,重构一下很值得。
3.保证测试方法简洁
如果连测试方法都很复杂,难道我们还要再写测试用例来保证它的正确执行不成?这样岂不是麻烦大了!所以,测试方法一定要写的尽可能的简单,写到你认为白痴都能看懂的程度。
四、如何才能做UT
1.代码要首先可测,然后才能测。 首先要遵守契约式设计。类的每一个方法都应该对外承担了一份契约,有明确的前置条件和后置条件。 当你对这个方法进行测试之前必须清楚明白这两个条件。一个有效的方法一定是做了什么事的。一定会产生一定的影响,我们可以通过对外围环境的改变来检测方法产生的作用是否如预期(例如,获取某一对象的属性进行检测)。
其次是,低Ce和单一责任原则。一个方法对外的依赖应该单一,不应该取决于很多的外部环境。因为不同的外部环境越多,组合项就越多,要测的先决条件就越多。而一个方法对外部环境的影响太多,则意味着职责不单一,对于输出越难测。
曾经听有人讲到,这些道理,你懂了就懂,不懂就不懂,说了没用。但我认为,如果你还以为这些只是大道理,如果你还想对它有点切身的感受,做单元测试是一个很好的途径。
2.信任你该信任的。
对于已经稳定的部分,类似于第三方包,平台部分,其至是遗留系统中已经证明是可靠的部分,都可以信任。这些是我们用例代码依赖的部分,是我们用来检验其它待测部分的基石。如果什么都要测,就会变成什么都测不了。
3. 单元测试要尽量少的增加开发人员的负担。
一方面,我们实在被问题单压抑的太久了。所以,从全局上来看待这个问题,如果可以确实的减少后期的维护压力,对我们自身而言当然是有益的。所谓增加的负担,不过是提早了结了一些痛苦。
另一方面,单元测试必须自动化 ,必须简单,傻瓜化。这是我们要努力的目标。
4.将调试的时间用来写单元测试
没有做过自动化单元测试的人永远也不能体会其给程序员带来的自信和好处。如果你还在调试,不如顺手加个测试。以后,保证同样的问题不会从你的眼皮下溜走。
5. 现在的单元测试难在哪里?
难以打桩。因为我们对其它模块的关联是这样的。
这就是麻烦的所在,关联太多。如果我们要测,我们就要打桩。但是,
1. 望而生畏,太多。
2. 无法下手,都是直接对象依赖,而不是接口依赖。
所以,你让我来测这样的代码,我是不会干的。
因为,我希望的是这样的:
但是,我们现在的代码欠债太多了。没有条件和能力再回去对这些代码进行单元测试。而且,这些功能经过这么多年的维护,大多已稳定。做的性价比也不高,不够实惠。所以,不必要做。
但在新功能开发过程中,这些老代码依旧会如恶梦一样纠缠着我们。让写单元测试过程中常常面临着举步维艰的境地。我们不得不在让代码变得可测与对代码的侵入性测试之间进行抉择。