延迟单元测试

时间:2019-04-26 20:07:26

标签: swift unit-testing timer delay

因此,我有一个单元测试来测试是否每10秒钟更新一次诊所。 5秒后,我清除了所有诊所。然后设定一个期望值,该期望值会在9秒后超时,以确保更新了诊所。这是我的代码:

func testRefresh() {

    let expec = expectation(description: "Clinics timer expectation")
    let expec2 = expectation(description: "Clinics timer expectation2")
    expec2.isInverted = true
    let dispatchGroup = DispatchGroup(count: 5)

    dataStore.load()

    wait(for: [expec2], timeout: 5.0) // This is what I am asking about
    self.dataStore.clinicsSignal.fire([])

    dataStore.clinicsSignal.subscribeOnce(with: dispatchGroup) {
        print("clinics signal = \($0)")
        expec.fulfill()
    }

    wait(for: [expec], timeout: 9.0)
    XCTAssertFalse(self.dataStore.clinics.isEmpty)
}

我希望延迟5秒。使用倒置的期望方法是我找到使它起作用的唯一方法。我只是认为使用反向期望是不好的做法。

如果我使用sleep(5),它将使整个程序停止5秒钟。我也尝试过使用DispatchQueue.main.asyncAfter的解决方案,如概述的here,但无济于事。

1 个答案:

答案 0 :(得分:1)

我有两个建议可以一起使用:

  • 使用spy test double确保两次调用您的数据存储区刷新诊所的服务
  • 注入刷新间隔以加快测试速度

间谍测试两次

测试数据加载对服务的负面影响可能是简化测试的一种方法。

您不必计算不同的期望并以运行时可能不会发生的方式(dataStore.clinicsSignal.fire([]))来运行被测系统,您只需计算服务被击中的次数,并断言该值为2。

注入刷新间隔

我建议的方法是注入时间设置,以便在班级中更新诊所的频率,然后在测试中设置较低的值。

毕竟,我猜您感兴趣的是更新代码按预期运行,而不是每10秒运行一次。也就是说,它应该以您设置的频率进行更新。

您可以通过在数据存储的init中将其默认值作为默认值,然后在测试中覆盖它来实现此目的。

我之所以建议使用较短的刷新间隔,是因为在单元测试的上下文中,它们运行得越快越好。您希望反馈循环尽可能快。

将所有内容放在一起,大概是这样

protocol ClinicsService {
  func loadClinics() -> SignalProducer<[Clinics], ClinicsError>
}

class DataSource {

  init(clinicsService: ClinicsService, refreshInterval: TimeInterval = 5) { ... }
}

// in the tests

class ClinicsServiceSpy: ClinicsService {

  private(var) callsCount: Int = 0

  func loadClinics() -> SignalProducer<[Clinics], ClinicsError> {
    callsCount += 1
    // return some fake data
  }
}

func testRefresh() {
  let clinicsServiceSpy = ClinicsServiceSpy()
  let dataStore = DataStore(clinicsService: clinicsServiceSpy, refreshInterval: 0.05)

  // This is an async expectation to make sure the call count is the one you expect
  _ = expectation(
    for: NSPredicate(
    block: { input, _ -> Bool in
      guard let spy = input as? ClinicsServiceSpy else { return false }
      return spy.callsCount == 2
    ),
    evaluatedWith: clinicsServiceSpy,
    handler: .none
  )

  dataStore.loadData()

  waitForExpectations(timeout: .2, handler: nil)
}

如果您还使用Nimble来拥有更完善的期望API,则测试可能如下所示:

func testRefresh() {
  let clinicsServiceSpy = ClinicsServiceSpy()
  let dataStore = DataStore(clinicsService: clinicsServiceSpy, refreshInterval: 0.05)

  dataStore.loadData()

  expect(clinicsServiceSpy.callsCount).toEventually(equal(2))
}

您在这种方法中要进行的权衡是通过编写更多代码来使测试更直接。权衡取舍取决于您自己。

我喜欢以这种方式工作,因为它使系统中的每个组件都摆脱了隐式依赖,并且最终编写的测试易于阅读,并且可以用作该软件的实时文档。

让我知道您的想法。