sinon.stub_JavaScript测试工具对决:Sinon.js vs testdouble.js

sinon.stub

两名戴着盾牌的希腊勇士与虫子作战

在对真实代码进行单元测试时,有许多情况使测试难以编写。 您如何检查是否调用了函数? 您如何测试Ajax呼叫? 还是使用setTimeout编码? 就是在这种情况下,您使用测试倍数 —替换代码使难以测试的东西易于测试。

多年来, Sinn.js一直是JavaScript测试中的事实标准,用于创建测试双打。 它是任何JavaScript开发人员编写测试的必备工具,因为没有它,为实际应用程序编写测试几乎是不可能的。

最近,一个恰当地命名为testdouble.js的新库开始风靡一时 。 它具有与Sinon.js类似的功能集,但在某些地方有所不同。

在本文中,我们将研究Sinon.js和testdouble.js都提供了什么,并比较它们各自的优缺点。 Sinon.js将继续是最佳选择,还是挑战者获得大奖?

注意:如果您不熟悉双打考试,建议先阅读Sinon.js教程 。 它将帮助您更好地理解我们将在此处讨论的概念。

本文中使用的术语

为了确保容易理解正在讨论的内容,这里是所用术语的快速概述。 这些是Sinon.js的定义,在其他地方可能稍有不同。

  • 双重测试是测试期间使用的功能的替代。 它可以引用以下三种类型中的任何一种。
  • 间谍是一种测试双重手段,可以在不影响目标功能行为的情况下检查效果。
  • 存根是一个测试double,它用其他方式(例如返回值)替换目标函数的行为。
  • 模拟是存根的另一种处理方法。 模拟包含内置的验证,可以代替单独的断言来使用。

应当指出,testdouble.js的目标之一是减少这种类型的术语之间的混淆。

Sinon.js和testdouble.js概览

让我们先来看一下Sinon.js和testdouble.js在基本用法上的比较。

Sinon的双重测试有三个独立的概念:间谍,存根和模拟。 这个想法是,每个代表不同的使用场景。 这使该库对于来自其他语言的读者或使用相同术语(例如xUnit测试模式)阅读过书籍的读者更加熟悉。 但是另一方面,这三个概念也会使Sinon首次使用时更加难以理解

这是使用Sinon的基本示例:

//Here's how we can see a function call's parameters:
var spy = sinon.spy(Math, 'abs');

Math.abs(-10);

console.log(spy.firstCall.args); //output: [ -10 ]
spy.restore();

//Here's how we can control what a function does:
var stub = sinon.stub(document, 'createElement');
stub.returns('not an html element');

var x = document.createElement('div');

console.log(x); //output: 'not an html element'
stub.restore();

相反,testdouble.js选择了一个更简单的API。 它没有使用诸如间谍程序或存根之类的概念,而是使用JavaScript开发人员更加熟悉的语言,例如td.functiontd.objecttd.replace 。 这使得testdouble可能更容易拿起,并且更适合某些任务。 但另一方面,可能根本无法进行更高级的使用(有时是故意的)。

这是testdouble.js的使用方式:

//Here's how we can see a function call's parameters:
var abs = td.replace(Math, 'abs');

Math.abs(-10);

var explanation = td.explain(abs);
console.log(explanation.calls[0].args); //output: [ -10 ]

//Here's how we can control what a function does:
var createElement = td.replace(document, 'createElement');
td.when(createElement(td.matchers.anything())).thenReturn('not an html element');

var x = document.createElement('div');
console.log(x); //output: 'not an html element'

//testdouble resets all testdoubles with one call, no need for separate cleanup
td.reset();

testdouble使用的语言更直接。 我们“替换”功能而不是“添加”功能。 我们要求testdouble“解释”一个函数以从中获取信息。 除此之外,到目前为止,它与Sinon非常相似。

这也扩展到创建“匿名”测试双打:

var x = sinon.stub();

var x = td.function();

Sinon的间谍和存根具有可以提供有关它们的更多信息的属性。 例如, stub.callCount提供了stub.callCountstub.args 。 在testdouble的情况下,我们从td.explain获取此信息:

//we can give a name to our test doubles as well
var x = td.function('hello');

x('foo', 'bar');

td.explain(x);
console.log(x);
/* Output:
{
  name: 'hello',
  callCount: 1,
  calls: [ { args: ['foo', 'bar'], context: undefined } ],
  description: 'This test double `hello` has 0 stubbings and 1 invocations.\n\nInvocations:\n  - called with `("foo", "bar")`.',
  isTestDouble: true
}
*/

