如何测试线程

假设我们有一个可变状态的下面的类:

class Machine {
   var state = 0
}

现在,假设有一些内部机制来控制状态。 但是,任何线程或队列都可能发生状态更改,因此读写state属性必须在线程安全的环境中执行。 为了实现这一点,我们将在dispatch_queue_t上使用简单的sync(:_)方法来同步对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测试这种行为?

由于class Machine可以有一个复杂的状态机,因此我们需要测试它在任何环境中的执行情况:

  • 从任何队列或线程测试对state访问权限
  • 测试写入state从任何队列或线程
  • 测试这种行为的最佳方法是什么?

    目前,我正在创建自定义调度队列和已定义状态数组的阵列。 然后我使用dispatch_async方法来改变状态并测试它的值。 这引入了XCTest执行的新问题,因为我需要跟踪所有状态突变何时完成。 该解决方案看起来相当复杂且难以维护。

    为了获得更好的测试,我可以采取什么不同的做法?


    考虑测试像这样的线程安全代码时,有两个重要的移动部分:

  • 状态访问器仅在锁的上下文中运行
  • 锁定机制实际上是线程安全的。
  • 虽然第一个可以通过使用嘲讽技术进行相对测试,但后者难以测试,主要是因为验证某些代码是线程安全的,它涉及到来自多个线程的代码同时访问线程安全资源。 甚至这种技术也不是防弹的,因为我们无法完全控制我们从单元测试中创建的线程的执行顺序,也没有为每个线程分配的时间,以确保我们捕捉到可能发生的所有竞争条件。

    考虑到上述情况,我建议编写一个提供锁定机制的小类/结构体,并在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
        public var value: T {
            get {
                guard !Thread.isMainThread else { return _value }
    
                var result: T!
                DispatchQueue.main.sync { result = self._value }
                return result
            }
            set {
                guard !Thread.isMainThread else { return _value = newValue }
    
                DispatchQueue.main.sync { _value = newValue }
            }
        }
    
        init(_ value: T) {
            _value = value
        }
    }
    
    class Machine {
        private var threadSafeState = ThreadSafeBox(0)
        public var state: Int {
            get { return threadSafeState.value }
            set { threadSafeState.value = newValue }
        }
    }
    

    ThreadSafeBox代码相对较小,任何设计缺陷都可以在代码审查时发现,所以理论上它的线程安全性可以通过代码分析来验证。 一旦我们证明了ThreadSafeBox的可靠性,那么我们就保证了Machine对于其state属性也是线程安全的。

    如果你真的想测试属性访问器,你可以验证get / set操作只在主线程上运行的事实,这应该足以验证线程的安全性。 请注意,锁定机制与该类的实现细节有关,单元测试实现细节具有将单元与单元测试紧密耦合的缺点,如果实现细节发生更改,则可能导致更新测试,这使得测试不太可靠。

    链接地址: http://www.djcxy.com/p/57931.html

    上一篇: How to test thread

    下一篇: Is it possible to detect if a Stream has been closed by the client?