延迟单元测试

Delay in unit test

所以我有一个单元测试来测试诊所是否每 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 = \([=10=])")
        expec.fulfill()
    }

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

我想延迟 5 秒。像我那样使用倒置期望是我能找到让它发挥作用的唯一方法。我只是认为使用反向期望是不好的做法。

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

我有两个建议一起使用:

  • 使用 spy test double 确保您的数据存储用于刷新诊所的服务被调用两次
  • 注入刷新间隔使测试更快

间谍测试替身

测试数据加载的副作用,即它命中服务,可能是简化测试的一种方法。

与其使用不同的期望并以可能不会在 运行 时间(dataStore.clinicsSignal.fire([]))发生的方式运行被测系统,不如计算服务运行了多少次命中,并断言值为 2.

注入刷新间隔

我推荐的方法是在class中注入诊所更新频率的时间设置,然后在class中设置一个较低的值测试。

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

您可以通过在数据存储的初始化中将该值作为默认值来实现,然后在测试中覆盖它。

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

把它们放在一起,或多或少是这样的

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))
}

您在这种方法中所做的权衡是通过编写更多代码使测试更直接。这是否是一个好的权衡取决于你自己决定。

我喜欢以这种方式工作,因为它使我系统中的每个组件都没有隐式依赖关系,而且我最终编写的测试易于阅读,并且可以作为软件的动态文档使用。

告诉我你的想法。