Как проверить безопасность потоков с помощью XCTest

Предположим, у нас есть следующий класс с изменяемым состоянием:

class Machine {
   var state = 0
}

Теперь предположим, что есть некоторые внутренние механизмы, которые контролируют государство. Однако изменение состояния может произойти в любом потоке или очереди, поэтому чтение и запись в state свойство должно быть выполнено в поточно-безопасной среде. Для достижения этого мы будем использовать простые sync(:_) метод на dispatch_queue_t синхронизировать доступ к state переменная. (Не единственный способ сделать это, но это один пример)

Теперь мы можем создать одну закрытую переменную, которая содержит значение состояния, и другую открытую переменную с пользовательскими установщиками и получателями, которые используют dispatch_sync(_:) метод.

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 выполнение, потому что мне нужно отслеживать, когда заканчиваются все мутации состояния. Это решение кажется довольно сложным и несостоятельным.

Что я могу сделать по-другому, чтобы улучшить тестирование?

1 ответ

Есть две важные движущиеся части при рассмотрении, чтобы проверить потокобезопасный код как это:

  1. что средство доступа к состоянию выполняется только в контексте блокировки
  2. что механизм блокировки фактически безопасен для потоков.

В то время как первый может быть относительно тестируемым с использованием методов моделирования, последний трудно проверить, главным образом потому, что проверка того, что некоторый код является поточно-ориентированным, включает в себя модульное тестирование кода из нескольких потоков, одновременно обращающихся к потокобезопасному ресурсу. И даже этот метод не является пуленепробиваемым, так как мы не можем полностью контролировать порядок выполнения потоков, которые мы создаем из модульных тестов, или выделенное время для потока, чтобы убедиться, что мы отловили все условия гонки, которые могут возникнуть.

Учитывая вышесказанное, я бы рекомендовал написать небольшой класс / структуру, которая обеспечивает механизм блокировки, и использовать его в 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 выполняются только в основном потоке, этого должно быть достаточно для проверки безопасности потока. Просто отметьте, что механизм блокировки является чем-то связанным с деталями реализации этого класса, а у деталей реализации модульного тестирования есть недостаток, заключающийся в том, что он тесно связан с модулем и модульным тестом, что может привести к необходимости обновления теста, если детали реализации меняются, что делает тест менее надежным.

Другие вопросы по тегам