最大的区别之一与您如何设置存根和验证有关。 使用Sinon,您可以在存根后面链接命令,并使用断言来验证结果。 testdouble.js只是向您展示了如何调用该函数,或者如何“演练”该函数调用。

var x = sinon.stub();
x.withArgs('hello', 'world').returns(true);

var y = sinon.stub();
sinon.assert.calledWith(y, 'foo', 'bar');

var x = td.function();
td.when(x('hello', 'world')).thenReturn(true);

var y = td.function();
td.verify(y('foo', 'bar'));

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

这使testdouble的API更易于理解,因为您不需要知道可以链接什么操作以及何时链接。

更详细地比较常见的测试任务

在较高的层次上,两个库都相当相似。 但是,在实际项目中可能需要执行的常见测试任务呢? 让我们看一些差异开始显现的情况。

testdouble.js没有间谍

首先要注意的是testdouble.js没有“间谍”的概念。 尽管Sinon.js允许我们替换函数调用,以便我们从中获取信息,同时保持函数的默认行为,但用testdouble.js根本不可能。 当您用testdouble替换功能时,它总是会失去其默认行为。

但是,这不一定是问题。 间谍的最常见用法是使用它们来验证是否调用了回调,这可以通过td.function轻松实现:

var spy = sinon.spy();

myAsyncFunction(spy);

sinon.assert.calledOnce(spy);

var spy = td.function();

myAsyncFunction(spy);

td.verify(spy());

尽管这不是一个大问题,但还是很高兴知道两者之间存在这种差异,否则,如果您希望能够通过testdouble.js以某种更特定的方式使用间谍,那么您可能会感到惊讶。

testdouble.js需要更精确的输入

您将遇到的第二个区别是testdouble在输入方面更加严格。

Sinon的存根和断言都使您对给出的参数不精确。 一个例子最容易说明:

var stub = sinon.stub();
stub.withArgs('hello').returns('foo');

console.log(stub('hello', 'world')); //output: 'foo'

sinon.assert.calledWith(stub, 'hello'); //no error

var stub = td.function();
td.when(stub('hello')).thenReturn('foo');

console.log(stub('hello', 'world')); //output: undefined

td.verify(stub('hello')); //throws error!

默认情况下,Sinn不在乎为一个函数分配多少额外的参数。 虽然它提供了诸如sinon.assert.calledWithExactly功能,但在文档中不建议将其作为默认功能。 像stub.withArgs类的stub.withArgs也没有“完全”变体。

另一方面,testdouble.js默认需要指定确切的参数。 这是设计使然。 想法是,如果给函数提供了测试中未指定的其他一些参数,则可能是错误,并且应该使测试失败。

可以允许在testdouble.js中指定任意参数,但这不是默认值:

//tell td to ignore extra arguments entirely
td.when(stub('hello'), { ignoreExtraArgs: true }).thenReturn('foo');

使用ignoreExtraArgs: true ,行为类似于Sinon.js

testdouble.js具有内置的Promise支持

虽然在Sinon.js中使用诺言并不复杂,但testdouble.js具有内置的用于返回和拒绝诺言的方法。

var stub = sinon.stub();
stub.returns(Promise.resolve('foo'));
//or
stub.returns(Promise.reject('foo'));

var stub = td.function();
td.when(stub()).thenResolve('foo');
//or
td.when(stub()).thenReject('foo');

注意 :可以使用sinon-as-promised在Sinon 1.x中包含类似的便利功能。 Sinon 2.0和更高版本以stub.resolvesstub.rejects的形式包含promise支持

testdouble.js的回调支持更强大

Sinon和testdouble都提供了一种简单的方法来使存根函数调用回调。 但是,它们的工作方式有所不同。

Sinon使用stub.yields来使存根调用它作为参数接收的第一个函数

var x = sinon.stub();
x.yields('a', 'b');

//callback1 is called with 'a' and 'b'
x(callback1, callback2);

testdouble.js默认为节点样式的模式,其中回调被假定为最后一个参数。 在演练调用时,您也不必指定它:

var x = td.function();
td.when(x(td.matchers.anything())).thenCallback('a', 'b');

//callback2 is called with 'a' and 'b'
x(callback1, callback2);

