swift - 延迟单元测试

标签 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但无济于事。

最佳答案

我有两个建议一起使用:

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

spy 测试替身

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

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

注入(inject)刷新间隔

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

毕竟,我猜您感兴趣的是更新代码按预期运行,而不是每 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))
}

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

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

让我知道你的想法。

关于swift - 延迟单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55874328/

相关文章:

ios - 在后台线程上加载 SpriteKit 场景会导致应用程序因内存问题而崩溃

ios - Swift编译器无法在合理的时间内对这种表达式进行类型检查。尝试将表达式分解为不同的子表达式

swift - Xcode 9 - 测试目标 X 遇到错误(无法连接到测试管理器)

c# - 包含逻辑的静态工厂方法的单元测试

java - 在一天中的特定时间运行的 Java 程序

java - 间隔运行 Java 线程

ios - Swift 无法从 NSIndexPath 对象调用部分/行

ios - DeepLink 支持 iOS 8 和 iOS 9

c - 单元测试错误条件 - EINTR

java - 使用时钟更新 TextView