Как мне выполнить модульное тестирование многопоточного кода? - PullRequest
643 голосов
/ 15 августа 2008

До сих пор я избегал кошмара, в котором тестируется многопоточный код, поскольку он кажется слишком большим минным полем. Я хотел бы спросить, как люди занимались тестированием кода, который опирается на потоки для успешного выполнения, или просто как люди занимались тестированием таких проблем, которые проявляются только тогда, когда два потока взаимодействуют определенным образом?

Это кажется действительно ключевой проблемой для программистов сегодня, было бы полезно объединить наши знания по этому вопросу.

Ответы [ 25 ]

6 голосов
/ 16 августа 2008

Пит Гудлифф имеет серию на модульном тестировании резьбового кода.

Это сложно. Я выбрал более простой путь и постарался не допустить, чтобы многопоточный код был абстрагирован от реального теста. Пит упоминает, что то, как я это делаю, неверно, но я либо получил правильное разделение, либо мне просто повезло.

6 голосов
/ 17 сентября 2008

Для Java ознакомьтесь с главой 12 JCIP . Есть несколько конкретных примеров написания детерминированных многопоточных модульных тестов, чтобы хотя бы проверить правильность и инварианты параллельного кода.

«Доказательство» безопасности потоков с помощью модульных тестов намного сложнее. Я считаю, что это лучше подходит для автоматизированного интеграционного тестирования на различных платформах / конфигурациях.

5 голосов
/ 06 января 2011

Посмотрите на мой соответствующий ответ на

Разработка класса теста для пользовательского барьера

Он смещен в сторону Java, но имеет разумную сводку опций.

Таким образом, (IMO) это не какая-то необычная структура, которая обеспечит правильность, а то, как вы разрабатываете многопоточный код. Разделение проблем (параллелизм и функциональность) имеет огромное значение для повышения доверия. Растущее объектно-ориентированное программное обеспечение, основанное на тестах объясняет некоторые варианты лучше, чем я.

Статический анализ и формальные методы (см., Параллельность: модели состояний и программы на Java ) - вариант, но я нашел, что они имеют ограниченное применение в коммерческой разработке.

Не забывайте, что любые тесты на нагрузку / выдержку редко гарантируют освещение проблем.

Удачи!

5 голосов
/ 08 сентября 2008

Мне нравится писать два или более тестовых метода для выполнения в параллельных потоках, и каждый из них делает вызовы в тестируемый объект. Я использовал вызовы Sleep () для координации порядка вызовов из разных потоков, но это не совсем надежно. Это также намного медленнее, потому что вы должны спать достаточно долго, чтобы время работало.

Я нашел многопоточную библиотеку Java из той же группы, которая написала FindBugs. Это позволяет вам определять порядок событий без использования Sleep (), и это надежно. Я еще не пробовал.

Самым большим ограничением этого подхода является то, что он позволяет только тестировать сценарии, которые, как вы подозреваете, вызовут проблемы. Как уже говорили другие, вам действительно нужно выделить многопоточный код в небольшое количество простых классов, чтобы иметь надежду на их тщательное тестирование.

После того, как вы тщательно протестировали сценарии, которые, как вы ожидаете, вызовут проблемы, ненаучный тест, который на время бросает кучу одновременных запросов в класс, является хорошим способом поиска неожиданных проблем.

Обновление: Я немного поиграл с многопоточной Java-библиотекой TC, и она хорошо работает. Я также перенес некоторые из его функций в версию .NET, которую я называю TickingTest .

5 голосов
/ 27 февраля 2010

Я выполняю модульные тесты резьбовых компонентов так же, как и любые юнит-тесты, то есть с инверсией систем управления и изоляции. Я разрабатываю на .Net-арене, и из коробки очень трудно (среди прочего) очень трудно (даже, скажем, почти невозможно) организовать многопоточность.

Поэтому я написал обертки, которые выглядят примерно так (упрощенно):

public interface IThread
{
    void Start();
    ...
}

public class ThreadWrapper : IThread
{
    private readonly Thread _thread;

    public ThreadWrapper(ThreadStart threadStart)
    {
        _thread = new Thread(threadStart);
    }

    public Start()
    {
        _thread.Start();
    }
}

public interface IThreadingManager
{
    IThread CreateThread(ThreadStart threadStart);
}

public class ThreadingManager : IThreadingManager
{
    public IThread CreateThread(ThreadStart threadStart)
    {
         return new ThreadWrapper(threadStart)
    }
}

Оттуда я могу легко внедрить IThreadingManager в мои компоненты и использовать мою изолированную инфраструктуру выбора, чтобы заставить поток вести себя так, как я ожидаю во время теста.

Это до сих пор отлично работало для меня, и я использую тот же подход для пула потоков, вещей в System.Environment, Sleep и т. Д. И т. Д.

