В последние годы я сталкивался с этой проблемой несколько раз, когда писал код обработки потоков для нескольких проектов. Я даю поздний ответ, потому что большинство других ответов, хотя и предоставляют альтернативы, на самом деле не отвечают на вопрос о тестировании. Мой ответ адресован случаям, когда нет альтернативы многопоточному коду; Я рассматриваю вопросы проектирования кода для полноты, но также обсуждаю модульное тестирование.
Написание тестируемого многопоточного кода
Первое, что нужно сделать, это отделить ваш код обработки производственных потоков от всего кода, который выполняет фактическую обработку данных. Таким образом, обработка данных может быть протестирована как однопоточный код, и единственное, что делает многопоточный код, - это координирует потоки.
Второе, что нужно помнить, это то, что ошибки в многопоточном коде вероятностны; ошибки, которые проявляются реже всего, - это ошибки, которые проникнут в производство, их будет трудно воспроизвести даже в процессе производства, и, следовательно, они вызовут самые большие проблемы. По этой причине стандартный подход к кодированию - быстро написать код, а затем отладить его, пока он не заработает, - плохая идея для многопоточного кода; это приведет к коду, в котором исправлены простые ошибки, а опасные ошибки все еще существуют.
Вместо этого, при написании многопоточного кода, вы должны писать код с позиции, которую вы собираетесь избегать, во-первых, избегая написания ошибок. Если вы правильно удалили код обработки данных, код обработки потоков должен быть достаточно маленьким - предпочтительно несколько строк, в худшем случае несколько десятков строк - чтобы у вас была возможность написать его без написания ошибки и, конечно же, без написания множества ошибок. Если вы понимаете многопоточность, не торопитесь, и будьте осторожны.
Написание модульных тестов для многопоточного кода
Как только многопоточный код написан настолько тщательно, насколько это возможно, все еще стоит написать тесты для этого кода. Основной целью тестов является не столько тестирование на наличие ошибок состояния гонки, зависящих от времени, - их невозможно повторить, но скорее тестирование того, что ваша стратегия блокировки для предотвращения таких ошибок позволяет нескольким потокам взаимодействовать по назначению ,
Чтобы правильно проверить правильность поведения блокировки, тест должен запустить несколько потоков. Чтобы сделать тест повторяемым, мы хотим, чтобы взаимодействия между потоками происходили в предсказуемом порядке. Мы не хотим внешнюю синхронизацию потоков в тесте, потому что это замаскирует ошибки, которые могут произойти в производственном процессе, когда потоки не синхронизированы извне. Это оставляет использование временных задержек для синхронизации потоков, что является техникой, которую я успешно использовал всякий раз, когда мне приходилось писать тесты многопоточного кода.
Если задержки слишком малы, то тест становится хрупким, потому что незначительные различия во времени - скажем, между разными машинами, на которых могут выполняться тесты - могут привести к отключению синхронизации и провалу теста. Обычно я начинаю с задержек, которые приводят к сбоям теста, увеличивают задержки, чтобы тест надежно проходил на моей машине для разработки, а затем удваивают задержки сверх этого, чтобы у теста был хороший шанс пройти на других машинах. Это означает, что тест займет макроскопическое количество времени, хотя, по моему опыту, тщательная разработка теста может ограничить это время не более чем дюжиной секунд. Поскольку в вашем приложении не должно быть много мест, требующих кода координации потоков, это должно быть приемлемо для вашего набора тестов.
Наконец, отслеживайте количество ошибок, обнаруженных вашим тестом. Если ваш тест покрывает 80% кода, можно ожидать, что он поймает около 80% ваших ошибок. Если ваш тест хорошо спроектирован, но не обнаружил ошибок, есть разумный шанс, что у вас не будет дополнительных ошибок, которые будут отображаться только в рабочей среде. Если тест обнаружит одну или две ошибки, вам все равно может повезти. Помимо этого, и вы можете рассмотреть возможность тщательного анализа или даже полного переписывания кода обработки потоков, поскольку вполне вероятно, что код все еще содержит скрытые ошибки, которые будет очень трудно найти, пока код не будет запущен, и очень трудно исправить тогда.