1

我正在使用 Sinon 假定时器来测试一些包含定时器的功能。问题是被测试的函数在调用定时器之前使用了几个异步调用,我发现很难同步测试用例,以便clock.tickAsync()保证在被测试的函数调用之后setTimeout()被调用。我见过其他类似问题的问题,但它们更多的是关于从测试中设置的计时器以及如何将它们与也从测试中调用的滴答声同步。但是如果我们认为我们的函数 other test 是一个黑盒子,并且我们所知道的是在执行一些异步调用之后,它会调用setTimeout,我们必须提前时钟以防止计时器挂起,这将是一个好方法?

示例:这里,foo是我们要测试的功能。我们所知道的是它执行一些异步任务,无论是 I/O 还是其他什么,然后等待 10 秒(我的真实代码实际上有一个循环,其中每秒检查一个条件,超时 10 秒)。测试调用foo然后将时钟提前 20 秒以触发计时器,但大多数情况下它不会工作,因为调用tickAsync()will be before foo已经调用setTimeout()

function foo() {
  await something();
  await something();
  await something();
  await something();
  await something();
  return new Promise(resolve => setTimeout(resolve, 10000));
}

let clock;
describe('Test with unpredictable timer', () => {
  beforeEach(() => {
    clock = FakeTimers.install();
  });
  afterEach(() => {
    clock.uninstall();
  });

  it('should advance timer', () => {
    const p = foo();
    // advance timer
    await clock.tickAsync(20000);
    return p;
  });
});

所以,这个测试通常会失败,因为计时器永远不会被触发。

我意识到最可靠的方法可能是如果我的测试可以在setTimeout被调用时得到通知,所以我考虑添加一个事件发射器来发送“等待”事件,但这确实使我的生产代码混乱。我还考虑过给猴子打补丁 setTimeout(在 Sinon 替换它之后)也发出一个事件。但这也很混乱。最后,我采取了一个tickAsync()调用循环,确保每个调用await clock.tickAsync()都给事件循环一个新的运行,这有效,但本质上当然是不可靠的,因为它仍然依赖于任意选择的循环数量等:

function tickSteps(clock, duration, steps) {
  for (let i = 0; i < steps; i++) {
    await clock.tickAsync(duration / steps);
  }
}

例如,这适用于tickSteps(clock, 20000, 100)但可能不适用于 的steps50,并且显然不是一个稳健的解决方案。

我还考虑在我的测试代码中添加一个“真实”计时器,让它在打假时钟之前实际等待 50“真实”毫秒,但这也被证明是混乱的。

除了重新设计我的代码以将其分解成更小的部分或添加回调/事件之外,Sinon 本身有什么办法可以帮助解决这个问题吗?本质上,我需要告诉Sinon 计时,但将其推迟到下一个设置的计时器(但我想如果运行时使用计时器等也可能有问题)。或者,如果有办法“勾选假时钟,但只有在真正的时钟延迟之后”。

是否有针对此问题的首选解决方案,或者我应该放弃并重新设计我测试过的代码?

4

0 回答 0