单元测试培训系列:(一)单元测试概念以及必要性

说起单元测试,多数同学应该都知道或听过,可能不少同学认为自己也写过,甚至觉得单元测试很简单有什么好培训的?其实这个事情还真没想象的那么简单!我基本可以比较负责任的说,你若没深入对单元测试做过研究,不知道Mock对象为何物的话,那么可能你以前写过的单元测试压根就不是单元测试。

  单元测试是什么?

  这个问题其实并不太容易一两句话说得特别清楚。先借用下百度百科的定义:

  单元测试是在软件开发过程中要进行的最低级别的测试活动,在单元测试活动中,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

  从以上这句定义我们可以看到,两个提取到到两个非常关键的字:最小粒度、隔离

  ● 单元测试是测试的最小单位,必须可信任的,可重复执行的。

  ● 如果测试的范围轻易的就会扩展到其他类或同类的其他方法,那就不再是最小单位,也就不是单元测试了!

  例如:

  类A中的方法CallMethod中调用了类B中的方法DoMethod,如果在编写测试的时候不把B类中的DoMethod隔离出来,造成单元测试CallMethod方法时,实际实行了DoMethod方法,那么这个测试方法不能算作是单元测试。(如何隔离会在后文详解)

  单元测试的目的是什么?

  有人曾给我一段非常简单的代码片段:一个方法,里面只是调用若干个其他方法,甚至也都没有任何返回值,然后问我这种代码写单元测试有任何价值?!完全是浪费体力!!

    public class ClassA
    {
        public void CallMethod()
        {
            DoSomethingForYou();
            DoSomethingForThem();
            DoSomethingForMe();
        }
    }

  其实提出这个疑问主要有两个原因导致:未能理解单元测试的目的是什么以及这段代码的可测试性并不高。(可测试性以及如何提高将在后面章节介绍)

  单元测试的目的是用来确保程式的逻辑如你预期的方式执行,而并不是用来验证是否符合客户的需求的!通过单元测试来建立一道坚实的保障,防止代码在日后的修改中不会被破坏掉。

  是不是很失望?单元测试并不是用来验证代码是否符合需求的。

  事实上,单元测试是白盒测试的一种,而且需要开发人员来完成,最好是谁开发的代码就该谁来编写单元测试代码,因为代码的编写者最熟悉该段代码的目的,进而编写出验证该目的是否达到的单元测试代码。

  单元测试并不能用来代替其他测试手段,不过是实践过程中确实会很有效的帮助开发人员自查代码,进而发现一些潜在的BUG。但这只是一个额外的收获,若不是采用TDD那样的测试先行开发方式,那么单元测试的根本目的不是用于也无法检验当前代码是否存在BUG的!

  上面有说到单元测试最好是由开发人员自己来编写,用于验证该段代码是否符合开发者开发的预期要求。这里可能会有个疑问,既然开发者自己已经很清楚自己想要 结果是什么,直接运行一遍代码实际跑一次,通过断点调试不是就可以很方便的验证了嘛?再通过编写代码的形式,甚至比开发这个功能本身更多的代码,去验证这 个方法是否符合编写的目的,不是很傻很笨很累的办法么?

  也许通过一个大家经常会碰到的实际场景能更好的说明:

  一个项目开始,项目经理把需求拆解为若干个模块分发给不同的开发人员去完成。这样每个人可能只熟悉自己的那部分代码。当项目某个阶段开发完成并上线后,可能部分开发人员会离开项目进入别的新项目,留下个别人员继续维护;或者项目下阶段开发新进一大批人员并不熟悉当前项目;当然最常见的是,在修改BUG阶段是无法完全做到谁产生的BUG就安排谁去修改。