4 голосов
/ 25 мая 2014

Я только недавно обнаружил (для Java) инструмент под названием Threadsafe. Это инструмент статического анализа, очень похожий на findbugs, но специально для выявления проблем с многопоточностью. Это не замена для тестирования, но я могу рекомендовать его как часть написания надежной многопоточной Java.

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

Если вы пишете многопоточную Java , попробуйте.

3 голосов
/ 31 июля 2013

Следующая статья предлагает 2 решения. Обертывание семафора (CountDownLatch) и добавляет функциональность, такую ​​как вывод данных из внутреннего потока. Другим способом достижения этой цели является использование пула потоков (см. Разделы, представляющие интерес).

Sprinkler - расширенный объект синхронизации

1 голос
/ 23 марта 2009

Большую часть прошлой недели я провел в университетской библиотеке, изучая отладку параллельного кода. Центральная проблема заключается в том, что параллельный код является недетерминированным. Как правило, академическая отладка попадает в один из трех лагерей здесь:

  1. Event-след / воспроизведения. Для этого требуется монитор событий, а затем просмотр отправленных событий. В рамках UT это будет включать ручную отправку событий как часть теста, а затем выполнение посмертных проверок.
  2. Scriptable. Здесь вы взаимодействуете с работающим кодом с помощью набора триггеров. "На x> foo, baz ()". Это можно интерпретировать как структуру UT, где у вас есть система времени выполнения, запускающая данный тест при определенном условии.
  3. Интерактивный. Это очевидно не будет работать в ситуации автоматического тестирования. ;)

Теперь, как заметили вышеупомянутые комментаторы, вы можете превратить вашу параллельную систему в более детерминированное состояние. Однако, если вы не сделаете это правильно, вы снова вернетесь к созданию последовательной системы.

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

Удачи, и продолжайте работать над проблемой.

1 голос
/ 15 августа 2008

У меня была неудачная задача тестирования многопоточного кода, и это определенно самые сложные тесты, которые я когда-либо писал.

При написании тестов я использовал комбинацию делегатов и событий. В основном это все об использовании PropertyNotifyChanged событий с WaitCallback или каким-то ConditionalWaiter, который опрашивает.

Я не уверен, что это был лучший подход, но он сработал для меня.

0 голосов
/ 21 марта 2019

Предполагается, что под "многопоточным" кодом подразумевается что-то, что

  • с изменяемым состоянием и изменчивостью
  • И доступ / изменение нескольких потоков одновременно

Другими словами, мы говорим о тестировании настраиваемого многопоточного класса / метода / модуля с безопасным потоком - который в наше время должен быть очень редким зверем.

Поскольку этот зверь встречается редко, прежде всего нам нужно убедиться, что есть все веские основания для его написания.

Шаг 1. Рассмотрите возможность изменения состояния в том же контексте синхронизации.

Сегодня легко написать совместимый и асинхронный код с возможностью компоновки, в котором ввод-вывод или другие медленные операции выгружаются в фоновый режим, но общее состояние обновляется и запрашивается в одном контексте синхронизации. например задачи async / await и Rx в .NET и т. д. - все они тестируемые по конструкции, «реальные» задачи и планировщики могут быть заменены, чтобы сделать тестирование детерминированным (однако это выходит за рамки вопроса).

Это может звучать очень ограниченно, но этот подход работает на удивление хорошо. Можно писать целые приложения в этом стиле без необходимости делать какие-либо потоки безопасными (я так делаю).

Шаг 2. Если манипулирование общим состоянием в едином контексте синхронизации абсолютно невозможно.

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

Примечание: если код большой / охватывает несколько классов и требует многопоточных манипуляций с состоянием, то очень велика вероятность того, что дизайн не будет хорошим, пересмотрите Шаг 1

Шаг 3. Если этот шаг достигнут, нам нужно протестировать наш собственный ориентированный на состояние потокобезопасный класс / метод / модуль .

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

Если бы мне действительно нужно было протестировать такой код (, наконец, фактический ответ ), то я бы попробовал пару вещей ниже

  1. Недетерминированное стресс-тестирование. например запустить 100 потоков одновременно и проверить, что конечный результат соответствует. Это более типично для высокоуровневого / интеграционного тестирования многопользовательских сценариев, но также может использоваться на уровне устройства.

  2. Предоставьте некоторые тестовые «ловушки», где тест может внедрить некоторый код, чтобы помочь создать детерминированные сценарии, в которых один поток должен выполнить операцию раньше другого. Как бы ужасно это ни было, я не могу придумать ничего лучшего.

  3. Тестирование с задержкой для запуска потоков и выполнения операций в определенном порядке. Строго говоря, такие тесты также являются недетерминированными (существует вероятность того, что система GC может заморозить / остановить мир, которая может исказить иные организованные задержки), также она уродлива, но позволяет избежать хуков.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...