Test Double总结

Test Double

第一次了解Test Double是在Martin Fowler的文章Test Double中,Gerard Meszaros提出了这个概念。虽然是06年的文章了,但里面的概念并不过时。这篇文章提到Test Double只是一个通用的词,代表为了达到测试目的并且减少被测试对象的依赖,使用“替身”代替一个真实的依赖对象,从而保证了测试的速度和稳定性。

在项目中,我们时常会遇到由于待测系统依赖组件无法工作而造成的测试阻碍,这是严重影响项目交付的风险之一,而Test Double就是规避这个风险的手段。在测试过程中,我们使用Test Double替代真实的依赖组件去和待测系统进行交互,Test Double不必和真实的依赖组件的实现一模一样,比如不用去实现依赖组件复杂的内部逻辑等,我们只需要在满足测试需求范围内,确保对于待测系统来说Test Double提供的API是和依赖组件提供的一样的,API是怎么实现的在这个上下文就显得不重要了。基于这个特点,Test Double多用于自动化测试比如单元测试和集成测试。

那么Test Double就是万能的吗?显然不是,毕竟我们是使用的是替代品而不是真实产品环境的配置,所以我们需要至少有一个测试去验证使用真实依赖对象的产品。此外,要时刻注意我们是使用Test Double去代替待测系统的依赖对象而不是直接去代替待测系统的部分功能,不然我们就在测试一个“错误”的产品。

Test Double可以进一步细化为:

  • Test Stub
  • Test Spy
  • Mock Object
  • Fake Object
  • Dummy Object

它们各自的定义和区别是什么呢?这篇文章会解答这个问题。

Test Stub - you can define answers to me; I'll respond the same

Test Stub是指一个完全代替待测系统依赖组件的对象,这个对象按照我们设计的输出与待测系统进行交互,可以理解是在待测系统内部打的一个桩。这个桩既不会与测试用例(代码)交互,也不会在待测系统内部进行验证。Test Stub常用于响应待测系统的请求,然后返回特定的值。接下来,这个值会对待测系统产生影响,然后我们就在测试用例里面去验证这个影响。

Test Stub的实现方式一般有两种:

  1. Hard-Coded Test Stub - 会返回固定response的Test Stub
  2. Configurable Test Stub - 会根据测试需求返回相应response的Test Stub,可配置化

当我们遇到下面场景时,Test Stub就可以派上用场

  • 依赖组件无法使用,影响测试结果
  • 依赖组件运行太慢,影响测试速度
  • 成为Responder响应者,当需要给待测系统注入特定数据,从而对待测系统产生影响
  • 成为Saboteur破坏者,当需要给待测系统注入无效数据,从而对待测系统产生异常影响,观察待测系统如何处理错误情况

下面是python-doublex的Stub例子

from doublex import Stub, ANY_ARG, assert_that, is_

class Collaborator:
    def hello(self):
        return "hello"

    def add(self, a, b):
        return a + b

with Stub(Collaborator) as stub:
    stub.hello().raises(SomeException)
    stub.add(ANY_ARG).returns(4)

assert_that(stub.add(2,3), is_(4))

Mock Object - you can set your expectation on me

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

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

下面是python-doublex的Mock例子

from doublex import Mock, assert_that, verify

with Mock() as smtp:
    smtp.helo()
    smtp.mail(ANY_ARG)
    smtp.rcpt("bill@apple.com")
    smtp.data(ANY_ARG).returns(True).times(2)

smtp.helo()
smtp.mail("poormen@home.net")
smtp.rcpt("bill@apple.com")
smtp.data("somebody there?")
smtp.data("I am afraid..")

assert_that(smtp, verify())

Fake Object - you can have me with limited capabilities

Fake Object是指一个轻量级的完全代替待测系统依赖组件的对象,采用更加简单的方法实现依赖组件的功能。Fake Object可以是一个“fake DB”比如简单的内存数据库来代替真实的重量级的数据库,也可以是一个“fake web service”比如创建一个简单的web service来返回指定的response。