使testdouble的回调支持更强大的原因是,您可以轻松定义具有多个回调或回调顺序不同的方案的行为。

假设我们要改为调用callback1

var x = td.function();
td.when(x(td.callback, td.matchers.anything())).thenCallback('a', 'b');

//callback1 is called with 'a' and 'b'
x(callback1, callback2);

请注意我们通过td.callback作为第一个参数在函数td.when 。 这告诉testdouble哪个参数是我们希望使用的回调。

使用Sinon,也可以更改行为:

var x = sinon.stub();
x.callsArgWith(1, 'a', 'b');

//callback1 is called with 'a' and 'b'
x(callback1, callback2);

在这种情况下,我们使用callsArgWith代替yields 。 我们必须提供该调用的特定索引才能使其正常工作,尤其是在具有许多参数的函数上,该索引可能会有些麻烦。

如果我们想用某些值调用两个回调怎么办?

var x = td.function();
td.when(x(td.callback('a', 'b'), td.callback('foo', 'bar'))).thenReturn();

//callback1 is called with 'a' and 'b'
//callback2 is called with 'foo' and 'bar'
x(callback1, callback2);

对于诗乃,这是根本不可能的。 您可以将多个调用链接到callsArgWith ,但它只会调用其中一个。

testdouble.js具有内置模块替换

除了可以使用td.replace替换功能td.replace ,testdouble还可以替换整个模块。

这主要在以下情况下有用:模块具有直接导出需要替换的功能的模块:

module.exports = function() {
  //do something
};

如果我们想用testdouble代替它,我们可以使用td.replace('path/to/file')例如。

var td = require('testdouble');

//assuming the above function is in ../src/myFunc.js
var myFunc = td.replace('../src/myFunc');

myFunc();

td.verify(myFunc());

虽然Sinon.js可以替换属于某个对象成员的函数,但它无法以与此类似的方式替换模块。 要使用兴农的时候做到这一点,你需要使用另一个模块,如proxyquire联控

var sinon = require('sinon');
var proxyquire = require('proxyquire');
var myFunc = proxyquire('../src/myFunc', sinon.stub());

关于模块更换的另一件值得注意的事情是testdouble.js会自动替换整个模块。 如果它是函数输出(如此处的示例中所示),它将替换该函数。 如果它是一个包含多个功能的对象,它将替换所有功能。 还支持构造函数和ES6类。 proxyquire和rewire都要求您分别指定要替换的内容和方式。

testdouble.js缺少Sinon的一些帮助者

如果您使用的是Sinon的伪计时器伪XMLHttpRequest或伪服务器 ,您会发现testdouble中缺少它们。

伪计时器可以作为插件使用 ,但是XMLHttpRequests和Ajax功能需要以不同的方式处理。

一种简单的解决方案是替换您正在使用的Ajax函数,例如$.post

//replace $.post so when it gets called with 'some/url',
//it will call its callback with variable `someData`
td.replace($, 'post');
td.when($.post('some/url')).thenCallback(someData);

使用testdouble.js更容易进行测试后清理

对于Sinon.js初学者来说,一个常见的绊脚石往往是清理间谍和存根。 Sinon提供了三种不同的实现方法,这一事实无济于事。

it('should test something...', function() {
  var stub = sinon.stub(console, 'log');
  stub.restore();
});

要么:

describe('something', function() {
  var sandbox;
  beforeEach(function() {
    sandbox = sinon.sandbox.create();
  });

  afterEach(function() {
    sandbox.restore();
  });

  it('should test something...', function() {
    var stub = sandbox.stub(console, 'log');
  });
});

要么:

it('should test something...', sinon.test(function() {
  this.stub(console, 'log');

  //with sinon.test, the stub cleans up automatically
}));

通常,在实践中通常建议使用sandbox和sinon.test方法,否则很容易意外地将存根或间谍留在原地,这可能在其他测试中引起问题。 这可能导致难以跟踪级联故障。

testdouble.js仅提供一种清理测试td.reset()方法: td.reset() 。 推荐的方法是在afterEach挂钩中调用它:

describe('something', function() {
  afterEach(function() {
    td.reset();
  });

  it('should test something...', function() {
    td.replace(console, 'log');

    //the replaced log function gets cleaned up in afterEach
  });
});

这大大简化了两次测试的设置以及测试后的清理工作,从而减少了难以跟踪错误的可能性。

利弊

