延迟单元测试
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))
}
您在这种方法中所做的权衡是通过编写更多代码使测试更直接。这是否是一个好的权衡取决于你自己决定。
我喜欢以这种方式工作,因为它使我系统中的每个组件都没有隐式依赖关系,而且我最终编写的测试易于阅读,并且可以作为软件的动态文档使用。
告诉我你的想法。
所以我有一个单元测试来测试诊所是否每 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))
}
您在这种方法中所做的权衡是通过编写更多代码使测试更直接。这是否是一个好的权衡取决于你自己决定。
我喜欢以这种方式工作,因为它使我系统中的每个组件都没有隐式依赖关系,而且我最终编写的测试易于阅读,并且可以作为软件的动态文档使用。
告诉我你的想法。