草庐IT

javascript - 为 promise 速率限制功能创建有效测试用例的问题

coder 2025-01-15 原文

我正在尝试为下面的 promiseRateLimit 函数创建一个有效的测试用例。 promiseRateLimit 函数的工作方式是它使用 queue 来存储传入的 promise ,并在它们之间放置一个 delay

import Promise from 'bluebird'

export default function promiseRateLimit (fn, delay, count) {
  let working = 0
  let queue = []
  function work () {
    if ((queue.length === 0) || (working === count)) return
    working++
    Promise.delay(delay).tap(() => working--).then(work)
    let {self, args, resolve} = queue.shift()
    resolve(fn.apply(self, args))
  }
  return function debounced (...args) {
    return new Promise(resolve => {
      queue.push({self: this, args, resolve})
      if (working < count) work()
    })
  }
}

下面是函数的一个例子。

async function main () {
  const example = (v) => Promise.delay(50)
  const exampleLimited = promiseRateLimit(example, 100, 1)
  const alpha = await exampleLimited('alpha')
  const beta = await exampleLimited('beta')
  const gamma = await exampleLimited('gamma')
  const epsilon = await exampleLimited('epsilon')
  const phi = await exampleLimited('phi')
}

示例 promise 需要50ms 来运行,promiseRateLimit 函数将只允许1 promise 每个100 毫秒。所以 promise 之间的间隔应该大于 100ms

这是一个有时返回成功有时失败的完整测试:

import test from 'ava'
import Debug from 'debug'
import Promise from 'bluebird'
import promiseRateLimit from './index'
import {getIntervalsBetweenDates} from '../utilitiesForDates'
import {arraySum} from '../utilitiesForArrays'
import {filter} from 'lodash'

test('using async await', async (t) => {
  let timeLog = []
  let runCount = 0
  const example = (v) => Promise.delay(50)
    .then(() => timeLog.push(new Date))
    .then(() => runCount++)
    .then(() => v)
  const exampleLimited = promiseRateLimit(example, 100, 1, 'a')
  const alpha = await exampleLimited('alpha')
  const beta = await exampleLimited('beta')
  const gamma = await exampleLimited('gamma')
  const epsilon = await exampleLimited('epsilon')
  const phi = await exampleLimited('phi')
  const intervals = getIntervalsBetweenDates(timeLog)
  const invalidIntervals = filter(intervals, (interval) => interval < 100)
  const totalTime = arraySum(intervals)
  t.is(intervals.length, 4)
  t.deepEqual(invalidIntervals, [])
  t.deepEqual(totalTime >= 400, true)
  t.is(alpha, 'alpha')
  t.is(beta, 'beta')
  t.is(gamma, 'gamma')
  t.is(epsilon, 'epsilon')
  t.is(phi, 'phi')
})

我创建了一个 getIntervalsBetweenDates 函数,它简单地比较两个 unix 时间戳并获取日期数组之间的持续时间。

export function getIntervalsBetweenDates (dates) {
  let intervals = []
  dates.forEach((date, index) => {
    let nextDate = dates[index + 1]
    if (nextDate) intervals.push(nextDate - date)
  })
  return intervals
}

问题是上面的测试有时会返回一个低于 delay 的间隔。例如,如果 delay100ms,有时间隔会返回 98ms96ms。没有理由会发生这种情况。

有没有办法让上面的测试100%通过?我试图确保 delay 参数有效,并且 promise 之间至少有那么多时间。

更新时间为 2016 年 12 月 28 日上午 9:20(美国东部时间)

这是完整的测试

import test from 'ava'
import Debug from 'debug'
import Promise from 'bluebird'
import promiseRateLimit from './index'
import {getIntervalsBetweenDates} from '../utilitiesForDates'
import {arraySum} from '../utilitiesForArrays'
import {filter} from 'lodash'

test('using async await', async (t) => {
  let timeLog = []
  let runCount = 0
  let bufferInterval = 100
  let promisesLength = 4
  const example = v => {
    timeLog.push(new Date)
    runCount++
    return Promise.delay(50, v)
  }
  const exampleLimited = promiseRateLimit(example, bufferInterval, 1)
  const alpha = await exampleLimited('alpha')
  const beta = await exampleLimited('beta')
  const gamma = await exampleLimited('gamma')
  const epsilon = await exampleLimited('epsilon')
  const phi = await exampleLimited('phi')
  const intervals = getIntervalsBetweenDates(timeLog)
  const invalidIntervals = filter(intervals, (interval) => interval < bufferInterval)
  const totalTime = arraySum(intervals)
  t.is(intervals.length, promisesLength)
  t.deepEqual(invalidIntervals, [])
  t.deepEqual(totalTime >= bufferInterval * promisesLength, true)
  t.is(alpha, 'alpha')
  t.is(beta, 'beta')
  t.is(gamma, 'gamma')
  t.is(epsilon, 'epsilon')
  t.is(phi, 'phi')
})

test('using Promise.all with 2 promises', async (t) => {
  let timeLog = []
  let runCount = 0
  let bufferInterval = 100
  let promisesLength = 1
  const example = v => {
    timeLog.push(new Date)
    runCount++
    return Promise.delay(50, v)
  }
  const exampleLimited = promiseRateLimit(example, bufferInterval, 1)
  const results = await Promise.all([exampleLimited('alpha'), exampleLimited('beta')])
  const intervals = getIntervalsBetweenDates(timeLog)
  const invalidIntervals = filter(intervals, (interval) => interval < bufferInterval)
  const totalTime = arraySum(intervals)
  t.is(intervals.length, promisesLength)
  t.deepEqual(invalidIntervals, [])
  t.deepEqual(totalTime >= bufferInterval * promisesLength, true)
})

