1.1单元测试的定义
单元测试就是针对一个工作单元设计的测试,这里的“工作单元”是指对一个工作方法的要求。
单元测试是开发者编写的一小段代码,用于检测被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试用于判断某个特定条件(或场景)下某个特定函数的行为。
例:
你可能把一个很大的值放入一个有序list中去,然后确认该值出现在list的尾部。或者,你可能会从字符串中删除匹配某种模式的字符,然后确认字符串确实不再包含这些字符了。
执行单元测试,就是为了证明某段代码的行为和开发者所期望的一致!
调用系统的一个公共方法到产生一个测试可见的最终结果,其间这个系统发生的行为总称为一个工作单元。我们通过系统的公共AP和行为就可以观察到一个可见的最终结果,无需查看系统的内部状态。一个最终结果可以是以下任何一种形式。
被调用的公共方法回一个值(一个返回值不为空的函数)
在方法调用的前后,系统的状态或行为有可见的变化,这种变化无需查询私有状态即可判断。(例如:一个以前不存在的用户可以登入系统,或者一个状态机系统的属性发生变化。)
调用了一个不受测试控制的第三方系统,这个第三方系统不返回任何值,或者返回值都被忽略。(例如:调用一个第三方日志系统,这个系统不是你编写的,而且你也没有源代码。)
很多人觉得被测试的工作单元应该尽可能的小。我却不这么看,我认为工作单元这个概念意味着一个单元既可以小到只包含一个方法,也可以大到包括实现某个功能的多个类和函数。如果你的工作单元很大,却但是其最终结果对用户可见度高,易于维护也未尝不是好的测试,相反如果试图把工作单元缩到最小,最后会不得不伪造一堆东西反而会增加测试的复杂度,适得其反。
2.什么不是单元测试单元测试其实是一门很基础也很简单的技术,然而在单元测试实践过程中,往往会对单元测试产生一些误区,进而写出一些不是单元测试的"单元测试" ,其中常见的主要有以下三种。
2.1 跨边界的测试单元测试背后的思想是,仅测试这个方法中的内容,测试失败时不希望必须穿过基层代码、数据库表或者第三方产品的文档去寻找可能的答案!
当测试开始渗透到其他类、服务或系统时,此时测试便跨越了边界,失败时会很难找到缺陷的代码。
测试跨边界时还会产生另一个问题,当边界是一个共享资源时,如数据库。与团队的其他开发人员共享资源时,可能会污染他们的测试结果!
如果发现所编写的测试对一件以上的事情进行了测试,就可能违反了“单一职责原则”。从单元测试的角度来看,这意味着这些测试是难以理解的非针对性测试。随着时间的推移,向类或方法种添加了更多的不恰当的功能后,这些测试可能会变的非常脆弱。诊断问题也将变得极具有挑战性。
如:StringUtility中计算一个特定字符在字符串中出现的次数,它没有说明这个字符在字符串中处于什么位置也没有说明除了这个字符出现多少次之外的其他任何信息,那么这些功能就应该由StringUtility类的其它方法提供!同样,StringUtility类也不应该处理数字、日期或复杂数据类型的功能!
单元测试应当是可预测的。在针对一组给定的输入参数调用一个类的方法时,其结果应当总是一致的。有时,这一原则可能看起来很难遵守。例如:正在编写一个日用品交易程序,黄金的价格可能上午九时是一个值,14时就会变成另一个值。
而好的设计原则就是将不可预测的数据的功能抽象到一个可以在单元测试中模拟(Mock)的类或方法中
其实上面三种测试已经到了集成测试的领域。任何测试,如果它运行速度不快,结果不稳定,或者要用到被测试单元的一个或多个真实依赖物,我们就认为它是集成测试。
集成测试是对一个工作单元进行的测试,这个测试对被测试的工作单元没有完全的控制,并使用该单元的一个或多个真实依赖物,例如时间、网络、数据库、线程或随机数产生器等。
集成测试本身并不是一种坏事,反而其具有和单元测试一样高的地位,但是在实践过程中我们把集成测试和单元测试分离开来还是很重要的。