单元测试:Mock对象

72 篇文章 5 订阅

为什么要引入mock对象

单元测试的目标是一次只验证一个方法,小步的前进,细粒度的测试,但是假如某个方法依赖于其他一些难以操控的东东,比如说网络连接,数据库连接,或者是Servlet容器,那么我们该怎么办呢?

要是你的测试依赖于系统的其他部分,甚至是系统的多个其他部分呢?

  • 在这种情况下,倘若不小心,你最终可能会发现自己几乎初始化了系统的每个组件,而这只是为了给一个测试创造足够的运行环境让它们可以运行起来。忙乎了大半天,看上去我们好像有点违背了测试的初衷了。这样不仅仅消耗时间,还给测试过程引入了大量的耦合因素。

  • 比如说,可能有人兴致冲冲地改变了一个接口或者数据库的一张表,突然,你那卑微的单元测试的神秘的挂掉了。在这种情况发生几次之后,即使是最有耐心的开发者也会泄气,甚至最终放弃所有的测试,那样的话后果就不能想像了。

再让我们看一个更加具体的情况:

  • 在实际的面向对象软件设计中,我们经常会碰到这样的情况,我们在对现实对象进行构建之后,对象之间是通过一系列的接口来实现。这在面向对象设计里是最自然不过的事情了,但是随着软件测试需求的发展,这会产生一些小问题。

  • 举个例子,用户A现在拿到一个用户B提供的接口,他根据这个接口实现了自己的需求,但是用户A编译自己的代码后,想简单模拟测试一下,怎么办呢?这点也是很现实的一个问题。我们是否可以针对这个接口来简单实现一个代理类,来测试模拟,期望代码生成自己的结果呢?

幸运的是,有一种测试模式可以帮助我们:mock对象。Mock对象也就是真实对象在调试期的替代品。

什么是Mock对象

  • Mock Object是指一个完全代替待测系统依赖组件,并且用于验证待测系统输出的对象。这个对象接受待测系统的输出,进行处理并且这个输出进行验证,一旦验证通过也会返回值给待测系统。Mock Object主要用于接收待测系统的输出,然后进行验证。

  • Mock Object一个重要的特点是它可以对无法在待测系统上直接被观察到的行为或输出进行验证。无法观察到的系统行为或输出可以是数据插入数据库,可以是数据写入文件,也可以是对其他组件的调用。以数据库类型Mock Object举例,这个Mock的数据库会去接受待测系统发过来的数据,并且对这个数据进行验证,一旦验证通过就会对数据进行处理(插入或更新操作),然后测试代码会去验证插入是否成功。

什么时候需要mock对象

关于什么时候需要Mock对象,Tim Mackinnon给我们了一些建议:

  • 真实对象具有不可确定的行为(产生不可预测的结果,如股票的行情)
  • 真实对象很难被创建(比如具体的web容器)
  • 真实对象的某些行为很难触发(比如网络错误)
  • 真实情况令程序的运行速度很慢
  • 真实对象有用户界面
  • 测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了)
  • 真实对象实际上并不存在(当需要和其他开发小组,或者新的硬件系统打交道的时候,这是一个普遍的问题)

如何实现Mock对象

使用mock对象进行测试的时候,我们总共需要3个步骤,分别是:

  • 使用一个接口来描述这个对象
  • 为产品代码实现这个接口
  • 以测试为目的,在mock对象中实现这个接口

在此我们又一次看到了针对接口编程的重要性了,因为被测试的代码只会通过接口来引用对象,所以它完全可以不知道它引用的究竟是真实的对象还是mock对象

示例

  1. 使用一个接口来描述这个对象
interface Environmental{
    public long getTime();
}
  1. 为产品代码实现这个接口
public class SystemEnvironment implements Environmental{
    @Override
    public long getTime() {
        return System.currentTimeMillis();
    }
    //other method
}
  1. 以测试为目的,在mock对象中实现这个接口
public class MockSystemEnvirorunent implements Environmental{
    public long getTime() {
        return current_time;
    }
    public void setTime(long aTime) {
        current_time = aTime;
    }
    private long current_time;
}

在mock实现里面添加了一个额外的方法 setTime() (以及对应的私有变量), 这让可以控制 Mock 对象;

  1. 在实际产品中,我们可能有某些地方需要依赖于Environmental的getTime:
import java.util.Calendar;

public class Checker {
    public Checker(Environmental anEnv) {
        env = anEnv;
    }
    public void reminder() {


        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(env. getTime());
        int hour = cal.get(Calendar.HOUR_OF_DAY);
        if (hour >= 17) {   //5:00PM
            env.playWavFile("quit_whistle.wav");
        }
    }
    // ...
    private Environmental env;
}

