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

Posted

技术标签:

【中文标题】如何使用 XCTest 测试线程安全性【英文标题】:How to test thread-safety with XCTest 【发布时间】:2016-07-17 12:17:52 【问题描述】:

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

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 测试这种行为?

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

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

成功测试这种行为的最佳方法是什么?

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

我可以做哪些不同的事情来实现更好的测试?

【问题讨论】:

***.com/users/4590961/said-sikira 正在做类似的事情 - 您是否设法找到解决方案? 【参考方案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 操作仅在主线程上运行的事实,这足以验证线程安全性。请注意,锁定机制与该类的实现细节相关联,单元测试实现细节具有单元和单元测试紧密耦合的缺点。如果实现细节发生变化,这可能会导致需要更新测试,从而降低测试的可靠性。

【讨论】:

以上是关于如何使用 XCTest 测试线程安全性的主要内容,如果未能解决你的问题,请参考以下文章

如何测试应用程序的线程安全性?

如何测试线程安全[重复]

我是怎样测试Java类的线程安全性的

java 单例 线程安全 写一个测试类 说明下面单例不是线程安全的

线程安全测试

如何确保Java线程安全?