当前位置 : 主页 > 手机开发 > 其它 >

swift – 如何使用XCTest测试线程安全性

来源:互联网 收集:自由互联 发布时间:2021-06-11
假设我们有一个具有可变状态的以下类: class Machine { var state = 0} 现在,让我们说有一些内部机制来控制状态.但是,状态更改可能发生在任何线程或队列上,因此必须在线程安全环境中执行
假设我们有一个具有可变状态的以下类:

class Machine {
   var state = 0
}

现在,让我们说有一些内部机制来控制状态.但是,状态更改可能发生在任何线程或队列上,因此必须在线程安全环境中执行对state属性的读取和写入操作.为此,我们将在dispatch_queue_t上使用简单的sync(:_)方法来同步对状态变量的访问. (不是唯一的方法,但这只是一个例子)

现在,我们可以使用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测试此行为?

由于类机器可以有一个复杂的状态机,我们需要测试它在任何环境中的表现:

>从任何队列或线程测试对状态的访问
>测试从任何队列或线程写入状态

成功测试此类行为的最佳方法是什么?

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

为了实现更好的测试,我能做些什么?

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

>状态访问器仅在锁定的上下文中运行
>锁定机制实际上是线程安全的.

虽然第一个可以通过使用模拟技术相对可测试,但后者难以测试,主要是因为验证某些代码是线程安全的,涉及单元测试来自多个线程的代码同时访问线程安全资源.甚至这种技术也不是防弹,因为我们不能完全控制我们从单元测试中创建的线程的执行顺序,也不能完全控制每个线程的分配时间,以确保我们能够捕获可能发生的所有竞争条件.

考虑到上述情况,我建议编写一个提供锁定机制的小类/结构,并在状态访问器中使用它.分离这样的责任使得通过代码审查更容易评估锁定机制的正确性.

因此,我的建议是将线程安全代码移动到专用包装器中,并使用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在其状态属性方面也是线程安全的.

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

网友评论