test('using Promise.props with 4 promises', async (t) => {
  let timeLog = []
  let runCount = 0
  let bufferInterval = 100
  let promisesLength = 3
  const example = v => {
    timeLog.push(new Date)
    runCount++
    return Promise.delay(200, v)
  }
  const exampleLimited = promiseRateLimit(example, bufferInterval, 1)
  const results = await Promise.props({
    'alpha': exampleLimited('alpha'),
    'beta': exampleLimited('beta'),
    'gamma': exampleLimited('gamma'),
    'delta': exampleLimited('delta')
  })
  const intervals = getIntervalsBetweenDates(timeLog)
  const invalidIntervals = filter(intervals, (interval) => interval < bufferInterval)
  const totalTime = arraySum(intervals)
  t.is(intervals.length, promisesLength)
  t.deepEqual(invalidIntervals, [])
  t.deepEqual(totalTime >= bufferInterval * promisesLength, true)
  t.is(results.alpha, 'alpha')
  t.is(results.beta, 'beta')
  t.is(results.gamma, 'gamma')
  t.is(results.delta, 'delta')
})


test('using Promise.props with 12 promises', async (t) => {
  let timeLog = []
  let runCount = 0
  let bufferInterval = 100
  let promisesLength = 11
  const example = v => {
    timeLog.push(new Date)
    runCount++
    return Promise.delay(200, v)
  }
  const exampleLimited = promiseRateLimit(example, bufferInterval, 1)
  const results = await Promise.props({
    'a': exampleLimited('a'),
    'b': exampleLimited('b'),
    'c': exampleLimited('c'),
    'd': exampleLimited('d'),
    'e': exampleLimited('e'),
    'f': exampleLimited('f'),
    'g': exampleLimited('g'),
    'h': exampleLimited('h'),
    'i': exampleLimited('i'),
    'j': exampleLimited('j'),
    'k': exampleLimited('k'),
    'l': exampleLimited('l')
  })
  const intervals = getIntervalsBetweenDates(timeLog)
  console.log(intervals)
  const invalidIntervals = filter(intervals, (interval) => interval < bufferInterval)
  const totalTime = arraySum(intervals)
  t.is(intervals.length, promisesLength)
  t.deepEqual(invalidIntervals, [])
  t.deepEqual(totalTime >= bufferInterval * promisesLength, true)
})

即使更改了 example,我仍然遇到问题。

[ 99, 98, 105, 106, 119, 106, 105, 105, 101, 106, 100 ]

  2 passed
  2 failed

  using Promise.props with 4 promises

  t.deepEqual(invalidIntervals, [])
              |                    
              [99]                 

  Generator.next (<anonymous>)

  using Promise.props with 12 promises

  t.deepEqual(invalidIntervals, [])
              |                    
              [99,98]              

  Generator.next (<anonymous>)

最佳答案

setTimeout(在 Promise.delay 内部使用)不保证准确的时间,它只确保回调被调用不早于 给定的超时到期。实际时间将取决于机器负载、事件循环的速度和 possibly anything else .

事实上,Node.js docs只声明

The callback will likely not be invoked in precisely delay milliseconds. Node.js makes no guarantees about the exact timing of when callbacks will fire, nor of their ordering. The callback will be called as close as possible to the time specified.

在您的测试中会发生的是 Promise.delay(50) 有时需要超过 50 毫秒(不多,但仍然如此),并且与以下日志的差异可能小于 100 毫秒当下一个 Promise.delay(50) 更准时时。

如果您只是立即记录示例 函数的调用时间,而不是在大约 50 毫秒的人为延迟之后,您应该能够减轻这种影响:

const example = v => {
  timeLog.push(new Date);
  runCount++;
  return Promise.delay(50, v)
};

要处理 100 毫秒超时本身的不准确性,最简单的解决方案是给它一些可能为 5% 的余地(在您的情况下为 5 毫秒):

const invalidIntervals = filter(intervals, (interval) => interval < 100 * .95)
t.true(totalTime >= 400 * .95)

如果你想绝对确定延迟永远不会太短,你可以编写自己的函数:

Promise.delayAtLeast = function(delay, value) {
    const begin = Date.now()
    return Promise.delay(delay, value).then(function checkTime(v) {
        const duration = Date.now() - begin;
        return duration < delay
          ? Promise.delay(delay - duration, v).then(checkTime);
          : v;
    });
};

并在 promiseRateLimit 中使用它。

关于javascript - 为 promise 速率限制功能创建有效测试用例的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41293841/

有关javascript - 为 promise 速率限制功能创建有效测试用例的问题的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  5. ruby-on-rails - 无法使用 Rails 3.2 创建插件? - 2

    我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby​​1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在

  6. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  7. ruby - Ruby 的 Hash 在比较键时使用哪种相等性测试? - 2

    我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。

  8. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  9. ruby - 如何使用 RSpec::Core::RakeTask 创建 RSpec Rake 任务? - 2

    如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake

  10. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

随机推荐