`
wh870712
  • 浏览: 40617 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
文章分类
社区版块
存档分类
最新评论

Stub & Mock的区别

阅读更多
作为测试的基本概念,在开发测试中经常遇到mock和stub。之前认为自己对这两个概念已经很明白了,但是当决定要写下来并写清楚以便能让不明白的人也能弄明白,似乎就很有困难。

    试着写下此文,以检验自己是不是真的明白mock和stub。

一. 相同点
   
    先看看两者的相同点吧,非常明确的是,mock和stub都可以用来对系统(或者将粒度放小为模块,单元)进行隔离。

    在测试,尤其是单元测试中,我们通常关注的是主要测试对象的功能和行为,对于主要测试对象涉及到的次要对象尤其是一些依赖,我们仅仅关注主要测试对象和次要测试对象的交互,比如是否调用,何时调用,调用的参数,调用的次数和顺序等,以及返回的结果或发生的异常。但次要对象是如何执行这次调用的具体细节,我们并不关注,因此常见的技巧就是用mock对象或者stub对象来替代真实的次要对象,模拟真实场景来进行对主要测试对象的测试工作。

    因此从实现上看,mock和stub都是通过创建自己的对象来替代次要测试对象,然后按照测试的需要控制这个对象的行为。


二. 不同点

    1. 类实现的方式

        从类的实现方式上看,stub有一个显式的类实现,按照stub类的复用层次可以实现为普通类(被多个测试案例复用),内部类(被同一个测试案例的多个测试方法复用)乃至内部匿名类(只用于当前测试方法)。对于stub的方法也会有具体的实现,哪怕简单到只有一个简单的return语句。

        而mock则不同,mock的实现类通常是有mock的工具包如easymock, jmock来隐式实现,具体mock的方法的行为则通过record方式来指定。

        以mock一个UserService, UserDao为例,最简单的例子,只有一个查询方法:

Java代码
public interface UserService {  
    User query(String userId);  
}  
 
public class UserServiceImpl implements UserService {  
    private UserDao userDao;   
    public User query(String userId) {  
        return userDao.getById(userId);  
    }  
    //setter for userDao  
}  
 
public interface UserDao {  
    User getById(String userId);  


public interface UserService {
User query(String userId);
}

public class UserServiceImpl implements UserService {
private UserDao userDao;
public User query(String userId) {
return userDao.getById(userId);
}
//setter for userDao
}

public interface UserDao {
User getById(String userId);
}


        stub的标准实现,需要自己实现一个类并实现方法:

Java代码
public class UserDaoStub implements UserDao {  
    public User getById(String id) {  
        User user = new User();  
        user.set.....  
        return user;  
    }  
}  
 
 
@Test 
public void testGetById() {  
    UserServiceImpl service = new UserServiceImpl();  
    UserDao userDao  = new UserDaoStub();  
    service.setUserDao(userDao);  
 
    User user = service.query("1001");  
    ...  


public class UserDaoStub implements UserDao {
public User getById(String id) {
User user = new User();
user.set.....
return user;
}
}


@Test
public void testGetById() {
UserServiceImpl service = new UserServiceImpl();
UserDao userDao  = new UserDaoStub();
service.setUserDao(userDao);

User user = service.query("1001");
...
}


        mock的实现,以easymock为例,只要指定mock的类并record期望的行为,并没有显式的构造新类:

Java代码
@Test 
public void testGetById() {  
    UserDao dao = Easymock.createMock(UserDao.class);  
    User user = new User();  
    user.set.....  
    Easymock.expect(dao.getById("1001")).andReturn(user);  
    Easymock.reply(dao);  
 
    UserServiceImpl service = new UserServiceImpl();  
    service.setUserDao(userDao);  
    User user = service.query("1001");  
    ...  
    Easymock.verify(dao);  


@Test
public void testGetById() {
UserDao dao = Easymock.createMock(UserDao.class);
User user = new User();
user.set.....
Easymock.expect(dao.getById("1001")).andReturn(user);
Easymock.reply(dao);

UserServiceImpl service = new UserServiceImpl();
service.setUserDao(userDao);
User user = service.query("1001");
...
Easymock.verify(dao);
}


        对比可以看出,mock编写相对简单,只需要关注被使用的函数,所谓"just enough"。stub要复杂一些,需要实现逻辑,即使是不需要关注的方法也至少要给出空实现。

    2. 测试逻辑的可读性

        从上面的代码可以看出,在形式上,mock通常是在测试代码中直接mock类和定义mock方法的行为,测试代码和mock的代码通常是放在一起的,因此测试代码的逻辑也容易从测试案例的代码上看出来。Easymock.expect(dao.getById("1001")).andReturn(user); 直截了当的指明了当前测试案例对UserDao这个依赖的预期: getById需要被调用,调用的参数应该是"1001",调用次数为1(不明确指定调用次数时easymock默认为1)。

        而stub的测试案例的代码中只有简单的UserDao userDao  = new UserDaoStub ();构造语句和service.setUserDao(userDao);设置语句,我们无法直接从测试案例的代码中看出对依赖的预期,只能进入具体的UserServiceImpl类的query()方法,看到具体的实现是调用userDao.getById(userId),这个时候才能明白完整的测试逻辑。因此当测试逻辑复杂,stub数量多并且某些stub需要传入一些标记比如true,false之类的来制定不同的行为时,测试逻辑的可读性就会下降。

    3. 可复用性

        Mock通常很少考虑复用,每个mock对象通过都是遵循"just enough"原则,一般只适用于当前测试方法。因此每个测试方法都必须实现自己的mock逻辑,当然在同一个测试类中还是可以有一些简单的初始化逻辑可以复用。

        stub则通常比较方便复用,尤其是一些通用的stub,比如jdbc连接之类。spring框架就为此提供了大量的stub来方便测试,不过很遗憾的是,它的名字用错了:spring-mock!

    4. 设计和使用

        接着我们从mock和stub的设计和使用上来比较两者,这里需要引入两个概念:interaction-based和state-based。

        具体关于interaction-based和state-based,不再本文阐述,强烈推荐Martin Fowler 的一篇文章,"Mocks Aren't Stubs"。地址为http://martinfowler.com/articles/mocksArentStubs.html(PS:当在google中输入mock stub两个关键字做搜索时,出来结果的第一条就是此文,向Martin Fowler致敬,向google致敬),英文不好的同学,可以参考这里的一份中文翻译:http://www.cnblogs.com/anf/archive/2006/03/27/360248.html。

        总结来说,stub是state-based,关注的是输入和输出。mock是interaction-based,关注的是交互过程。

    5. expectiation/期望

        这个才是mock和stub的最重要的区别:expectiation/期望。

        对于mock来说,exception是重中之重:我们期待方法有没有被调用,期待适当的参数,期待调用的次数,甚至期待多个mock之间的调用顺序。所有的一切期待都是事先准备好,在测试过程中和测试结束后验证是否和预期的一致。

        而对于stub,通常都不会关注exception,就像上面给出的UserDaoStub的例子,没有任何代码来帮助判断这个stub类是否被调用。虽然理论上某些stub实现也可以通过自己编码的方式增加对expectiation的内容,比如增加一个计数器,每次调用+1之类,但是实际上极少这样做。

    6. 总结

        关于mock和stub的不同,在Martin Fowler的"Mocks Aren't Stubs"一文中,有以下结束,我将它列出来作为总结:

                (1) Dummy
                        对象被四处传递,但是从不被真正使用。通常他们只是用来填充参数列表。

                (2) Fake
                        有实际可工作的实现,但是通常有一些缺点导致不适合用于产品(基于内存的数据库就是一个好例子)。

                (3) Stubs

                        在测试过程中产生的调用提供预备好的应答,通常不应答计划之外的任何事。stubs可能记录关于调用的信息,比如 邮件网关的stub 会记录它发送的消息,或者可能仅仅是发送了多少信息。

                (4) Mocks

                        如我们在这里说的那样:预先计划好的对象,带有各种期待,他们组成了一个关于他们期待接受的调用的详细说明。

三. 退化和转化

        在实际的开发测试过程中,我们会发现其实mock和stub的界限有时候很模糊,并没有严格的划分方式,从而造成我们理解上的含糊和困惑。

        主要的原因在于现实使用中,我们经常将mock做不同程度的退化,从而使得mock对象在某些程度上如stub一样工作。以easymock为例,我们可以通过anyObject(), isA(Class)等方式放宽对参数的检测,以atLeastOnce(),anytimes()来放松对调用次数的检测,我们可以使用Easymock.createControl()而不是Easymock.createStrictControl()来放宽对调用顺序的检测(或者调用checkOrder(false)),我们甚至可以通过createNiceControl(), createNiceMock()来创建完全不限制调用方式而且自动返回简单值的mock,这和stub就几乎没有本质区别了。

        目前大多数的mock工具都提供mock退化为stub的支持,比如easyock中,除了上面列出的any***,NiceMock之外,还提供诸如andStubAnswer(),andStubDelegateTo(),andStubReturn(),andStubThrow()和asStub()。

        前面也谈到过stub也是可以通过增加代码来实现一些expectiation的特性,stub理论上也是可以向mock的方向做转化,而从使得两者的界限更加的模糊。
分享到:
评论

相关推荐

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

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

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

    可以使用Codeception\Stub静态调用来构造Codeception\Stub : <?php // create a stub with find method replaced $ userRepository = Stub :: make ( UserRepository ::class, [ 'find' => new User ]); $ ...

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

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

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

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

    Kiwi单元测试

    - **模拟和存根功能**:Kiwi 提供了模拟(mock)和存根(stub)功能,帮助开发者隔离系统中的其他部分,专注于测试单一模块的行为。 - **异步测试支持**:特别支持异步编程模式下的测试。 #### 二、Kiwi中的核心...

    easymock详解教程

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

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

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

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

    HttpMock HttpMock使您可以在测试过程中模拟应用程序所依赖的HTTP服务的行为。 这对于集成和验收测试特别有用。 HttpMock在运行时返回罐装响应。用法。 首先,在要测试的应用程序中,使用HttpMock的URL更改要模拟的...

    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运行...

    cpp-stub函数打桩相关文件

    在这个"cpp-stub函数打桩相关文件"的压缩包中,很可能是包含了一些工具、库或者示例代码,帮助开发者创建和使用C++的函数打桩。本文将详细探讨函数打桩的概念、用途、实现方式以及其在C++中的应用。 1. **函数打桩...

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

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

    有效使用Mock编写java单元测试

    Java单元测试对于开发人员质量保证至关重要,尤其当...Mock说白了就是打桩(Stub)或则模拟,当你调用一个不好在测试中创建的对象时,Mock框架为你模拟一个和真实对象类似的替身来完成相应的行为。使用如下方式在Mav

    置换测试:Mock,Stub和其他

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

    test_stub

    在Python中,可以使用unittest.mock库;在JavaScript中,可以使用sinon.js等工具。这些库提供了丰富的API来创建和配置模拟对象,包括设置返回值、抛出异常、记录调用等行为。 在实际应用中,创建test_stub时需要...

    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 ...

Global site tag (gtag.js) - Google Analytics