【发布时间】:2017-01-20 03:49:58
【问题描述】:
我正在开发一个使用 Swift 的 Timer 类的项目。我的TimerController 类将通过启动、暂停、恢复和重置来控制Timer 实例。
TimerController由以下代码组成:
internal final class TimerController {
// MARK: - Properties
private var timer = Timer()
private let timerIntervalInSeconds = TimeInterval(1)
internal private(set) var durationInSeconds: TimeInterval
// MARK: - Initialization
internal init(seconds: Double) {
durationInSeconds = TimeInterval(seconds)
}
// MARK: - Timer Control
// Starts and resumes the timer
internal func startTimer() {
timer = Timer.scheduledTimer(timeInterval: timerIntervalInSeconds, target: self, selector: #selector(handleTimerFire), userInfo: nil, repeats: true)
}
internal func pauseTimer() {
invalidateTimer()
}
internal func resetTimer() {
invalidateTimer()
durationInSeconds = 0
}
// MARK: - Helpers
@objc private func handleTimerFire() {
durationInSeconds += 1
}
private func invalidateTimer() {
timer.invalidate()
}
}
目前,我的TimerControllerTests 包含以下代码:
class TimerControllerTests: XCTestCase {
func test_TimerController_DurationInSeconds_IsSet() {
let expected: TimeInterval = 60
let controller = TimerController(seconds: 60)
XCTAssertEqual(controller.durationInSeconds, expected, "'durationInSeconds' is not set to correct value.")
}
}
在初始化TimerController 的实例时,我能够测试计时器的预期持续时间设置是否正确。但是,我不知道从哪里开始测试TimerController 的其余部分。
我想确保该类成功处理startTimer()、pauseTimer() 和resetTimer()。我希望我的单元测试尽可能快地运行,但我认为我需要实际启动、暂停和停止计时器以测试在调用适当的方法后是否更新了 durationInSeconds 属性。
在TimerController 中实际创建计时器并调用我的单元测试中的方法来验证durationInSeconds 是否已正确更新是否合适?
我意识到它会减慢我的单元测试速度,但我不知道另一种方法可以适当地测试这个类以及它的预期操作。
更新
我一直在做一些研究,并且我发现了我认为的解决方案,就我的测试而言似乎可以完成工作。但是,我不确定这种实现是否足够。
我重新实现了我的TimerController,如下所示:
internal final class TimerController {
// MARK: - Properties
private var timer = Timer()
private let timerIntervalInSeconds = TimeInterval(1)
internal private(set) var durationInSeconds: TimeInterval
internal var isTimerValid: Bool {
return timer.isValid
}
// MARK: - Initialization
internal init(seconds: Double) {
durationInSeconds = TimeInterval(seconds)
}
// MARK: - Timer Control
internal func startTimer(fireCompletion: (() -> Void)?) {
timer = Timer.scheduledTimer(withTimeInterval: timerIntervalInSeconds, repeats: true, block: { [unowned self] _ in
self.durationInSeconds -= 1
fireCompletion?()
})
}
internal func pauseTimer() {
invalidateTimer()
}
internal func resetTimer() {
invalidateTimer()
durationInSeconds = 0
}
// MARK: - Helpers
private func invalidateTimer() {
timer.invalidate()
}
}
另外,我的测试文件通过了测试:
class TimerControllerTests: XCTestCase {
// MARK: - Properties
var timerController: TimerController!
// MARK: - Setup
override func setUp() {
timerController = TimerController(seconds: 1)
}
// MARK: - Teardown
override func tearDown() {
timerController.resetTimer()
super.tearDown()
}
// MARK: - Time
func test_TimerController_DurationInSeconds_IsSet() {
let expected: TimeInterval = 60
let timerController = TimerController(seconds: 60)
XCTAssertEqual(timerController.durationInSeconds, expected, "'durationInSeconds' is not set to correct value.")
}
func test_TimerController_DurationInSeconds_IsZeroAfterTimerIsFinished() {
let numberOfSeconds: TimeInterval = 1
let durationExpectation = expectation(description: "durationExpectation")
timerController = TimerController(seconds: numberOfSeconds)
timerController.startTimer(fireCompletion: nil)
DispatchQueue.main.asyncAfter(deadline: .now() + numberOfSeconds, execute: {
durationExpectation.fulfill()
XCTAssertEqual(0, self.timerController.durationInSeconds, "'durationInSeconds' is not set to correct value.")
})
waitForExpectations(timeout: numberOfSeconds + 1, handler: nil)
}
// MARK: - Timer State
func test_TimerController_TimerIsValidAfterTimerStarts() {
let timerValidityExpectation = expectation(description: "timerValidity")
timerController.startTimer {
timerValidityExpectation.fulfill()
XCTAssertTrue(self.timerController.isTimerValid, "Timer is invalid.")
}
waitForExpectations(timeout: 5, handler: nil)
}
func test_TimerController_TimerIsInvalidAfterTimerIsPaused() {
let timerValidityExpectation = expectation(description: "timerValidity")
timerController.startTimer {
self.timerController.pauseTimer()
timerValidityExpectation.fulfill()
XCTAssertFalse(self.timerController.isTimerValid, "Timer is valid")
}
waitForExpectations(timeout: 5, handler: nil)
}
func test_TimerController_TimerIsInvalidAfterTimerIsReset() {
let timerValidityExpectation = expectation(description: "timerValidity")
timerController.startTimer {
self.timerController.resetTimer()
timerValidityExpectation.fulfill()
XCTAssertFalse(self.timerController.isTimerValid, "Timer is valid")
}
waitForExpectations(timeout: 5, handler: nil)
}
}
我能想到的让测试更快的唯一方法就是模拟类并将let timerIntervalInSeconds = TimeInterval(1) 更改为private let timerIntervalInSeconds = TimeInterval(0.1)。
为了使用更短的时间间隔进行测试而模拟类是否过大?
【问题讨论】:
标签: swift unit-testing timer mocking