那么这时候就会出现一种常见的情况:因为对当前代码要满足的各种目的不熟悉,在修改一个模块或者BUG的时候把原有正确的功能也影响到了!更要命的是,谁也不知道这个BUG出现了,等待测试人员需要去重新发现一遍。

  于是项目经理会发现,每次只要做了代码修改,无论是重构还是功能新增修改,还是修改了BUG,都无法知道当前代码的健壮性,以前编写的东西是否依然正确可用?

  然而如果这个项目在一开始就编写了单元测试的话,我们可以通过方便的自动化单元测试框架运行所有的单元测试,进而检查在此次修改前的所有被单元测试所覆盖的代码是否依然正常运行(符合以前编写的单元测试期望,如果验证通过,则认为原有代码未受到影响)

  由上我们可以看出,单元测试虽然增加了相当大的开发工作量,但对于一个长期不断改进和维护的项目而言,单元测试反而是消减整体成本的一个有效手段,它能及时而准确的发现在代码修改之后,原来对代码要求的功能是否都依然正确满足。

  但这里有个严重缺陷:单元测试无法检测到某个方法修改后是否对其他方法造成了影响,只能检测到被修改的方法本身的原有目的是否被影响(这个将在下面的与集成测试的区别中详解)

  也因此,个人觉得单元测试最适合的场景是基于TDD开发。若需求发生改变,修改了一个方法,而多数情况下也会去修改单元测试代码,因为预期也发生了改变,这个时候又不能检测到对其他代码的影响,这时单元测试意义确实不大。

  单元测试与集成测试的区别是什么?

  多数人其实一直不能很好的区分集成测试和单元测试,甚至不少人一直理解的单元测试只能算是集成测试,但其实两者的概念是完全不同的。

  单元测试测试的对象是每一个独立的方法,而且尽可能的隔离方法和其他方法以及其他外界依赖项;

  集成测试的测试对象是被单元测试检测后的方法与方法之间的调用关系,以及调用执行过程是否符合预期。

  ● 针对项目的一部份或全部进行测试,可以跨越不同的类别与方法,并可直接存取的外部资源。例如: File I/O, 数据库操作, 网络连接, …

  ● 通常做集成测试都会需要先设置(Configure)测试所需的环境,测试完毕后通常要清除测试所产生的残留资料,以利下次测试或避免影响其他整合测试的结果。

    ○ ClassInitialize Attribute

    ○ ClassCleanup Attribute

    ○ TestInitialize Attribute

    ○ TestCleanup Attribute

  以上这些属性常用于集成测试,不能出现在单元测试中。

  单元测试的三大基本要素(Trustworthiness/Maintainability/Readability)

  1、信任你的测试代码结果

    1)你是否能信任你的测试结果?

    2)当它通过,我们有信心说被测试代码一定工作。

    3)当它失败,它一定证明被测试代码是错误的。

4)如果你不断的对测试结果失去信心,那么你也不会继续坚持撰写单元测试。有

    5)许多人搞不清楚单元测试与集成测试的差别,以致于感觉自己写的单元测试过于薄弱而不相信测试的结果。

    6)如果你因为某些原因导致测试失败,直接去改Code或直接去改Test Code都不是好事,你的首要目的是要能找出测试失败发生的主因,而非只是看错误这件事,这样你才能信任你的测试程式。

  2、测试代码的可维护性

1)是否能够持续的维护你的测试程式?

    2)如何有效的降低维护测试程式的成本?

    PS:透过一些Testable Design Pattern 可以有效提升可维护性。例如: Repository Pattern, Service Pattern……

  3、测试代码的可读性

    1)你的测试程式的命名是否易于理解?

    2)当你测试失败时是否能从测试失败的测试方法(TestMethod)明确看出实际失败的原因?

    3)当读取测试数据的人看不懂你的测试,人们就不会执行这些测试、也不会去维护这些测试,久而久之就会越来越恶化。

  Test Driven Development & Unit Test

  写在本文最后,其实我一直觉得单元测试其实是为了TDD开发模式而诞生的,在这种开发模式下使用单元测试完全是非常顺畅的:

  1、根据软件需求文档拆解软件功能,并设计出功能模块划分;

  2、根据需要的功能模块设计出单元测试场景用例,因为此时可以很清晰的知道能够提供什么样的数据,以及需要达到什么样的功能,这对设计单元测试用例已经完 全足够了;

  3、编写单元测试代码,这个时候可以专注于检验这个方法的是否满足设计的要求,此时甚至实际的代码还根本没开发,而.NET 4.0的Dynamic关键字在这里可以得到充分的发挥:调用那些根本都还不存在的方法,却不会导致编译无法通过。

  4、若在编写单元测试过程中,可以预期当前这个方法若需要调用一些其他类或方法的支持,可以通过编写Mock Object来模拟,同样也是无需实现真正的代码,只需要有基本的代码框架或者接口即可。

  5、在为这个方法编写好单元测试代码之后,就可以开始编写实际的代码实现了,因为在之前为了满足Testability的需要,代码已经是基于依赖倒置模 式的了,无需再担心其他需要调用的类或方法是否已经实现或正确实现。在编写好本方法的实现之后就可以通过运行之前的单元测试进行验收了。

  可以看到,若按照以上这种方式进行开发,首先代码的耦合性是非常低的,其次代码的质量也是很高的,最后还会因为代码之间的耦合度低从而降低在开发过程中, 相互制约进度相互影响的可能性。在追查BUG的时候也很有优势:很容易查到BUG是否蔓延。

  反之,对一个Legacy System进行重构使之Testable,再编写单元测试其实工作量不小,实际的收益也不会特别大。

  单元测试的基本概念以及价值就基本讲完,下篇文章将开始介绍Visual Studio 2010中的单元测试工具与环境。

本文出自seven的测试人生公众号最新内容请见作者的GitHub页:http://qaseven.github.io/

时间: 2024-12-09 10:53:15

单元测试培训系列:(一)单元测试概念以及必要性的相关文章

E-Mapreduce培训系列之基本介绍

概述 本节介绍E-Mapreduce的一些基本的概念,优势,我们做了啥,演示了如何创建一个集群.如果大家有啥问题,欢迎直接在留言区域回复. 视频 如果没有播放,请大家点击: E-Mapreduce培训系列之基本介绍播放 E-Mapreduce培训序列之基本介绍PPT下载 基本的概念 集群 作业 执行计划 E-mapreduce产品的介绍 E-MapReduce是什么? E-MapReduce 是一项Web服务,简化了大数据处理,提供的大数据框架可以让您轻松.高速.经济.安全.稳定地处理大数据,满