Fake ObjectTest Stub很类似,都是依赖组件的代替,区别就在于这个“轻量级”的定义。“轻量级”是指Fake Object仅仅提供和依赖组件一样的功能接口保证待测系统正常工作,让待测系统认为Fake Object就是“真的”依赖组件,实现细节可以非常简单,不需要具有真实依赖组件的很多特性,也不需要像Test Stub那样接受测试的需求,返回特定response给待测系统。

总之,Fake Object的实现比Test StubMock Object简单,所以更加可以快速满足测试需求。不过如果我们需要控制依赖组件对待测系统的输入或输出,我们应该使用Test StubMock Object

Test Spy - monitor real ones; you can change my behavior

Test Spy是指一个待测系统依赖组件的替身,并且会捕捉和保存待测对象对依赖系统的输出,这个输出会用于测试代码中的验证。Test Spy主要用于记录和验证待测对象对依赖系统的输出。

那和Mock Object不同之处是什么呢?Test Spy是把待测对象对依赖系统的输出拿到了测试代码里面进行验证,这样的话,如果待测系统的输出不符合期望,Test Spy并不像Mock Object那样第一时间让测试失败,而是可以在测试代码中加入更多判断信息,让验证和测试结果更加可控和可视化

下面是python-doublex的Spy例子

from hamcrest import contains_string
from doublex import Spy, assert_that, called

class Sender:
    def say(self):
        return "hi"

    def send_mail(self, address, force=True):
        pass  # [some amazing code]

sender = Spy(Sender)

sender.send_mail("john.doe@example.net")  # right, Sender.send_mail interface support this

assert_that(sender.send_mail, called())
assert_that(sender.send_mail, called().with_args("john.doe@example.net"))
assert_that(sender.send_mail, called().with_args(contains_string("@example.net")))

sender.bar()  # interface mismatch exception

jasmine也支持Test Spy

describe("A spy", function() {
  var foo, bar = null;

  beforeEach(function() {
    foo = {
      setBar: function(value) {
        bar = value;
      }
    };

    spyOn(foo, 'setBar');

    foo.setBar(123);
    foo.setBar(456, 'another param');

  });

it("tracks that the spy was called", function() {
    expect(foo.setBar).toHaveBeenCalled();

  });

it("tracks that the spy was called x times", function() {
    expect(foo.setBar).toHaveBeenCalledTimes(2);

  });

it("tracks all the arguments of its calls", function() {
    expect(foo.setBar).toHaveBeenCalledWith(123);
    expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');

  });

it("stops all execution on a function", function() {
    expect(bar).toBeNull();
  });
});

Dummy Object - pass around but never actually used

Dummy Object对象是指为了调用被测试方法而传入的假参数,为什么说是假参数呢?实际上这些传入的Dummy对象并不会对测试有任何作用,仅仅是为了成功调用被测试方法。所以,Dummy Object又被称为Dummy parameter或placeholder。

比如有一个类的实例创建要求传入多个参数,这些参数里面没有可选参数,其中有几个参数不会对测试产生任何作用,这时我们就可以创建Dummy对象作为假参数去创建这个实例。

模块推荐

下面列出了一些可用的Test Double工具

Java
Python
  • doublex - Powerful test doubles framework for Python
  • mock - (Python standard library) A mocking and patching library
  • httpretty - HTTP request mock tool for Python.
JavaScript

总结

testdouble.png

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • 单元测试实践背景 测试环境定位bug时,需要测试同学协助手动发起相关业务URL请求,开发进行远程调试问题:1、远程...
    Zeng_小洲阅读 7,628评论 0 4
  • Mock 方法是单元测试中常见的一种技术,它的主要作用是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与...
    熊熊要更努力阅读 28,333评论 2 25
  • Martin Fowler的一篇文章。  Key point: two differences; SUT  'M...
    Luna_Lu阅读 1,621评论 0 4
  • 此物最珍惜,自古不费财。 并非多难见,一日一念怀。 世人皆有之,却是最难买。 待到散尽后,千金不复来。 (小儿作诗)
    芃悠阅读 91评论 1 1