`
lantian_123
  • 浏览: 1368211 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

关于mock和stub

 
阅读更多
注:这篇文章来自于 意外收获,关于mock和stub 中的评论,由于pocket没法把评论也收藏,因此特摘抄了 frostred 的评论,文章版权归原作者所有
很高兴前面的一点文字能对你有所帮助。事实上,写东西时候,也是对自己的思想整理和精炼的过程,所以可以说是互相帮助吧。你要是有什么疑问或不同意见,可以指出来,我们可以再深入探讨。

好了,下面说说我对Mock/Stub区别的看法。

首先,我想再强调一下使用Mock/Stub的目的,那就是,去代替那些被测试代码所依赖的,但不可信赖东西。不管这些东西是什麽,当然最终表现出的还是class。 如class BlogDao, 它不可信赖是因为它访问数据库,class ConfigReader, 它不可信赖是因为它访问配置文件。class MyStringParser, 它不可信赖是因为它有很多逻辑,而且还没有对它进行足够的测试。当然,如果你对它进行足够的测试,你也可以认为它可以信赖。例如,你有一个class MyStringProcessor, 它用到了MyStringParser, 当你测试MyStringProcessor 时,你就有选择是隔离MyStringParser 还是不隔离。注意这一选择是建立在你是否认为MyStringParser 可以信赖的基础上,而不是创建的成本是否很高的基础上。当然,在现实生活中,创建的成本高,往往意味着它用到了外部资源,而用到外部资源也就意味着它不可信赖,也就是它必须被隔离。这也是很多人误以为“创建的成本是否很高”就是判断是否需要隔离的条件。
以上又废了好多话强调使用Mock/Stub的目的,不过我一直认为理解目的是最重要的,目的理解了,其它就容易明白了。

Back to Mock/Stub, 不知你注意了没有,我一直没用Mock这个词做动词,我用的是“代替”或 “隔离”,在这里“代替”和“隔离”是一个意思 。(“隔离”或许更准确些,但“代替”更容易理解,而Mock(动词)是一可非常不准确的词)。那么我们用什么来代替或隔离呢?答案是,Stub / Mock objects。那么,为什么会有Stub / Mock 的区别呢。这是因为Stub / Mock 在测试中扮演的角色有细微的差别,这一差别其实又取决于“被隔离对象”在“被测试对象”里扮演角色的差别(对不起写得有点绕嘴,希望你能看明白)。

其实,分得细点,不只有Stub / Mock,还有其它类型。如,在 xUnit Test Patterns 这本书里,它把这类对象统称为Test Double(因为stunt double 在电影里是替身的意思)。具体的类型有
• Dummy Object
• Test Stub
• Test Spy
• Mock Object
• Fake Object

其中,Fake Object是指一个假的(相对于现实要用到的),但完整的实现。如, InMemoryBlogDao,相对于 SqlBlogDao,它不真的访问数据库,但它是一个对 BlogDao 接口 的完整实现。
其他的类型,我认为,Dummy Object,Test Stub,Test Spy基本可以归为Stub,剩下的Mock Object 当然是Mock。
首先看Dummy Object,测试代码需要Dummy Object是因为有了它才能通过编译,测试才能跑起来,但其实测试中可能根本就用不到它。例如,创建BlogService 需要 BlogDao,但你可能测试BlogService 的一个方法,它根本就没用到BlogDao。此时,你可以用 new BlogService(new NullBlogDao()),  NullBlogDao 就是Dummy Object,因为它的存在只是为了通过编译,它根本就不参与测试。

Test Stub 参与测试, 但你不在乎它是何时何地以何种方式参与测试的,它的存在是为了让测试跑起来。非常常见的情况是你需要它提供一些返回值。例如,你可以用HttpContextStub 来代替真正的HttpContext, 用它提供例如SessionId, ResquestParameter 之类的值。你的测试可能会用到这些值,但你不会去验证是不是getSessionId() 被调用了,更不会去验证它是何时何地以何种方式被调用的。

Test Spy 不但参与测试,你还要验证它的参与产生了某种结果。如BlogService 例子。你可以定义一个TestSpy:
public class BlogDaoTestSpy implements BlogDao {
public Blog savedBlog = null;

public void save(Blog blog) {
savedBlog = blog;
}
}
那么你的测试可写成这样:
@Test  
public void testSaveBlog(){  
Blog blog = new Blog();    
blogService.save(blog);  
assertEqual(blog, ((BlogDaoTestSpy)blogDao).savedBlog);
}  
注意,在测试中我们验证了blogService.save(blog) 会导致blogDao的savedBlog 产生变化,但我们不去验证blogDao是以何种形式参与测试而导致这一变化的。

下面终于该Mock Object 出场了,事实上,你的测试就是很好的Mock例子。在测试中,你验证了如果blogService.save(blog) 被调用,blogDao的save(blog) 一定也会被调用,而且被调用时,参数一定是 blog。也就是说,你验证了blogDao必须以这种特定的形式参与测试。

通过比较,我们可以看到,从Dummy Objec到 Mock Object,测试代码对TestDouble 的要求越来越强,验证的内容也越来越强。这种强制约有好处也有坏处,不过,总的来说,我们希望够用就行。换句话来说,如果能验证代码的正确性,如果Stub够用,就不要用Mock,因为Stub比Mock简单,Stub 对被测代码的制约也小的多,所以被测代码改起来也更容易。
为什么有些时候必须用Mock呢?一个常见的情况是需要参与的方法没有返回值。例如blogDAO.save(blog)。首先,我想说的是,这行代码非常重要,是一定要测试到的。不然的话,你把这行从BlogService中删掉,都没有测试报错,这显然不对。问题是blogDAO.save(blog) 没有返回值,我们怎么才能知道它被正确调用了呢。当然,我们可以用TestSpy,象上面的例子。不过,一般的Dynamic Mock framework 都不支持象上面那类的TestSpy,所以你要手写TestSpy。如果你不愿手写,我认为用Mock是完全可以接受的。这里我还想说的是,它没有“深入到DAO的接口设计中去了”, 因为你的测试只是验证blogDao的save(blog) 会被调用,而没有验证save(blog)的结果是不是正确。如果你验证save(blog)的结果是不是正确,那才是“深入到DAO的接口设计中去了”。所以总的来说,我认为你用Mock 测试是perfectl valid。一点小毛病是,在测试中,你没必要去setTitle("title"), setContent("content"),setCreatedTime(new Date()),这些跟你要测的东西没有任何关系。

一些comments:
“测试代码本来只需要知道传什么参数给Service,并且预期测试Service返回什么值就够了,管它DAO调用什么方法干嘛?”

--理想情况下是,问题是, Service没有返回值,更讨厌的是,连DAO都没有返回值,我们有不能让它沉到太平洋里去,那怎么测试,只好用没办法的办法,验证blogDao的save(blog)将 被调用。

“整个Service的实现对测试代码应该是不可见的“
--理想情况下是,现实中常常不是,尤其是Service class 自己没什么逻辑,but just some interaction with other classes. 例如,Service class 接受一个DTO参数, 然后,用Mapper 把它Map 成Entity object,用Validator 去 validate, 用 logger 写 一个 log, 最后用DAO 存到数据库。这种情况下,你不得不做一些基于Mock 的Interactive 测试。

最后,我的观点,
• 尽量少用 Mock
• 该用Mock的时候就用,Mock没什么可怕的
• 明白测试的目的是最重要的
7 楼 yuan 2009-11-04  
hi,frostred,谢谢你的回复,你的观点让我对mock/stub的作用有了更深的认识。另外我更想弄明白的是,mock和stub之间有多大区别,分别使用于什么场景,如果你有时间,希望可以谈谈你的看法
6 楼 frostred 2009-11-04  
你的文章写得很好,不过里面有不少对Mock/Stub的误解。
首先,我们来看看为什么需要Mock/Stub。
抛开TDD, 单从UnitTest的角度来讲,我们说,一个好的测试必须是一个“值得信赖”的测试。做到值得信赖, 它必须:
• 当它通过,我们有信心说被测试代码一定工作。
• 当它失败,它一定证明被测试代码是错误的。

其中第二点与Mock/Stub的关系更紧密些。注意,第二点的重点是“被测试代码”,当测试失败是,一定是什么地方出了问题。我们想要知道的是“被测试代码”出了问题,而不是其它的地方。所以,让我们来看看为什么当测试失败时,它不能证明被测试代码是错误的。最常见的原因是:被测试代码用到了不可信赖东西。例如, 我们常听到:
• 虽然测试失败,但是我的代码没问题,可能是配置文件让谁改了吧。
• 虽然测试在这台Vista上失败,但在我的XP上通过,是系统问题, 不是代码问题。
• 测试失败是因为用了不一样的数据库,里面的数据不同导致失败。

所以说,外部资源(External Resource)是常见的不可信赖东西之一。
还有一个常见的不可信赖东西,那就是其他的class, 尤其是你自己的,未经任何测试的class。例如, 你想要测试ClassA,但在ClassA里,它用到了ClassB,ClassB也是你自己写的,而且它没有被测试过。所以,当ClassA的测试失败是,你怎么可能知道是ClassA,还是ClassB出了问题呢?

怎么解决这一问题呢,这就引出了Mock/Stub。我么需要Mock/Stub去代替那些不可信赖东西。到这,我想我们已经可以回答你的“为什么不对String进行mock” 的疑问。你认为String class是“不可信赖东西”吗?你的class 里用到了String, 当测试失败是,你会怀疑是String class 出了为题吗?如果回答是否定的,那我们说 “没必要对String进行mock”

对不起,没时间了,先写到这。。。
分享到:
评论

相关推荐

    mocha, mocha 是用于 ruby的mock和stub库.zip

    mocha, mocha 是用于 ruby的mock和stub库 mocha 描述用于模拟和stub的ruby 库。完整的。简单的和可以读的语法,完全&部分模拟。内置支持MiniTest和 Test::Unit 。由许多其他测试框架支持。安装 gem使用以下

    webmock, 在 ruby 中,对HTTP请求的stub和设置期望的库.zip

    webmock, 在 ruby 中,对HTTP请求的stub和设置期望的库 WebMock 在 ruby 中对HTTP请求进行存储和设置期望的库。特性在低HTTP客户机库级别的Stubbing请求( 在更改HTTP库时不需要更改测试)设置和验证HTTP请求的期望...

    置换测试:Mock,Stub和其他

    在理想情况下,你所做的所有测试都是能应对你实际代码的高级测试。例如,UI测试将模拟实际的用户输入(Klaas 在他的文章中有讨论)等等。实但际上,这并非永远都是个好主意。为每个测试用例都访问一次数据库或者旋转...

    Stub:灵活的Stub包装器,用于PHPUnit的Mock Builder

    -使用特征创建存根和模拟 -定义对模拟的期望 安装 默认情况下在Codeception中启用。 对于PHPUnit,请安装此软件包: composer require codeception/stub --dev 存根 可以使用Codeception\Stub静态调用来构造...

    jest-spy-mock-stub-reference:开玩笑.fn()和.spyOn()spystubmock断言参考

    开玩笑.fn()和spyOn()spy / stub / mock断言参考要求节点10 纱线1.x或npm设置克隆存储库运行yarn或npm install将安装所有必需的依赖项。npm脚本等效的npm run [removed]也应该起作用yarn test测试使用Jest运行...

    easymock详解教程

    mock和stub在测试领域中都是为了实现对测试对象(通常是待测代码)与其他系统组件之间交互的模拟,从而确保测试环境的纯净性。它们的主要作用在于: - **隔离测试对象**:通过替换实际的对象,避免了对外部系统的...

    .NET单元测试艺术PDF中文版.rar

    同时,还涉及mock,stub和框架(如Typemock Isolator和Rhino Mocks)等高级主题,旨在帮助读者逐步掌握高级的测试模式和结构,高效地为遗留代码和甚至根本不可测试的代码编写测试。书中还讨论了测试数据库时需要的工具和...

    Mockito Essentials(PACKT,2014)

    Explore test doubles and work with dummy, spy, fake, stub, and mock objects Uncover the Mockito architecture and build a custom mocking framework Mock, stub, and spy external code dependencies using ...

    基于流量回放技术进行中台建设 .pdf

    * 实现mock和stub 流量回放技术应用场景 流量回放技术可以应用于多种场景,例如: * produced environment中记录流量 * 在开发环境中回放流量 * 实现自动化测试 * mock和stub * 业务逻辑测试 流量回放技术优势 ...

    HttpMock:一个用于在测试和响应中即时创建Http服务器的库

    这对于集成和验收测试特别有用。 HttpMock在运行时返回罐装响应。用法。 首先,在要测试的应用程序中,使用HttpMock的URL更改要模拟的HTTP服务的URL。 告诉HttpMock监听您提供的端口。 这始终是本地主机,例如: _...

    cpp-stub函数打桩相关文件

    - **文档**:关于如何使用这些资源的说明文档,可能包含使用示例和最佳实践。 5. **应用场景** - **单元测试**:在单元测试中,通过桩函数替换外部依赖,保证测试的独立性和可重复性。 - **性能分析**:通过桩函数...

    如何使Singletons的行为可以被Mock对象覆盖

    然而,Singletons在单元测试中往往成为挑战,因为它们的静态实例化和不可变性使得它们难以被Mock或Stub。当我们需要测试与Singleton交互的代码时,无法替换Singleton的行为,这限制了测试的灵活性。本篇文章将探讨...

    cpp-CMock一个C的mockstub生成器

    `cpp-CMock一个C的mockstub生成器`是关于使用CMock这个开源工具在C语言开发中创建模拟对象(mock)和存根(stub)的实践介绍。CMock是一个专门针对C语言设计的工具,它允许开发者在单元测试中方便地生成mock对象,以...

    redismock:Go在Go的单元测试中模拟Redis

    Redismock 软件包github.com/elliotchance/redismock对于与Redis交互的单元测试应用程序很有用。 它使用了。 与使用真实或伪造的Redis(在下文中有更多介绍)不同, redismock提供了正常且美观的redismock ,以...

    Jeek - stub and mock generator for .NET-开源

    Jeek是一个针对.NET平台的专业工具,它专注于生成类、存根(stub)和模拟(mock)代码,极大地简化了单元测试的过程。在软件开发中,尤其是采用敏捷开发方法时,单元测试是确保代码质量、可维护性和可靠性的重要环节...

    easymock教程

    - **相同点**:Mock与Stub都用于实现系统隔离,即在测试过程中,用它们来替代次要对象(即依赖项),使得测试能够集中关注于主要测试对象的功能和行为。 - **不同点**: - **类实现方式**:Stub具有一个显式的类...

Global site tag (gtag.js) - Google Analytics