`
power1128
  • 浏览: 24378 次
  • 性别: Icon_minigender_1
  • 来自: 成都
文章分类
社区版块
存档分类
最新评论

对于单元测试的一些新认识

    博客分类:
  • java
阅读更多
    我也忘了怎么机缘巧合的看到了Martin Fowler先生的这篇大作:Mocks Aren't Stubs。确实写的深刻,使我对单元测试的目的和方式又有了新的认识。
 
    在这之前我就一直有个困惑,Unit Test究竟是黑盒测试还是白盒测试?其实这个问题换个说法也就是,到底是验证状态还是验证过程(behavior)?答案就在大神的这篇文章中。
 
    我将按照这篇文章的叙述顺序,逐一总结文章中的关键内容,并附带部分原文引用。,
 
    首先,Martin引入对mock和stub区别的思考。他一开始也认为两者是一致的( I saw them as similar for a while too)。后来渐渐认识到,它们主要有两个方面的区别:
    1. 测试结果如何被验证。是验证状态还是验证行为。(how test results are verified: a distinction between state verification and behavior verification
    2. TDD的方式不同。(a whole different philosophy to the way testing and design play together, which I term here as the classical and mockist styles of Test Driven Development.
 
    接下来,Martin通过对一个测试用例,使用传统和mock的方式分别写出具体的测试代码,来更直观的阐述两者的区别。具体的代码可以参考原文,这里我简单描述一下被测试的类。被测试的类为Order,它有一个方法fill(WareHouse wh),该方法从仓库对象中拣出需要数量的货品来满足订单。如果库存不足,订单的完成标记就为否,否则为是。
    
    传统测试是创建一个真实的WareHouse对象(如果WareHouse对象很复杂,不适合直接创建,则可能新建一个专门用于测试的Stub类对象),通过分别验证订单对象的完成标记和仓库对象的库存数量来验证测试是否通过。这就是传统的对状态的验证。
 
    第二个例子是通过mock方式来测试的。Matrin先用了jMock框架,然后用EasyMock又写了一遍。作者对比了两个框架的具体实现方式。jMock更类似于传统方式,先进行setup(与传统方式setup不同的是,其增加了对mock对象行为的expectation。从而在verify阶段减少了对mock对象状态的验证),然后exercise,最后verify和tear down。但其不支持直接对mock对象方法进行调用,而是通过类似反射的调用方式。
 
    EasyMock则支持进行真实的方法调用。这对于编译期检查和将来的自动化重构都有极大的好处。据说jMock也将支持真实方法调用。EasyMock引入了一个新的概念,叫“Record/Replay”。如果你看一下EasyMock测试的例子你就知道为什么它要引入这么一个模式。因为其无法区分你是在对行为进行expectation,还是真实的在exercise。它必须通过显示的调用replay,表示以后的操作将是exercise,而不是setup。这点和同样支持真实方法调用的Mockito不同。Mockito就不需要“Record/Replay”。因为其api可以显示的区分setup和exercice(不清楚的同学可以随便看两个例子就明白了)。因此我是比较喜欢Mockito的。因为其既支持真实方法调用,又不用显示的调用replay来区分record阶段和replay阶段。使得测试代码风格更接近传统的风格(setup,exercise,verify,teardown)。
 
    接下来,作者总结了mock和stub的区别。并且在这里引入了一个新的名词Double,作为所有对模拟依赖对象手段的总称(mock.stub都是Double的一种方式)。我理解作者的意思就是模拟依赖对象相当于真实依赖对象的双胞胎,长得一样(接口一致),但实现不一样。实现Double可以通过mock,或者stub。
 
    作者提出了Double的4种分类方式:Dummy, Fake, Stubs, Mocks。其实我也没太搞明白Dummy和Fake的分类依据。作者认为Stubs是专门为测试代码而写的实现,Stubs有可能会记录调用信息。而Mock是通过预先编写调用期望来实现的,并且通过验证期望行为而不是状态来决定测试是否通过。随后作者通过使用Mocks和Stubs方式来实现同一个测试用例,来解释两者的不同。
 
    接下来作者简单总结了一下传统TDD和Mock方式TDD的区别。作者说,传统方式的TDD会尽量采用真实对象。如果真实对象复杂到难以直接创建,那么就新建一个该对象的stub实现类,使用stub代替真实实现。不过最后仍然要通过验证stub对象的状态来确定测试是否通过。
 
    作者认为Mock方式的TDD,会始终采用mock的方式隔离依赖对象。
 
    下一节中(Choosing Between the Differences),作者更详细的从如何驱动TDD开发,测试基础设施创建等多个方面对Mock和Classic方式的测试进行比较。其中在Fixture Setup中,我觉得有一点总结的很好:使用Mock方式的人经常认为传统方式需要编写大量的Fixture代码,但使用传统方式的人认为这些基础设施可以重用,并且认为Mock方式必须为每一次基础设施创建mock对象(As a result I've heard both styles accuse the other of being too much work. Mockists say that creating the fixtures is a lot of effort, but classicists say that this is reused but you have to create mocks with every test)。我觉得这句话点出了两种测试风格的缺点。
 
    在介绍测试隔离性时(Test Isolation),作者从Mock Style支持者的角度,说明了传统测试并不是真正意义上的单元测试,而是一种迷你版本的集成测试(我以前一直被这个问题困惑。使用多个真实对象怎么能叫单元测试呢?因为别的类实现会影响当前被测试的类)。因为传统单元测试可能包括多个真实对象。同时,作者又强调了无论你选择何种风格的单元测试,都应该配以粗粒度的验收测试(acceptance tests)。
 
    接下来作者比较了两种风格对真实实现的耦合程度,显然mock方式耦合度更高。这也是我写mock形式的单元测试中遇到的最头疼的问题。如果你改变了调用的接口,甚至调用的顺序,都可能导致mock验证失败!而传统方式只验证状态。相当于起点和终点是确定的,但是其路径确有无数条。mock是在验证路径,而传统方式只验证是否到达终点。另外,mock风格的测试在编写的时候,还要被具体实现牵扯精力。比如你要想依赖的mock对象的接口是什么样的。
 
    作者在So should I be a classicist or a mockist这节中表明自己是一个传统的单元测试者,并没有深入的使用过mock方式的TDD。因此他将测试风格的选择权交给了读者。但他仍然对mock方式中,测试与被测代码耦合过高表示担心。
 
    最后总结下自己的收获:
  • 了解到传统方式的单元测试可能并不是真正意义上的单元测试(如果依赖对象使用其真实实现的话)。对于依赖对象,有可能采用真实实现。如果真实对象创建成本太高,则采用stub方式模拟。
  • 我认为Mock方式的测试适用了层与层之间代码调用。比如Service调用DAO。对于业务层对象来说,可能传统方式的测试更合适。
分享到:
评论

相关推荐

    实用软件单元测试指导

    - **“直接集成”误区**:还有一些人认为可以跳过单元测试环节,直接进入集成阶段,他们认为即使存在问题,也能在集成测试或系统测试阶段被发现。 - **“表面验证”误区**:最后一种误解是将单元测试视为仅是对代码...

    软件测试(单元测试)

    本实验旨在帮助学习者理解单元测试的基本原理及其实施过程,通过具体实践加深对单元测试技术的认识。 #### 开发环境与工具 本次实验选择在Windows 2000(SP2) 或 Windows XP 操作系统上进行,使用C++编程语言,并...

    《认识人民币》单元测试卷.docx

    这篇文档是针对人教版一年级数学下册《认识人民币》单元的测试卷,主要涵盖了人民币的基本知识、货币单位转换、比较大小以及实际购物场景的应用。试卷主要包括以下几个部分: 一、填空题: 这部分主要考察学生对...

    测试员速成-单元测试

    在实际工作中,一些错误的认识常常成为阻碍有效进行单元测试的主要因素: - **时间浪费论**:“单元测试太浪费时间了,我们现在要赶进度。”事实上,良好的单元测试能够在早期发现并修复问题,从而节省更多的调试...

    JAVA单元测试JUnit

    ### JAVA单元测试JUnit的核心知识点详解 #### 一、JUnit概览与重要性 JUnit作为一款卓越的Java单元测试框架,自问世以来便以其强大的功能和易用性深受开发者喜爱。由软件大师Erich Gamma和Kent Beck共同打造,...

    一年级下册第5单元《认识人民币》单元测试卷及答案(三套).pdf

    10. 答案核对:提供单元测试的参考答案,帮助学生自我检查和理解他们的解题是否正确。 以上知识点是小学一年级下册《认识人民币》单元的主要教学内容,旨在培养孩子的基本货币认知能力和简单的数学运算能力,同时为...

    初中物理九年级《第三章-认识电路》单元测试卷及答案..pdf

    初中物理九年级《第三章-认识电路》单元测试卷及答案..pdf

    1.1新人教版小学四上数学第1单元《大数的认识》测试题A.doc

    1.1新人教版小学四上数学第1单元《大数的认识》测试题A.doc

    一年级数学上册第五单元11-20各数的认识测试卷精选.doc

    这份测试卷全面覆盖了一年级第五单元11到20各数的认识,既包含了基础知识的考察,也有对基本运算技能的检验,旨在帮助孩子们巩固课堂所学,提升数学素养。通过这样的练习,孩子们能够更好地理解数字,提高数数、比较...

    单元测试大揭密单元测试大揭密

    - **回归性**:自动化单元测试能够有效防止代码回归问题,确保新添加的功能不会破坏原有功能。 #### 2. 单元测试的基本理论 单元测试是软件开发过程中的一个重要组成部分,其理论基础包括以下几个方面: ##### ...

    部编版一年级数学上册第七单元测试卷含答案(一).pdf

    根据提供的信息,文件《部编版一年级数学上册第*单元测试卷含答案(一).pdf》和描述似乎是关于小学生数学教育的测试卷,其中涉及到基础的时间概念学习,如时钟的认识和时间的读法。但是,由于提供的【部分内容】并非...

    青岛版一年级数学下册《第七单元测试卷》(附答案).pdf

    第*单元测试卷中的内容旨在评估学生对于该单元数学知识点的掌握情况。此资源对于青岛版教材的使用有着特定的适用性,尤其适合一年级下学期的学生。由于提供了答案,因此家长、教师或学生可以利用这份测试卷进行自我...

    人教版一年级数学上册第二单元测试卷及答案.pdf

    从文件信息中提供的内容来看,该文件名为《人教版一年级数学上册第*单元测试卷及答案.pdf》,属于教育领域中的一年级数学教材。尽管提供的部分内容由于OCR技术原因存在识别错误或漏识别的问题,但我们仍然可以从中...

    《认识人民币》单元测试题.pdf

    《认识人民币》单元测试题.pdf

    新三年级下数学单元测试卷及解析第8单元-小数的初步认识.pdf

    新三年级下数学单元测试卷及解析第8单元-小数的初步认识.pdf

    青岛版五年级数学下册第七单元测试题.pdf

    从标题和描述中我们可以得知,这份文件是《青岛版五年级数学下册第*单元测试题.pdf》。然而,所提供的【部分内容】部分包含了许多看似随机的数字、字母和符号,实际这些内容更可能是OCR扫描错误的产物,并没有实际的...

    新版一年级下册语文第七单元测试卷资料.pdf

    根据提供的信息,【标题】和【描述】描述的是同一个内容:“新版一年级下册语文第*单元测试卷资料.pdf”。【标签】标记为“资料”。然而,【部分内容】提供的信息是一串无法解读的字符,似乎是因为OCR扫描导致识别...

    单元测试之道(使用NUnit).doc

    1. **误解:**一些开发人员认为单元测试是测试团队的责任,而非开发者的任务。这种观念源于对软件开发流程的传统理解,但实际上,单元测试应该是开发过程的一部分,由编写代码的人负责。 2. **收益不明确:**许多人...

    2021新北师大版小学一年级上册数学《第一单元测试卷(二)》.pdf

    这篇文档是针对小学一年级学生设计的一份数学单元测试卷,主要涵盖了基础的数学概念和比较数字的技能。以下是对这份测试卷中涉及知识点的详细解释: 1. 数字排序:题目要求将9、6、0、3进行排列。这是对数字顺序的...

Global site tag (gtag.js) - Google Analytics