在产品环境中, 当初始化这个类的对象时,传入的是一个真实的 SystemEnvironment; 而另一方面, 测试代码传入的则是MockSystemEnvironment;使用 env. getTime() 的被测试代码并不知道测试环境和真实环境之间的区别, 因为它们都实现了相同的接口;

现在可以借助 Mock对象, 通过把时间设置为已知值, 并检查行为是否如预期那样来编写测试了.

除了已经展示的getTime () , Environmental 接口还有一个 playWavFile() 函数(Checker用到了);

通过给 mock 对象添加一些额外的支持代码, 从而能够在不倾听计算机喇叭的情况下, 添加测试来观察它是否被调用了。

 public class MockSystemEnvironment implements Environmental{
    public long getTime() {
        return current_time;
    }
    public void setTime(long aTime) {
        current_time = aTime;
    }
    private long current_time;
    //...
    public void playWavFile(String filename){
        playedWav = true;
    }
    public boolean wavwasPlayed(){
        return playedWav;
    }
    public void resetwav() {
        playedWav = false;
    }
    private boolean playedWav = false;
}

整合上方代码:TestChecker.java

public class TestChecker extends TestCase {
  public void testQuittingTime() {
      MockSystemEnvironment env = new MockSystemEnvironment();
      Calendar cal = Calendar.getInstance();
      cal.set(Calendar.YEAR, 2004);
      cal.set(Calendar.MONTH, 10);
      cal.set(Calendar. DAY_OF_MONTH, 1) ;
      cal.set(Calendar.HOUR_OF_DAY, 16);
      cal.set(Calendar.MINUTE, 55);
      long tl = cal.getTimeInMillis();
      env.setTime(tl);
      Checker checker = new Checker(env);
      // Run the checker
      checker.reminder();
      //Nothing should have been played yet
      assertFalse(env.wavwasPlayed());
      // Advance the time by 5 minutes
      tl += (5 * 60 * 1000);
      env.setTime (tl);
      //Now run the checker
      checker.reminder();
      //Should have played now
      assertTrue(env.wavwasPlayed());
      //Reset the flag so we can try again
      env.resetwav();
      //Advance the time by 2 hours and check
      tl += 2 * 60 * 60 * 1000;
      env.setTime(tl);
      checker.reminder();
      assertTrue(env.wavwasPlayed());
  }
}
  • 代码创建了一个应用环境的 mock 版本;并设置了我们将使用的假的时间, 然后将它们设置给了mock环境对象;
  • 调用 reminder(), 这将(不知情地)使用mock环境。调用断言检查wav文件是不是还没播放, 因为在mock对象的环境中,
    此时还不是quitting time;
  • 但是我们将很快调整时间;把mock时间调整到刚好是 quitting time;然后再一次调用reminder()函数。这次,声音已经被播放过了,因而调用断言确认了.wav文件这次已经播放过了;
  • 最后重设 Mock 环境的 .wav 文件的标志, 并且测试再过两个小时之后的情况。
  • 因为已经有了一个提供所有系统功能的现成接口,所以会使用接口而不是直接调用诸如 System.currentTimeMillis() 这样的方法, Mock 对象通过接口拥有了控制一切行为的能力。
    单元测试中使用Mock对象
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Vue中进行单元测试时,可以使用Jest作为测试框架。Jest是一个功能强大且易于使用的JavaScript测试工具,它提供了一套丰富的API和内置的断言库,可以帮助我们编写和运行单元测试。 要在Vue中进行单元测试,可以使用Jest提供的mock功能来模拟window对象。通过mocking window对象,我们可以模拟浏览器环境中的一些行为和属性,以便更好地测试Vue组件。 下面是一个简单的示例,演示如何使用Jest来mock window对象: ```javascript // MyComponent.vue <template> <div> <button @click="handleClick">Click me</button> </div> </template> <script> export default { methods: { handleClick() { if (window.innerWidth > 768) { // do something } else { // do something else } } } } </script> ``` ```javascript // MyComponent.spec.js import { shallowMount } from '@vue/test-utils'; import MyComponent from '@/components/MyComponent.vue'; describe('MyComponent', () => { it('should do something when window width is greater than 768', () => { // Mock window.innerWidth window.innerWidth = 800; const wrapper = shallowMount(MyComponent); wrapper.vm.handleClick(); // Assert something // ... }); it('should do something else when window width is less than or equal to 768', () => { // Mock window.innerWidth window.innerWidth = 600; const wrapper = shallowMount(MyComponent); wrapper.vm.handleClick(); // Assert something // ... }); }); ``` 在上面的示例中,我们使用Jest的mock功能来模拟window.innerWidth属性。通过设置不同的值,我们可以测试不同的分支逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值