【问题标题】:Testing a function entirely using stubs完全使用存根测试函数
【发布时间】:2019-02-07 09:07:15
【问题描述】:

过去几周我一直在编写测试。在我的工作地点,我们使用 Mocha 作为我们的测试运行程序,使用 Chai 作为断言库。我也在使用 Sinon 来创建存根,有些东西一直在困扰着我。我已经为几个函数编写了测试,其中我对函数中的每个依赖项进行了存根,最糟糕的是,我什至没有考虑我正在测试的函数正在接受的参数。举个例子吧

module.exports = {
  "someFunc": (arg1, arg2) => {
    return new Promise((resolve, reject) => {
      Promise.all(arg1).then(data => {
        let someArray = ourHelperLib.toArray(data);
        let someObj = ourHelperLib.toObject(arg2);
          if(someArray.length == 0){
            reject("error");
          }else{
            resolve({
              "array": someArray,
              "object": someObj
            });
          }
        }).catch(err => {
            reject(err);
        });
    });
  },
}
  1. 现在,当我为这个函数编写测试时,我遇到了一个我存根 Promise.all() 以引发错误的情况。
  2. 对于我的第二个测试,我存根 Promise.all() 以返回误报值,存根 ourHelperLib.toArray() 抛出错误并检查函数是否处理它。
  3. 对于我的第三个测试,我存根 Promise.all()ourHelperLib.toArray()ourHelperLib.toObject() 以返回误报,然后检查输出中是否存在已解决的承诺,其值是操作的结果。

从函数定义中可以清楚地看出,传递给函数的两个参数都直接传递给我正在存根的依赖项,因此我可以完全忽略这些值,这就是我的意思

const stubOurHelperLibToThrowError = argFromCaller => {
    throw new Error("This is an error");
}

由于我没有处理传递给我的存根函数的参数,所以我根本没有根据传递给它的数据来测试该函数。我只是在测试函数someFunc()的逻辑结构。

这是一个好习惯吗?我还没有找到很多可靠的答案,而且由于我负责在我目前工作的地方介绍编写单元测试的指南,所以我认为这是至关重要的。

和平!

【问题讨论】:

    标签: javascript unit-testing testing sinon stub


    【解决方案1】:

    您可以将 Promise 传递给您的函数,而不必为您所描述的许多内容存根。


    我有一个案例,我存根 Promise.all() 抛出错误

    不要存根Promise.all,只需将带有被拒绝的Promise 的数组传递给您的函数:

    someFunc([Promise.reject(new Error('fail'))], null)
    

    ...这将导致Promise.all 掉入catch 并拒绝并出现错误。


    我存根 Promise.all() 返回一个误报值,存根 ourHelperLib.toArray() 抛出错误并检查函数是否处理它

    同样,不要存根Promise.all,只需传递一个带有已解析Promise的数组:

    someFunc([Promise.resolve('a value')], null)
    

    您可以存根 ourHelperLib.toArray 以引发错误,或者让您的 Promise 数组解析为您知道会导致 ourHelperLib.toArray 抛出的内容。


    对于我的第三个测试,我存根 Promise.all()、ourHelperLib.toArray() 和 ourHelperLib.toObject() 以返回误报,然后检查输出中是否存在已解决的 promise,其值是操作的结果。

    存根ourHelperLib.toArrayourHelperLib.toObject 是可选的。除非它们在计算上很昂贵(例如,如果它们进行网络调用),那么像平常一样调用它们通常是有意义的。

    您可以在已解析的Promises 数组中传递您想要给ourHelperLib.toArray 的数据,然后只需将您想要发送的值作为第二个参数传递给ourHelperLib.toObject

    someFunc([
      Promise.resolve('value 1 for ourHelperLib.toArray'),
      Promise.resolve('value 2 for ourHelperLib.toArray')
    ], 'value for ourHelperLib.toObject')
    

    ...并检查生成的 Promise 是否解析为预期值。


    一般来说,最好的做法是坚持黑盒测试。

    这个函数似乎没有任何副作用,只是返回一个Promise,它根据传递的参数解析为一个结果。

    除非函数具有计算量大的依赖项,否则最好尽可能通过简单地传递参数并验证结果来测试这样的函数。

    【讨论】:

    • 感谢您的见解。我肯定会使用传递拒绝和解决 promises 作为参数的方法。还要感谢您确认 black-box 方法没有任何问题。实际上,我们有很多 API 处理程序,它们有很多第三方依赖项,这些依赖项执行网络调用和计算成本很高的读写和 i/o。
    • @VimalSheoran 不客气,很高兴听到它有帮助!
    猜你喜欢
    • 1970-01-01
    • 2019-02-21
    • 1970-01-01
    • 2021-05-21
    • 1970-01-01
    • 2012-01-22
    • 1970-01-01
    • 2019-03-20
    • 1970-01-01
    相关资源
    最近更新 更多