假设我们有一个具有可变状态的以下类:
class Machine {
var state = 0
}
现在,让我们说有一些控制国家的内部机制。但是,状态更改可能发生在任何线程或队列上,因此必须在线程安全环境中执行对state
属性的读写操作。为此,我们将在sync(:_)
上使用简单的dispatch_queue_t
方法来同步对state
变量的访问。 (不是唯一的方法,但这只是一个例子)
现在,我们可以使用dispatch_sync(_:)
方法创建一个包含状态值的私有变量和另一个包含自定义setter和getter的公共变量。
class Machine {
private var internalState = 0
var state: Int {
get {
var value: Int?
dispatch_sync(dispatch_get_main_queue()) {
value = self.internalState
}
return value!
}
set(newState) {
dispatch_sync(dispatch_get_main_queue()) {
self.internalState = newState
}
}
}
}
state
现在可以从任何队列或线程进行安全的同步访问 - 它的线程安全。
现在问题就在这里。
XCTest
?由于类Machine
可以有一个复杂的状态机,我们需要测试它在任何环境中的表现:
state
的访问权state
成功测试此类行为的最佳方法是什么?
目前,我正在创建自定义调度队列数组和已定义状态数组。然后我使用dispatch_async
方法来更改状态并测试其值。这引入了XCTest
执行的新问题,因为我需要跟踪所有状态突变何时完成。该解决方案似乎相当复杂且难以维护。
为了获得更好的测试,我能做些什么?
答案 0 :(得分:1)
在考虑测试这样的线程安全代码时,有两个重要的移动部分:
虽然第一个可以通过使用模拟技术相对可测试,但后者难以测试,主要是因为验证某些代码是线程安全的,涉及单元测试来自多个线程的代码同时访问线程安全资源时间。甚至这种技术也不是防弹,因为我们不能完全控制我们从单元测试中创建的线程的执行顺序,也不能完全控制每个线程的分配时间,以确保我们捕获可能发生的所有可能的竞争条件。
考虑到上述情况,我建议编写一个提供锁定机制的小类/结构,并在state
访问器中使用它。将这样的责任分开使得通过代码审查更容易评估锁定机制的正确性。
所以,我的建议是将线程安全代码移动到专用包装器中,并使用Machine
类中的包装器:
/// A struct that just wraps a value and access it in a thread safe manner
public struct ThreadSafeBox<T> {
private var _value: T
/// Thread safe value, uses the main thread to synchronize the accesses
public var value: T {
get {
if Thread.isMainThread { return _value }
else { return DispatchQueue.main.sync { _value } }
}
set {
if Thread.isMainThread { _value = newValue }
else { DispatchQueue.main.sync { _value = newValue } }
}
}
/// Initializes the box with the given value
init(_ value: T) {
_value = value
}
}
ThreadSafeBox
代码相对较小,在代码审查时可以发现任何设计缺陷,因此理论上它的线程安全性可以通过代码分析来证明。一旦我们证明了ThreadSafeBox
的可靠性,我们就可以保证Machine
在其state
属性方面也是线程安全的。
如果你真的想测试属性访问器,你可以验证get / set操作只在主线程上运行的事实,这应该足以验证线程的安全性。请注意,锁定机制与该类的实现细节有关,单元测试实现细节的缺点是紧密耦合单元和单元测试。如果实现细节发生变化,这可能导致需要更新测试,这使得测试不太可靠。