Предполагается, что под "многопоточным" кодом подразумевается что-то, что
- с изменяемым состоянием и изменчивостью
- И доступ / изменение нескольких потоков
одновременно
Другими словами, мы говорим о тестировании настраиваемого многопоточного класса / метода / модуля с безопасным потоком - который в наше время должен быть очень редким зверем.
Поскольку этот зверь встречается редко, прежде всего нам нужно убедиться, что есть все веские основания для его написания.
Шаг 1. Рассмотрите возможность изменения состояния в том же контексте синхронизации.
Сегодня легко написать совместимый и асинхронный код с возможностью компоновки, в котором ввод-вывод или другие медленные операции выгружаются в фоновый режим, но общее состояние обновляется и запрашивается в одном контексте синхронизации. например задачи async / await и Rx в .NET и т. д. - все они тестируемые по конструкции, «реальные» задачи и планировщики могут быть заменены, чтобы сделать тестирование детерминированным (однако это выходит за рамки вопроса).
Это может звучать очень ограниченно, но этот подход работает на удивление хорошо. Можно писать целые приложения в этом стиле без необходимости делать какие-либо потоки безопасными (я так делаю).
Шаг 2. Если манипулирование общим состоянием в едином контексте синхронизации абсолютно невозможно.
Убедитесь, что колесо не изобретается заново, и, безусловно, не существует стандартной альтернативы, которая может быть адаптирована для данной работы. Вполне вероятно, что код очень сплоченный и содержится в одном блоке, например с большой вероятностью это особый случай некоторой стандартной потокобезопасной структуры данных, такой как хэш-карта, коллекция или что-то еще.
Примечание: если код большой / охватывает несколько классов и требует многопоточных манипуляций с состоянием, то очень велика вероятность того, что дизайн не будет хорошим, пересмотрите Шаг 1
Шаг 3. Если этот шаг достигнут, нам нужно протестировать наш собственный ориентированный на состояние потокобезопасный класс / метод / модуль .
Я буду абсолютно честен: мне никогда не приходилось писать надлежащие тесты для такого кода. Большую часть времени я ухожу на шаге 1, иногда на шаге 2. В прошлый раз мне приходилось писать собственный поточно-ориентированный код, который был так много лет назад, что это было до того, как я принял модульное тестирование / вероятно, мне не пришлось бы его писать в любом случае с текущими знаниями.
Если бы мне действительно нужно было протестировать такой код (, наконец, фактический ответ ), то я бы попробовал пару вещей ниже
Недетерминированное стресс-тестирование. например запустить 100 потоков одновременно и проверить, что конечный результат соответствует.
Это более типично для высокоуровневого / интеграционного тестирования многопользовательских сценариев, но также может использоваться на уровне устройства.
Предоставьте некоторые тестовые «ловушки», где тест может внедрить некоторый код, чтобы помочь создать детерминированные сценарии, в которых один поток должен выполнить операцию раньше другого.
Как бы ужасно это ни было, я не могу придумать ничего лучшего.
Тестирование с задержкой для запуска потоков и выполнения операций в определенном порядке. Строго говоря, такие тесты также являются недетерминированными (существует вероятность того, что система GC может заморозить / остановить мир, которая может исказить иные организованные задержки), также она уродлива, но позволяет избежать хуков.