Я стикався з цим питанням кілька разів за останні роки, коли писав код обробки потоків для кількох проектів. Я надаю пізню відповідь, оскільки більшість інших відповідей, надаючи альтернативи, насправді не відповідають на питання про тестування. Моя відповідь адресована у випадках, коли немає альтернативи багатопотоковому коду; Я розглядаю питання дизайну коду для повноти, але також обговорюю тестування одиниць.
Написання перевіреного багатопотокового коду
Перше, що потрібно зробити - це відокремити свій код обробки обробної нитки від усього коду, який здійснює фактичну обробку даних. Таким чином, обробка даних може бути перевірена як однопотоковий код, і єдине, що багатопотоковий код робить - це координувати нитки.
Друге, що слід пам’ятати, це те, що помилки в багатопотоковому коді є імовірнісними; помилки, які проявляються найменш часто - це клопи, які проникнуть у виробництво, їх буде важко відтворити навіть у виробництві, і, таким чином, викличуть найбільші проблеми. З цієї причини стандартний підхід до кодування швидкого запису коду та його налагодження, поки він не працює, є поганою ідеєю для багатопотокового коду; це призведе до коду, де виправлені легкі помилки, а небезпечні помилки все ще є.
Натомість, коли ви пишете багатопотоковий код, ви повинні писати код із таким ставленням, що ви збираєтесь у першу чергу уникати написання помилок. Якщо ви правильно видалили код обробки даних, код обробки потоків повинен бути досить малим - бажано, декількома рядками, в гіршому випадку - декількома десятками рядків - щоб у вас був шанс написати це, не написавши помилку, і, звичайно, не написавши багато помилок , якщо ви розумієте різьблення, не витрачайте час і будьте обережні.
Написання одиничних тестів для багатопотокового коду
Після того, як багатопотоковий код буде написаний максимально ретельно, все одно варто написати тести для цього коду. Основна мета тестів - це не стільки тестування на сильно залежні від строкових помилок станів гонки - неможливо повторно перевірити такі умови перегонів - а скоріше перевірити, що ваша стратегія блокування для запобігання таких помилок дозволяє взаємодіяти декілька потоків за призначенням .
Щоб правильно перевірити правильну поведінку блокування, тест повинен запустити кілька потоків. Щоб зробити тест повторюваним, ми хочемо, щоб взаємодії між потоками відбувалися в передбачуваному порядку. Ми не хочемо зовнішньо синхронізувати потоки в тесті, тому що це маскує помилки, які можуть статися у виробництві, де потоки не синхронізовані зовні. Це залишає використання затримок синхронізації для синхронізації потоків, що є технікою, яку я успішно використовував, коли мені доводилося писати тести багатопотокового коду.
Якщо затримки занадто короткі, то тест стає крихким, оскільки незначні різниці в часі - скажімо, між різними машинами, на яких можна проводити тести - можуть спричинити вимкнення часу і тест. Те, що я зазвичай робив, - це починати із затримок, які спричиняють збої тесту, збільшуйте затримки, щоб тест надійно проходив на моїй розроблювальній машині, а потім подвоюйте затримки поза цим, тому тест має хороші шанси пройти на інших машинах. Це означає, що тест займе макроскопічну кількість часу, хоча, на мій досвід, ретельна тестова конструкція може обмежити цей час не більше десятка секунд. Оскільки у вашій програмі не повинно бути дуже багато місць, які вимагають код координації потоків, це повинно бути прийнятним для вашого тестового набору.
Нарешті, слідкуйте за кількістю помилок, виявлених вашим тестом. Якщо ваш тест має 80% кодового покриття, можна очікувати, що він зачепить близько 80% ваших помилок. Якщо ваш тест добре розроблений, але не виявлено помилок, є обґрунтований шанс, що у вас немає додаткових помилок, які з’являться лише у виробництві. Якщо тест виявить одну або дві помилки, можливо, вам все-таки пощастить. Крім цього, і ви можете розглянути ретельний огляд або навіть повне перезапис вашого коду обробки потоків, оскільки, ймовірно, код все ще містить приховані помилки, які буде дуже важко знайти, поки код не з'явиться у виробництві, і дуже важко виправити тоді.