现在,我们已经研究了两个库中的功能。 它们都提供相似的功能集,但彼此之间的设计理念略有不同。 我们可以将其分为利弊吗?

让我们首先谈谈Sinon.js。 它提供了比testdouble.js更高的一些附加功能,并且某些方面更具可配置性。 这使它在更特殊的测试方案中具有更大的灵活性。 Sinon.js还使用其他语言所熟悉的语言-间谍,存根和模拟之类的概念存在于不同的库中,并且在测试相关书籍时也会进行讨论。

缺点是增加了复杂性。 尽管它的灵活性使专家可以做更多的事情,但这也意味着某些任务比testdouble.js中的更为复杂。 对于测试双打概念的新手来说,它也可以拥有更陡峭的学习曲线。 实际上,即使是像我这样非常熟悉的人,也可能难以阐明sinon.stubsinon.mock之间的sinon.stub sinon.mock

testdouble.js而是选择了一个更简单的界面。 大多数代码使用起来相当简单,并且对于JavaScript感觉更直观,而Sinon.js有时会觉得它是在考虑其他某种语言的情况下设计的。 由于这一点及其某些设计原则,对于初学者来说更容易上手,甚至有经验的测试人员也会发现许多任务更容易完成。 例如,testdouble使用相同的API来设置测试倍数和验证结果。 由于其更简单的清理机制,它还可以减少错误。

testdouble的最大问题是由其一些设计原则引起的。 例如,完全缺乏间谍可能会使某些喜欢使用它们而不是存根的人无法使用。 这是一个非常有见地的问题,您可能根本没有发现任何问题。 除此之外,尽管testdouble.js的条目较新,但它仍为Sinon.js带来了严重的竞争。

逐特征比较

以下是按功能进行的功能比较:

特征 Sinon.js testdouble.js
间谍 没有
存根
存根结果延迟 没有
cks 是的1
承诺支持 是(在2.0及更高版本中)
时间帮手 是(通过插件)
Ajax助手 否(代替功能)
模块更换 没有
内置断言
匹配器
自定义匹配器
争吵者 2号
代理测试加倍 没有
  1. 技术上讲 ,testdouble.js不像Sinon.js那样具有模拟。 但是,由于Sinon中的模拟本质上是包含存根和验证的对象,因此可以使用td.replace(someObject)获得类似的效果。
  2. 通过使用stub.yield (不要与stub.yields混淆),可以达到与参数捕获者类似的效果。

总结与结论

Sinon.js和testdouble.js都提供了相当相似的功能集。 从这个意义上讲,它们都不明显是优越的。

两者之间最大的区别在于它们的API。 Sinon.js可能稍微冗长一些,同时提供了许多处理方法的选项。 这可能是它的祝福和诅咒。 testdouble.js具有更简化的API,可以使它更易于学习和使用,但是由于其设计更加固执己见,因此有些人可能会觉得有问题。

那么哪一个适合我呢?

您是否同意testdouble的设计原则? 如果是,则没有理由不使用它。 我在许多项目中都使用了Sinon.js,可以肯定地说,testdouble.js至少完成了我对Sinon.js所做的所有事情的95%,其余的5%可以通过一些简单的解决方法来实现。

如果您发现Sinon.js难以使用,或者正在寻找一种更“ JavaScripty”的方式来进行测试加倍,那么testdouble.js也可能适合您。 即使是花很多时间学习使用Sinon的人,我也倾向于建议尝试testdouble.js并查看您是否喜欢它。

然而,testdouble.js的某些方面可能会使那些了解Sinon.js或以其他方式成为资深测试人员的人感到头痛。 例如,完全缺乏间谍可能会破坏交易。 对于专家和想要最大程度灵活性的人们,Sinn.js仍然是一个不错的选择。

如果您想了解更多有关如何在实践中使用测试双打的信息,请在“真实世界”指南中查看我免费的Sinon.js 。 尽管它使用Sinon.js,但您也可以对testdouble.js应用相同的技术和最佳实践。

有什么问题吗 注释? 您已经在使用testdouble.js吗? 阅读本文后,您是否考虑尝试一下? 在下面的评论中让我知道。

James WrightJoan YinChristian JohansenJustin Searls对本文进行了同行评审。 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态!

翻译自: https://www.sitepoint.com/javascript-testing-tool-showdown-sinon-js-vs-testdouble-js/

sinon.stub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值