概述
在今天, 前后端分离已经是首选的一个开发模式。这对于后端团队来说其实是一个好消息,减轻任务并且更专注。在测试方面,就更加依赖于单元测试对于API以及后端业务逻辑的较验。当然单元测试并非在前后端分离流行之后才有,它很早就存在,只是鲜有人重视且真的能够用好它。而在前后端分离开发模式下,特别是两者交付时间差别很大的情况时,后端可能需要更加地依赖于单元测试来保证代码的正确性。
本文主要围绕单元测试展开,从单元测试的基础概念说起,对比单元测试和集成测试,同时我们还会聊一聊单元测试与测试驱动开发的区别。在我们了解完单元测试的概念之后,我们会探讨一下什么样的单元测试算得上是好的单元测试,它们具备哪些特征,如何使用隔离框架来帮助我们对一些复杂的组件进行测试。最后一个内容也是本文想要阐述的重点: 单元测试是开发人员写的,那么开发人员在写自己的代码的时候,如何提高自己代码的可测试性? 什么样的代码算的上是对单元测试友好的代码? 带着这些问题,我们这就来开始我们的单元测试之旅。
什么是单元测试?
有人可能写过单元测试,但是却不知道为什么要写单元测试,有人知道为什么要写单元测试,但不确定如何写才是好的单元测试。但是对于“测试” 我们每个人都轻车熟路, 你看看下面的功能是否似曾相识?
单元测试与测试
测试种类分为很多种:单元测试、集成测试、系统测试、压力测试、负载测试、验收测试等等 ,我们今天不打算也不能进行系统性的介绍。作为开发人员,我们平常所说的“测试”。也就是说你代码写完了,老大问你测试通过了吗?你说过了,然后就可以Check in 代码了。这里的“测试”,实际上指的是不完整的功能测试。为什么说它不完整,是因为从专业测试的角度来讲,还需要定义规范的测试用例,用例写完之后还要开发和测试人员一起评审等等 。 而我们只是在脑海中预想了一下它应该如何工作的,应该给我什么结果等,然后运行一下,咦,还真是这样的,那我们的测试就算通过了。 会有多少Bug,就取决于我们这个预想有多细了,往往有时候我们只能想到很少一部份,这时候专业独立的测试人员就派上用场了。同时精通开发和测试的人是很有优势的,自己能够保证写出来的软件的质量,这也是现代敏捷开发团队所追求的,但是这样的人总是少之又少。
单元测试是通过把一个应用程序拆分成可测试的足够小的部分,然后把每一部分与其它所有功能隔离开,单独对这一部分进行测试。而这个“可测试的足够小的部分”就称之为“单元“,在C语言中一个单元可以是一个函数,在C#中单元测试可以是一个类。 如果所有的单元都能够像我们所预料的正常工作,那么把他们合并起来就能够保证至少不会出现很严重的错误。
单元测试与集成测试
为什么要把这两项拿出来对比,是因为这两项很容易混淆,一不小心你就可能把单元测试写成集成测试了,这也是为什么单元测试有时候看起来那么糟糕的主要原因。我们上面说单元测试是把每一个单元孤立出来,在测试的时候不能和任何其它的单元有任何联系,这是单元测试,反过来你一旦在你的测试代码中引入了另外一个单元,那你就要开始小心,你是不是已经开始写集成测试了。 当然有时候往往不是引入了其它的一些单元,有可能是一些组件,下面列出了一些单元测试和集成测试的主要特点,希望能够帮助大家区分单元测试与集成测试。
单元测试
可重复运行的
持续长期有效,并且返回一致的结果
在内存中运行,没有外部依赖组件(比如说真实的数据库,真实的文件存储等)
快速返回结果
一个测试方法只测试一个问题
集成测试
利用真实的外部依赖(采用真实的数据库,外部的Web Service,文件存储系统等)
在一个测试里面可能会多个问题(数据库正常确,配置,系统逻辑等)
可以在运行较长时间之后才返回测试结果
单元测试与测试驱动开发(TDD)
测试驱动开发其实我们用一个问题就可以解释清楚,那就是“你什么时候写单元测试?” 有人选择在开发的代码写完之后再写,这样我们的开发过程是: 理解需求-》编写代码-》针对代码结合需求写单元测试。后来大家发现,往往在写单元测试的时候发现自己有些需求没有理解清楚,或者这些需求原来设计的时候就没有考虑到,所以又重新改原来的代码。 于是有人就说,为什么我们不干脆反过来? 先写单元测试,再写代码? 所以我们开发的过程就变成了这样:理解需求-》针对需求写单元测试 -》 编写代码让单元测试通过。 最开始是叫测试先行(TFD: Test First Development) ,后来就发展成我们熟知的"测试驱动开发"了。
测试驱动开发最大的好处是,让开发人员更好的理解需求,甚至是挖掘需求之后再进行开发。 当然,我们不可能一次性把所有的测试代码都写出来之后再写代码,这是一个重复迭代的过程:
由于TDD不是我们本篇的主要内容,这里仅仅希望能给大家一个对TDD的浅显认识的同时了解到TDD与单元测试的联系。到这里,我们对于单元测试的概念就介绍的差不多了,接下来是代码时间。:) 我们来上一个真实的例子更形象的了解一下单元测试。
一个单元测试的例子
那么问题来了,我们用什么来案例来写了一个单元测试的例子呢?既然这样,那么我们就用前两篇我们在领域模型驱动设计中讲到的用户注册的例子吧。在用户的领域服务中,UserService提供了一个Register的方法,通过用户名、邮箱和密码三个参数来创建一个用户的对象。 像所有注册逻辑一样,邮箱是不能重复的,这是我们现在这个领域服务中比较重要的业务逻辑,所以我们的单元测试必须要覆盖到。 我们的测试