单元测试基础 -- 基本概念


1.1单元测试的定义

调用系统的一个公共方法到产生一个测试可见的最终结果,其间这个系统发生的行为总称为一个工作单元。我们通过系统的公共AP和行为就可以观察到一个可见的最终结果,无需查看系统的内部状态。一个最终结果可以是以下任何一种形式。

  1. 被调用的公共方法回一个值(一个返回值不为空的函数)
  2. 在方法调用的前后,系统的状态或行为有可见的变化,这种变化无需查询私有状态即可判断。(例如:一个以前不存在的用户可以登入系统,或者一个状态机系统的属性发生变化。)
  3. 调用了一个不受测试控制的第三方系统,这个第三方系统不返回任何值,或者返回值都被忽略。(例如:调用一个第三方日志系统,这个系统不是你编写的,而且你也没有源代码。)

很多人觉得被测试的工作单元应该尽可能的小。我却不这么看,我认为工作单元这个概念意味着一个单元既可以小到只包含一个方法,也可以大到包括实现某个功能的多个类和函数。如果你的工作单元很大,却但是其最终结果对用户可见度高,易于维护也未尝不是好的测试,相反如果试图把工作单元缩到最小,最后会不得不伪造一堆东西反而会增加测试的复杂度,适得其反。

2.什么不是单元测试

单元测试背后的思想是,仅测试这个方法中的内容,测试失败时不希望必须穿过基层代码、数据库表或者第三方产品的文档去寻找可能的答案!
当测试开始渗透到其他类、服务或系统时,此时测试便跨越了边界,失败时会很难找到缺陷的代码。
测试跨边界时还会产生另一个问题,当边界是一个共享资源时,如数据库。与团队的其他开发人员共享资源时,可能会污染他们的测试结果!

2.2 不具有针对性的测试

单元测试应当是可预测的。在针对一组给定的输入参数调用一个类的方法时,其结果应当总是一致的。有时,这一原则可能看起来很难遵守。例如:正在编写一个日用品交易程序,黄金的价格可能上午九时是一个值,14时就会变成另一个值。
而好的设计原则就是将不可预测的数据的功能抽象到一个可以在单元测试中模拟(Mock)的类或方法中

2.4 集成测试

单元测试是非常有魔力的魔法,也是一把双刃剑。使用得当,可以很有效的提高我们的编码质量,提升研发效率,但是如果使用不恰当亦会浪费大量的时间在测试编码、维护和调试上从而影响代码和整个项目,徒劳而无功!
因此做好单元测试至关重要!而想要做好单元测试,我们首先应该知道优秀的单元测试有哪些特性。
一个好的单元测试一定是有以下几个特性的
? 自动化
? 彻底的
? 可重复的
? 独立的
? 专业的
回顾一下自己以前写过的单元测试问自己几个问题。

  1. 它是不是可以自动化一键运行、并且可以重复运行

  2. 几个月后它是不是仍可以运行、并且得到期望的结果

  3. 它是否可以在几分钟内运行结束

  4. 在运行之前你是否不需要需要进行一系列的配置

  5. 每次运行是否能够得到相同的结果

  6. 外部的系统因素是否不会影响你的测试结果

  7. 测试代码是否很简单就可以编写完成

如果针对以上问题有任何一个的回答是“否”,那么你应该好好的思考一下到底如何去做好单元测试。

4. 如何进行单元测试

一般来说有六个值得测试的具体方面,可以把这六个方面统称为Right-BICEP:

  • Right——结果
    对于单元测试测试而言,首要的也是最明显的任务就是查看所期望的结果是否正确,例如判断一个方法的返回值是否为序列中的最大值......
  • B——边界条件
    找边界条件是做单元测试中最有价值的工作之一,因为bug一般就出现在边界上。关于边界条件3.2小节会有详细总结
  • I——检查反向关联
    对于一些方法,我们可以使用反向的逻辑关系来验证它们。例如,你可以用对结果进行平方的方式来检查一个计算平方根的函数,然后测试结果是否和原数据很接近
  • C——交叉检查
    有些时候我们实现一个问题会有不同的算法,在生产系统中我们使用一种算法,而在测试中我们可以使用另一种算法来验证其结果是否一致。
  • E——强制产生错误条件
    在实际运行过程中,有时候会发生一些意外的难以避免的错误,例如磁盘会满,网络连线会断开.....从而导致程序崩溃。我们应该在测试中强制引发错误,来测试代码是否能够按照预期处理这些异常。
  • P——是否满足性能条件
    性能同样是我们测试过程中需要验证的指标

3.2 注意边界条件

单元测试的目标是验证我们的工作单元,但是如果这个工作单元依赖一些其他的对象或是一些难以操控的东西,比如网络、数据库等。这时我们就要使用mock对象,使得在运行UT的时候使用的那些难以操控的东西实际上是我们mock的对象,而我们mock的对象则可以按照我们的意愿返回一些值用于测试。通俗来讲,Mock对象就是真实对象在我们调试期间的测试品。对于外部对象内的逻辑我们并不关心,我们只需要让它给我们返回我们想要的值,来验证我们的业务逻辑即可

IFileExtensionManager fileManager;

public bool IsValidFileName(){
    //获取文件扩展名
    string extName=fileManager.GetExtName();
    if(extName=="jpg"){
        return true;
    }
    return false;
}

如上示例,假设从文件系统中读取一个文件,获取文件的扩展名,如果扩展名是jpg就返回true,否则返回false。
注意,这里我们要测试的逻辑是如果扩展名是jpg就返回true,否则返回false。而对于fileManager.GetExtName()方法内部的逻辑是什么样的的我们是不关心的,我们只需要mock这个方法使其返回我们想要的值就可以了。
关于具体如何去mock工作单元中的一些外部依赖,会在存根与模拟对象里面详细进行总结。

总结

本文总结了什么是单元测试、什么不是单元测试以及优秀的单元测试有哪些特性,简单介绍了如何进行单元测试。
编写差劲的单元测试是没有意义的,我看到过很多公司尝试去实践单元测试,但最终要么在某个阶段放弃了,要么并没有真正执行单元测试。最终还是依赖集成测试或者人工测试来发现问题,不得不以失败而告终,并堂而皇之的认为单元测试是一个耗时好力而无功的鸡肋东西。
因此如果你想要真正的去实践单元测试,那么必须充分的理解到底什么是单元测试,已经如何去更好的进行实践优秀的单元测试。
而对于如何更好的去实践单元测试,后续会结合实践用更多的篇幅去总结分享。

出处:https://www.cnblogs.com/hunternet/p/14311421.html