如何窥探JavaScript中的递归函数 [英] How to spy on a recursive function in JavaScript

查看:83
本文介绍了如何窥探JavaScript中的递归函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:我看到过这个问题的变体以不同的方式提出并参考了不同的测试工具。我认为明确描述问题和解决方案会很有用。我的测试是使用 Sinon间谍编写的,以便于阅读,并将使用 Jest Jasmine (和只需要使用Mocha和Chai进行微小的更改,但是使用任何测试框架和任何间谍实现都可以看到所描述的行为。

Note: I've seen variations of this question asked in different ways and in reference to different testing tools. I thought it would useful to have the issue and solution clearly described. My tests are written using Sinon spies for readability and will run using Jest or Jasmine (and need only minor changes to run using Mocha and Chai), but the behavior described can be seen using any testing framework and with any spy implementation.

ISSUE

我可以创建测试来验证递归函数返回正确的值,但我无法监视递归调用。

I can create tests that verify that a recursive function returns the correct value, but I can't spy on the recursive calls.

示例

鉴于此递归函数:

const fibonacci = (n) => {
  if (n < 0) throw new Error('must be 0 or greater');
  if (n === 0) return 0;
  if (n === 1) return 1;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

...我可以通过执行以下操作测试它是否返回正确的值:

...I can test that it returns the correct values by doing this:

describe('fibonacci', () => {
  it('should calculate Fibonacci numbers', () => {
    expect(fibonacci(5)).toBe(5);
    expect(fibonacci(10)).toBe(55);
    expect(fibonacci(15)).toBe(610);
  });
});

...但如果我在函数中添加一个间谍,它会报告该函数只被调用一次:

...but if I add a spy to the function it reports that the function is only called once:

describe('fibonacci', () => {
  it('should calculate Fibonacci numbers', () => {
    expect(fibonacci(5)).toBe(5);
    expect(fibonacci(10)).toBe(55);
    expect(fibonacci(15)).toBe(610);
  });
  it('should call itself recursively', () => {
    const spy = sinon.spy(fibonacci);
    spy(10);
    expect(spy.callCount).toBe(177); // FAILS: call count is 1
  });
});


推荐答案

问题

间谍工作是通过围绕跟踪调用和返回值的原始函数创建包装函数。间谍只能记录通过它的调用。

Spies work by creating a wrapper function around the original function that tracks the calls and returned values. A spy can only record the calls that pass through it.

如果递归函数直接调用自身,则无法将该调用包装在间谍中。

If a recursive function calls itself directly then there is no way to wrap that call in a spy.

解决方案

递归函数必须以与调用它相同的方式调用自身在外面。然后,当函数包含在间谍中时,递归调用将包含在同一个间谍中。

The recursive function must call itself in the same way that it is called from outside itself. Then, when the function is wrapped in a spy, the recursive calls are wrapped in the same spy.

示例1:类方法

递归类方法使用这个来调用它们自己的类实例。当实例方法被间谍替换时,递归调用会自动调用相同的间谍:

Recursive class methods call themselves using this which refers to their class instance. When the instance method is replaced by a spy, the recursive calls automatically call the same spy:

class MyClass {
  fibonacci(n) {
    if (n < 0) throw new Error('must be 0 or greater');
    if (n === 0) return 0;
    if (n === 1) return 1;
    return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  }
}

describe('fibonacci', () => {

  const instance = new MyClass();

  it('should calculate Fibonacci numbers', () => {
    expect(instance.fibonacci(5)).toBe(5);
    expect(instance.fibonacci(10)).toBe(55);
  });
  it('can be spied on', () => {
    const spy = sinon.spy(instance, 'fibonacci');
    instance.fibonacci(10);
    expect(spy.callCount).toBe(177); // PASSES
    spy.restore();
  });
});

注意:类方法使用这个所以为了使用 spy(10); 而不是 instance.fibonacci(10)来调用间谍功能; 该函数需要转换为箭头函数或显式绑定到实例 this.fibonacci = this.fibonacci.bind(this); 在类构造函数中。

Note: the class method uses this so in order to invoke the spied function using spy(10); instead of instance.fibonacci(10); the function would either need to be converted to an arrow function or explicitly bound to the instance with this.fibonacci = this.fibonacci.bind(this); in the class constructor.

示例2:模块

如果模块中的递归函数使用模块调用自身,则它会变得间谍。当模块功能被间谍替换时,递归调用会自动调用相同的间谍:

A recursive function within a module becomes spy-able if it calls itself using the module. When the module function is replaced by a spy, the recursive calls automatically call the same spy:

ES6

// ---- lib.js ----
import * as lib from './lib';

export const fibonacci = (n) => {
  if (n < 0) throw new Error('must be 0 or greater');
  if (n === 0) return 0;
  if (n === 1) return 1;
  // call fibonacci using lib
  return lib.fibonacci(n - 1) + lib.fibonacci(n - 2);
};


// ---- lib.test.js ----
import * as sinon from 'sinon';
import * as lib from './lib';

describe('fibonacci', () => {
  it('should calculate Fibonacci numbers', () => {
    expect(lib.fibonacci(5)).toBe(5);
    expect(lib.fibonacci(10)).toBe(55);
  });
  it('should call itself recursively', () => {
    const spy = sinon.spy(lib, 'fibonacci');
    spy(10);
    expect(spy.callCount).toBe(177); // PASSES
    spy.restore();
  });
});

Common.js

// ---- lib.js ----
exports.fibonacci = (n) => {
  if (n < 0) throw new Error('must be 0 or greater');
  if (n === 0) return 0;
  if (n === 1) return 1;
  // call fibonacci using exports
  return exports.fibonacci(n - 1) + exports.fibonacci(n - 2);
}


// ---- lib.test.js ----
const sinon = require('sinon');
const lib = require('./lib');

describe('fibonacci', () => {
  it('should calculate Fibonacci numbers', () => {
    expect(lib.fibonacci(5)).toBe(5);
    expect(lib.fibonacci(10)).toBe(55);
  });
  it('should call itself recursively', () => {
    const spy = sinon.spy(lib, 'fibonacci');
    spy(10);
    expect(spy.callCount).toBe(177); // PASSES
    spy.restore();
  });
});

示例3:对象包装

如果放在一个包装对象中并使用该对象调用自身,则不属于模块的独立递归函数可能会变得间谍。当对象中的函数被间谍替换时,递归调用会自动调用相同的间谍:

A stand-alone recursive function that is not part of a module can become spy-able if it is placed in a wrapping object and calls itself using the object. When the function within the object is replaced by a spy the recursive calls automatically call the same spy:

const wrapper = {
  fibonacci: (n) => {
    if (n < 0) throw new Error('must be 0 or greater');
    if (n === 0) return 0;
    if (n === 1) return 1;
    // call fibonacci using the wrapper
    return wrapper.fibonacci(n - 1) + wrapper.fibonacci(n - 2);
  }
};

describe('fibonacci', () => {
  it('should calculate Fibonacci numbers', () => {
    expect(wrapper.fibonacci(5)).toBe(5);
    expect(wrapper.fibonacci(10)).toBe(55);
    expect(wrapper.fibonacci(15)).toBe(610);
  });
  it('should call itself recursively', () => {
    const spy = sinon.spy(wrapper, 'fibonacci');
    spy(10);
    expect(spy.callCount).toBe(177); // PASSES
    spy.restore();
  });
});

这篇关于如何窥探JavaScript中的递归函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