E-Mapreduce培训系列之离线计算

概述 对初学者或者想用Emapreduce平台的同学可以看下视频,里面有实际的操作.如果大家有任何的问题,欢迎留言. 视频 如果没有播放,请大家点击:E-Mapreduce培训系列之数据同步播放地址 E-Mapreduce培训系列之数据同步PPT下载地址 主要介绍 基本架构 我们会上传一篇文章到OSS中,再启动emapreduce hadoop mr(也可以用hive的)做离线分析,这里 的分析是统计文章每个单词出现的个数.这也是一个典型的存储计算分离的分析场景. 下载.编译代码 git clo

E-Mapreduce培训系列之流式计算

视频见: 如果没有播放,请大家点击:E-Mapreduce培训系列之流式计算视频地址E-Mapreduce培训系列之流式计算PPT下载地址 介绍 基本架构 我们的数据来源自logservice,我们在logservice上配置一个数据源,此数据源是监控我们EMR集群的master机器的一个文件的,当这个文件增加内容时,数据就会被logservice采集到,sparkStreaming就会消费这个消息. 下载.编译代码 git clone https://github.com/aliyun/ali

Android系列----JUnit单元测试的使用

[正文] 一.单元测试的引入: 如果只是普通的一个小程序,编写测试是有些多此一举,但是当项目比较庞大的时候,一般都应该去编写单元测试.JUnit测试是白盒测试,即主要是程序员自己对开发的方法进行功能性测试.JUnit是一套框架,Android中也沿用了这一套框架. 在Android中使用JUnit测试大致分如下几个步骤: (1)在AndroidManifest.xml中增加对JUnit的支持,并制定测试项目包. (2)在AndroidManifest.xml中<application.../>

整合网络营销公益培训系列一:什么是seo思维

大家好,又是一个礼拜没有写什么文章投稿了!因为最近一直在忙准备汇道整合网络营销公益培训的系列课程,昨晚就为60多名朋友上了一课!非常感谢朋友们的围观,同时也在这篇文章再相信介绍一下我们培训的课程内容,毕竟当时在我的QQ里有20多个关于交流seo的网络营销QQ群发布,但是还是只是来了60多个人,可能是很多人都知道什么是seo,所以不屑一顾,但是朱卫坤还是希望让更多人了解我们的培训课程! 昨晚关于什么是seo思维的课程,现场与大家互动的,毕竟思维就是要让更多的人去思考,而不大家都来听我的一面之词,需

云计算-从基础到应用架构系列-云计算的概念,互联网营销

开篇       本篇是主要讲述什么是云计算,并且讲述云计算相比之前的一些比较新兴的计算模式之间的区别,并且简单的分析下云计算相比这些传统的企业应用模式之间的优势及优缺点.由于本篇是云计算系列的开篇,可能概念性的内容相对来说多一些,我会尽量讲述的生动一些,加深大家对云计算的理解,当然如果您在本文中发现错误之处,那么请您批评指出,谢谢. 摘要       "云计算"这个词,我想大伙肯定听过不止一次了,但是可能我们经常会被同行的同事或者业界的朋友问道,什么是云计算,云计算是干什么的?可能让

SOA系列之基本概念

  SOA是面向服务架构,面向服务(SO:Service Orientation)代表的是一种设计理念,和面向对象(OO:Object Orientation).面向组件(CO:Component Orientation)一样,对关注点进行分解的思想,面向服务是和技术无关的.   面向服务的体系结构,是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来.接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台.操作系统和编程语言.这使得构建在各

第二章 单元测试的基本概念和核心技法

第二章 单元测试的基本概念和核心技法 2.1 良好的单元测试--定义 我们已经了解了程序员需要单元测试,下面我们来给单元测试作一个完整的定义: ● 定义: 单元测试是一段自动执行的代码,它调用被测类或被测方法,然后验证关于被测类或被测方法逻辑行为的假设确实成立.单元测试几乎总是用单元测试框架(unit testing framework)来写就的,单元测试是易于写就.执行快速.完全自动化.值得依赖.易于阅读并易于维护的. 这个定义有点长,但是它却包含了大量重要信息: ● 单元测试的测试重点是被测

Android单元测试 - 验证函数参数、返回值的正确姿势

前言 读者有没发觉我写文章时,喜欢有个前言.序?真相是,一半用来装逼凑字数,一半是因为不知道接下来要写什么,先闲聊几句压压惊^_^ 哈哈哈......该说的还是要说. 上一篇<Android单元测试 - Sqlite.SharedPreference.Assets.文件操作 怎么测?> 讲了一些DAO(Data Access Object)单元测试的细节.本篇讲解参数验证. 验证参数传递.函数返回值,是单元测试中十分重要的环节.笔者相信不少读者都有验证过参数,但是你的单元测试代码真的是正确的吗