测试js异步代码_测试异步代码

测试js异步代码

异步代码很难。 每个人都知道。 编写异步测试更加困难。 最近,我修复了一个不稳定的测试,我想分享一些有关编写异步测试的想法。

在本文中,我们探讨了异步测试的一个常见问题-如何强制测试在线程之间进行特定排序,以及如何迫使某些线程的某些操作先于其他线程的其他操作完成。

通常,我们不希望在不同线程的执行之间强制执行顺序,因为它打乱了使用线程的原因,即允许并发并允许CPU在给定当前资源和应用程序状态的情况下选择最佳执行顺序。 但是在测试的情况下,有时需要确定性排序以确保测试的稳定性。

测试油门

节流阀是软件中的一种模式,负责限制并发操作的数量以保留某些资源配额,例如连接池,网络缓冲区或CPU密集型操作。 与其他同步工具不同,调节器的作用是启用“快速失败”功能,从而允许超额报价请求立即失败而无需等待。 快速失败很重要,因为等待等待会消耗资源(端口,线程和内存)。

这是调节器的简单实现(基本上是信号灯的包装;在现实世界中,可能会等待,重试等):

class ThrottledException extends RuntimeException("Throttled!")
class Throttler(count: Int) {
  private val semaphore = new Semaphore(count)
  def apply(f: => Unit): Unit = {
    if (!semaphore.tryAcquire()) throw new ThrottledException
    try {
      f
    } finally {
      semaphore.release()
    }
  }
}

让我们从一个基本的单元测试开始:测试一个线程的节流阀(我们使用specs2进行测试)。 在此测试中,我们正在验证,与节流阀的最大并发调用数(下面的maxCount变量)相比,我们可以按顺序进行更多的调用。 请注意,因为我们使用的是单线程,所以我们不会测试调节器的“故障快速”功能,因为我们不会使调节器饱和。 实际上,我们仅测试了在节流阀未达到饱和时不会中断操作。

class ThrottlerTest extends Specification {
  "Throttler" should {
    "execute sequential" in new ctx {
      var invocationCount = 0
      for (i <- 0 to maxCount) {
        throttler {
          invocationCount += 1
        }
      }
      invocationCount must be_==(maxCount + 1)
    }
  }
  trait ctx {
    val maxCount = 3
    val throttler = new Throttler(maxCount)
  }
}

异步测试油门

在先前的测试中,我们并没有仅仅因为单个线程不可能使油门饱和。 因此,下一步是测试节流阀在多线程环境中是否能正常工作。

设置:

val e = Executors.newCachedThreadPool()
implicit val ec: ExecutionContext=ExecutionContext.fromExecutor(e)
private val waitForeverLatch = new CountDownLatch(1)

override def after: Any = {
  waitForeverLatch.countDown()
  e.shutdownNow()
}

def waitForever(): Unit = try {
  waitForeverLatch.await()
} catch {
  case _: InterruptedException =>
  case ex: Throwable => throw ex
}

ExecutionContext用于将来的构造; 在测试结束之前,waitForever方法将保持一个线程,直到释放闩锁为止。 在after函数中,我们关闭了执行程序服务。

测试节流器的多线程行为的一种简单方法如下:

"throw exception once reached the limit [naive,flaky]" in new ctx {
  for (i <- 1 to maxCount) {
    Future {
      throttler(waitForever())
    }
  }
  throttler {} must throwA[ThrottledException]
}

在这里,我们正在创建maxCount线程(对Future {}的调用),这些线程将调用waitForever函数,该函数一直等到测试结束。 然后,我们尝试执行另一个操作来绕过节流器-maxCount +1。根据设计,此时应获得ThrottledException。 但是,当我们等待异常时,可能不会发生。 对调速器的最后一次调用(带有期望)可能发生在某个期货开始之前(导致在此将来引发异常,但未达到预期)。

上面测试的问题在于,在尝试违反节流阀引发异常的预期结果而违反节流阀之前,我们无法确保所有线程都已启动并在waitForever函数中等待。 要解决此问题,我们需要某种方法来等待所有期货开始。 这是我们许多人都熟悉的方法:只需添加一段合理持续时间的sleep方法调用即可。

"throw exception once reached the limit [naive, bad]" in new ctx {
  for (i <- 1 to maxCount) {
    Future {
      throttler(waitForever())
    }
  }
  Thread.sleep(1000)
  throttler {} must throwA[ThrottledException]
}

好的,现在我们的测试几乎总是会通过,但是这种方法至少有两个原因是错误的:
测试的持续时间将仅与我们设置的“合理持续时间”一样长。
在非常罕见的情况下,例如当机器处于高负载下时,这个合理的持续时间是不够的。

如果您仍然不确定,请搜索Google以了解更多原因。

更好的方法是使线程(期货)的开始与期望同步。 让我们使用java.util.concurrent中的CountDownLatch类:

"throw exception once reached the limit [working]" in new ctx {
  val barrier = new CountDownLatch(maxCount)

  for (i <- 1 to maxCount) {
    Future {
      throttler {
        barrier.countDown()
        waitForever()
      }
    }
  }

  barrier.await(5, TimeUnit.SECONDS) must beTrue

  throttler {} must throwA[ThrottledException]
}

我们使用CountDownLatch进行障碍同步 。 等待方法阻塞主线程,直到闩锁计数为零为止。 随着其他线程的运行(让我们将其他线程表示为期货),这些期货中的每一个都会调用barrier countDown方法来将锁存器计数降低一个。 一旦闩锁计数为零,所有期货都将在waitForever方法内。

到那时,我们可以确保节流阀处于饱和状态,其中包含maxCount线程。 另一个线程尝试进入节流阀将导致异常。 我们可以采用确定性的方式来设置测试,即尝试使主线程进入节流阀。 主线程可以并且确实会在此时恢复(锁存器计数达到零,并且CountDownLatch释放等待线程)。

我们使用稍高的超时作为保护措施,以避免在发生意外情况时无限阻塞。 如果确实发生了某些情况,我们将无法通过测试。 此超时不会影响测试持续时间,因为除非发生意外情况,否则我们不应该等待它。

结论

在测试异步代码时,通常要求在特定测试的线程之间进行特定的操作排序。 不使用任何同步会导致不稳定的测试有时会起作用,有时会失败。 使用Thread.sleep会减慢速度并降低测试的脆弱性,但不能解决问题。

在大多数情况下,当我们需要在测试中强制执行线程之间的排序时,可以使用CountDownLatch而不是Thread.sleep。 CountDownLatch的优点是我们可以告诉它何时释放等待(保持)线程,从而获得两个重要的好处:确定性排序,因此测试更加可靠,运行速度更快。 即使对于琐碎的等待(例如,waitForever函数),我们也可以使用Thread.sleep(Long.MAX_VALUE)之类的方法,但是最好不要使用脆弱的方法。

  • 您可以在GitHub上找到完整的代码。

翻译自: https://www.javacodegeeks.com/2015/10/testing-asynchronous-code.html

测试js异步代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值