Как мне выполнить модульный тест многопоточного кода?

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

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

15.08.2008 11:44:33
Я думал о том, чтобы опубликовать вопрос по точно такой же проблеме. Хотя Уилл делает много хороших замечаний ниже, я думаю, что мы можем добиться большего. Я согласен, что нет единого «подхода» к чистому решению этого вопроса. Тем не менее, «тестирование как можно лучше» устанавливает планку очень низкой. Я вернусь с моими выводами.
Zach Burlingame 24.09.2008 19:10:05
В Java: пакет java.util.concurrent содержит несколько плохо известных классов, которые могут помочь в написании детерминированных тестов JUnit. Посмотрите - CountDownLatch - Семафор - Exchanger
Synox 20.01.2010 11:25:07
Можете ли вы дать ссылку на ваш предыдущий вопрос, связанный с модульным тестированием, пожалуйста?
Andrew Grimm 6.05.2010 03:55:04
@
jkp 6.05.2010 06:58:31
Я думаю, что важно отметить, что этому вопросу уже 8 лет, а библиотеки приложений за это время прошли довольно долгий путь. В «современную эпоху» (2016) многопоточная разработка в основном проявляется во встроенных системах. Но если вы работаете с настольным или телефонным приложением, сначала изучите альтернативные варианты. Прикладные среды, такие как .NET, теперь включают инструменты для управления или значительного упрощения, вероятно, 90% распространенных многопоточных сценариев. (asnync / await, PLinq, IObservable, TPL ...). Многопоточный код сложен. Если вы не изобретаете колесо заново, вам не нужно его повторно тестировать.
Paul Williams 11.05.2016 15:06:25
25 ОТВЕТОВ

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

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

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

2
19.06.2016 22:22:52

Трудный действительно! В моих (C ++) модульных тестах я разбил это на несколько категорий в соответствии с используемым шаблоном параллелизма:

  1. Модульные тесты для классов, которые работают в одном потоке и не поддерживают потоки - просто, тестируйте как обычно.

  2. Модульные тесты для объектов Monitor (тех, которые выполняют синхронизированные методы в потоке контроля вызывающих), которые предоставляют синхронизированный общедоступный API-интерфейс - создание нескольких фиктивных потоков, которые осуществляют API. Построить сценарии, которые осуществляют внутренние условия пассивного объекта. Включите один более продолжительный тест, который в основном выбивает его из нескольких потоков в течение длительного периода времени. Я знаю, что это ненаучно, но это вселяет уверенность.

  3. Модульные тесты для активных объектов (те, которые инкапсулируют свой собственный поток или потоки управления) - аналогично # 2 выше с вариациями в зависимости от дизайна класса. Публичный API может быть блокирующим или неблокирующим, вызывающие абоненты могут получать фьючерсы, данные могут поступать в очереди или должны быть исключены из очереди. Здесь возможно множество комбинаций; белая коробка прочь По-прежнему требуется несколько фиктивных потоков для выполнения вызовов тестируемого объекта.

Как в сторону:

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

65
15.08.2008 13:38:18

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

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

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

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

Вероятно, лучший способ проверить код на наличие потоков - это статический анализ кода. Если ваш многопоточный код не следует конечному набору потоковых шаблонов, возможно, у вас проблема. Я считаю, что Code Analysis в VS действительно содержит некоторые знания о многопоточности, но, вероятно, не так много.

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

241
23.06.2019 21:46:18
Анализ кода хорош, если вы имеете дело с языком / структурой, которая позволяет это. Например: Findbugs найдет очень простые и легкие проблемы совместного использования со статическими переменными. Чего он не может найти, так это одноэлементных шаблонов проектирования, он предполагает, что все объекты могут быть созданы несколько раз. Этот плагин крайне не подходит для таких фреймворков, как Spring.
Zombies 27.08.2013 23:38:38
ThreadSafe ( contemplateltd.com/threadsafe , бесплатные пробные версии доступны) - это статический анализатор, специально ориентированный на параллелизм Java. Он гораздо лучше, чем FindBugs, обнаруживает проблемы с параллелизмом. См. Infoq.com/articles/… для примеров ошибок параллелизма, которые он обнаруживает в приложениях с открытым исходным кодом, включая Apache JMeter и K9Mail. (Раскрытие информации: ThreadSafe - коммерческий инструмент, и я являюсь соучредителем Contemplate, компании, которая его производит.)
dsannella 16.06.2014 21:37:32
на самом деле есть лекарство: активные объекты. drdobbs.com/parallel/prefer-using-active-objects-instead-of-n/…
Dill 8.01.2015 19:49:57
Хотя это хороший совет, я все еще спрашиваю: «Как проверить те минимальные области, где требуется несколько потоков?»
Bryan Rayner 27.05.2016 20:29:23
«Если это слишком сложно для тестирования, вы делаете это неправильно» - мы все должны погрузиться в устаревший код, который мы не писали. Как это наблюдение помогает кому-то точно?
Ronna 12.09.2016 11:49:41

У Пита Гудлиффа есть серия статей о модульном тестировании многопоточного кода.

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

6
15.08.2008 20:59:45
Я прочитал две статьи, опубликованные до сих пор, и я не нашел их очень полезными. Он просто говорит о трудностях, не давая много конкретных советов. Возможно будущие статьи улучшатся.
Don Kirkby 18.09.2008 20:19:42

У меня также были серьезные проблемы с тестированием многопоточного кода. Тогда я нашел действительно классное решение в «Тестовых шаблонах xUnit» Джерарда Месароса. Образец, который он описывает, называется скромным объектом .

В основном он описывает, как вы можете извлечь логику в отдельный, легко тестируемый компонент, который отделен от его среды. После проверки этой логики вы можете проверить сложное поведение (многопоточность, асинхронное выполнение и т. Д.)

22
20.08.2008 12:45:46

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

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

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

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

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

6
22.02.2017 20:24:49

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

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

6
17.09.2008 16:35:12

Я сделал много этого, и да, это отстой.

Несколько советов:

  • GroboUtils для запуска нескольких тестовых потоков
  • alphaWorks ConTest для классов инструментов, чтобы чередование чередования варьировалось между итерациями
  • Создайте throwableполе и зарегистрируйте его tearDown(см. Листинг 1). Если вы поймали плохое исключение в другом потоке, просто назначьте его для throwable.
  • Я создал класс utils в листинге 2 и нашел его бесценным, особенно waitForVerify и waitForCondition, которые значительно повысят производительность ваших тестов.
  • Хорошо используйте AtomicBooleanв своих тестах. Это потокобезопасно, и вам часто понадобится конечный ссылочный тип для хранения значений из классов обратного вызова и тому подобное. Смотрите пример в листинге 3.
  • Всегда проверяйте тайм-аут вашего теста (например, @Test(timeout=60*1000)), так как тесты параллелизма могут иногда зависать вечно, если они не работают.

Листинг 1:

@After
public void tearDown() {
    if ( throwable != null )
        throw throwable;
}

Листинг 2:

import static org.junit.Assert.fail;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Random;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.time.StopWatch;
import org.easymock.EasyMock;
import org.easymock.classextension.internal.ClassExtensionHelper;
import static org.easymock.classextension.EasyMock.*;

import ca.digitalrapids.io.DRFileUtils;

/**
 * Various utilities for testing
 */
public abstract class DRTestUtils
{
    static private Random random = new Random();

/** Calls {@link #waitForCondition(Integer, Integer, Predicate, String)} with
 * default max wait and check period values.
 */
static public void waitForCondition(Predicate predicate, String errorMessage) 
    throws Throwable
{
    waitForCondition(null, null, predicate, errorMessage);
}

/** Blocks until a condition is true, throwing an {@link AssertionError} if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param errorMessage message use in the {@link AssertionError}
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, String errorMessage) throws Throwable 
{
    waitForCondition(maxWait_ms, checkPeriod_ms, predicate, new Closure() {
        public void execute(Object errorMessage)
        {
            fail((String)errorMessage);
        }
    }, errorMessage);
}

/** Blocks until a condition is true, running a closure if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param closure closure to run
 * @param argument argument for closure
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, Closure closure, Object argument) throws Throwable 
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    if ( checkPeriod_ms == null )
        checkPeriod_ms = 100;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    while ( !predicate.evaluate(null) ) {
        Thread.sleep(checkPeriod_ms);
        if ( stopWatch.getTime() > maxWait_ms ) {
            closure.execute(argument);
        }
    }
}

/** Calls {@link #waitForVerify(Integer, Object)} with <code>null</code>
 * for {@code maxWait_ms}
 */
static public void waitForVerify(Object easyMockProxy)
    throws Throwable
{
    waitForVerify(null, easyMockProxy);
}

/** Repeatedly calls {@link EasyMock#verify(Object[])} until it succeeds, or a
 * max wait time has elapsed.
 * @param maxWait_ms Max wait time. <code>null</code> defaults to 30s.
 * @param easyMockProxy Proxy to call verify on
 * @throws Throwable
 */
static public void waitForVerify(Integer maxWait_ms, Object easyMockProxy)
    throws Throwable
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for(;;) {
        try
        {
            verify(easyMockProxy);
            break;
        }
        catch (AssertionError e)
        {
            if ( stopWatch.getTime() > maxWait_ms )
                throw e;
            Thread.sleep(100);
        }
    }
}

/** Returns a path to a directory in the temp dir with the name of the given
 * class. This is useful for temporary test files.
 * @param aClass test class for which to create dir
 * @return the path
 */
static public String getTestDirPathForTestClass(Object object) 
{

    String filename = object instanceof Class ? 
        ((Class)object).getName() :
        object.getClass().getName();
    return DRFileUtils.getTempDir() + File.separator + 
        filename;
}

static public byte[] createRandomByteArray(int bytesLength)
{
    byte[] sourceBytes = new byte[bytesLength];
    random.nextBytes(sourceBytes);
    return sourceBytes;
}

/** Returns <code>true</code> if the given object is an EasyMock mock object 
 */
static public boolean isEasyMockMock(Object object) {
    try {
        InvocationHandler invocationHandler = Proxy
                .getInvocationHandler(object);
        return invocationHandler.getClass().getName().contains("easymock");
    } catch (IllegalArgumentException e) {
        return false;
    }
}
}

Листинг 3:

@Test
public void testSomething() {
    final AtomicBoolean called = new AtomicBoolean(false);
    subject.setCallback(new SomeCallback() {
        public void callback(Object arg) {
            // check arg here
            called.set(true);
        }
    });
    subject.run();
    assertTrue(called.get());
}
13
23.12.2019 01:15:01
Тайм-аут - это хорошая идея, но если время теста истекло, любые последующие результаты в этом прогоне являются подозрительными. В тесте по тайм-ауту могут остаться запущенные потоки, которые могут вас испортить.
Don Kirkby 20.10.2010 05:59:06

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

Quote:

Нечеткое тестирование или фаззинг - это метод тестирования программного обеспечения, который предоставляет случайные данные («фазз») на входы программы. В случае сбоя программы (например, из-за сбоя или из-за сбоя утверждений встроенного кода) можно заметить дефекты. Большим преимуществом нечеткого тестирования является то, что дизайн теста чрезвычайно прост и не содержит предвзятых мнений о поведении системы.

...

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

...

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

14
4.12.2014 00:04:17

Это было время, когда этот вопрос был опубликован, но до сих пор не ответили ...

Ответ kleolb02 хороший. Я постараюсь вдаваться в подробности.

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

Это идея из книги Джерарда Месардоса « Тестовые шаблоны xUnit », которая называется «Смиренный объект» (стр. 695): вы должны отделить основной логический код и все, что пахнет асинхронным кодом друг от друга. Это привело бы к классу для основной логики, который работает синхронно .

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

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

Все, что выше этого (тестирование взаимодействия между классами), является тестированием компонентов. Также в этом случае вы должны иметь абсолютный контроль над временем, если вы придерживаетесь шаблона «Humble Object».

95
23.05.2017 12:10:43
Но иногда, если потоки хорошо взаимодействуют друг с другом, тоже нужно что-то тестировать, верно? Я определенно отделю основную логику от асинхронной части после прочтения вашего ответа. Но я все еще собираюсь проверить логику через асинхронные интерфейсы с обратным вызовом работа над всеми потоками.
CopperCash 6.04.2015 07:17:50
Как насчет многопроцессорных систем?
Technophile 15.04.2015 15:43:51

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

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

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

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

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

2
22.03.2009 22:45:10

Я выполняю модульные тесты резьбовых компонентов так же, как и любые юнит-тесты, то есть с инверсией систем управления и изоляции. Я разрабатываю на .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 и т. Д. И т. Д.

5
26.02.2010 23:38:20

Есть несколько хороших инструментов вокруг. Вот краткое изложение некоторых из Java.

Некоторые хорошие инструменты статического анализа включают FindBugs (дает несколько полезных советов), JLint , Java Pathfinder (JPF & JPF2) и Bogor .

MultithreadedTC - это довольно хороший инструмент для динамического анализа (встроенный в JUnit), в котором вы должны настроить свои собственные тестовые случаи.

ConTest от IBM Research - это интересно. Он обрабатывает ваш код, вставляя все виды изменяющих потоки поведения (например, сон и выход), чтобы попытаться случайно обнаружить ошибки.

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

MultithreadedTC, вероятно, является наиболее распространенным, но некоторые из перечисленных выше инструментов статического анализа, безусловно, заслуживают внимания.

20
8.07.2010 12:23:19

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

await().untilCall( to(myService).myMethod(), greaterThan(3) );

или

await().atMost(5,SECONDS).until(fieldIn(myObject).ofType(int.class), equalTo(1));

Он также имеет поддержку Scala и Groovy.

await until { something() > 4 } // Scala example
14
19.11.2010 17:26:44
Ожидание блестящее - именно то, что я искал!
Forge_7 17.06.2016 13:37:14

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

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

Он смещен в сторону Java, но имеет разумное резюме вариантов.

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

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

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

Удачи!

5
23.05.2017 12:10:43
Вы также должны упомянуть tempus-fugitздесь свою библиотеку, которая helps write and test concurrent code;)
Idolon 17.09.2013 09:13:06

(если возможно) не используйте потоки, используйте актеры / активные объекты. Легко проверить.

-3
4.07.2013 08:11:57
@OMTheEternity может быть, но это все еще лучший ответ IMO.
Dill 11.03.2015 19:16:12

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

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

3
11.09.2013 16:12:18
Пожалуйста, объясните подходы здесь, внешние ссылки могут быть мертвыми в будущем.
Uooo 1.08.2013 08:10:37

Вы можете использовать EasyMock.makeThreadSafe, чтобы сделать тестовый экземпляр безопасным

-5
13.01.2014 11:48:15
Это вовсе не возможный способ тестирования многопоточного кода. Проблема не в том, что тестовый код запускается многопоточным, а в том, что вы тестируете код, который обычно выполняется многопоточным. И вы не можете синхронизировать все, потому что тогда вы на самом деле больше не тестируете гонки данных.
bennidi 18.09.2014 10:19:09

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

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

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

4
24.05.2014 22:09:42

Как уже говорилось, тестирование МТ-кода на правильность является довольно сложной задачей. В итоге все сводится к тому, чтобы в вашем коде не было неправильно синхронизированных гонок данных. Проблема в том, что существует бесконечно много возможностей выполнения потоков (чередований), над которыми у вас нет большого контроля ( хотя обязательно прочитайте эту статью). В простых сценариях можно было бы реально доказать правильность, рассуждая, но обычно это не так. Особенно, если вы хотите избежать / минимизировать синхронизацию и не использовать самый очевидный / самый простой вариант синхронизации.

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

Кстати, я думаю, что этот аспект тестирования кода MT здесь не упоминался: выявляйте инварианты кода, которые вы можете проверить случайным образом. К сожалению, найти эти инварианты тоже довольно сложно. Кроме того, они могут не удерживаться все время во время выполнения, поэтому вы должны найти / применить точки выполнения, где вы можете ожидать, что они будут истинными. Приведение выполнения кода в такое состояние также является сложной проблемой (и может привести к проблемам с параллелизмом. Вот, черт возьми, это сложно!

Некоторые интересные ссылки для чтения:

  • Детерминированное чередование : структура, которая позволяет принудительно выполнять чередование определенных потоков, а затем проверять наличие инвариантов.
  • jMock Blitzer : синхронизация стресс-тестов
  • assertConcurrent : JUnit версия стресс-тестирования синхронизации
  • Тестирование параллельного кода : краткий обзор двух основных методов грубой силы (стресс-тест) или детерминистического (переход к инвариантам)
12
18.09.2014 12:31:08
Автор ссылается на рандомизацию в тестировании. Это может быть QuickCheck , который был перенесен на многие языки. Вы можете посмотреть доклад о таком тестировании для параллельной системы здесь
Max 24.08.2015 11:11:40

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

Написание тестируемого многопоточного кода

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

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

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

Написание модульных тестов для многопоточного кода

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

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

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

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

50
15.04.2017 20:19:03
Тестирование может выявить только наличие ошибок, а не их отсутствие. Первоначальный вопрос касается проблемы с двумя потоками, в этом случае может быть возможным исчерпывающее тестирование, но часто это не так. Для чего-либо, кроме простейших сценариев, вам, возможно, придется прикусить пулю и использовать формальные методы - но не пропустите юнит-тесты! Написание правильного многопоточного кода сложно, во-первых, но столь же сложная проблема - защитить его от регрессии в будущем.
Paul Williams 11.05.2016 13:55:29
Удивительное резюме одного из наименее понятных способов. Ваш ответ - удар по реальной сегрегации, которую люди обычно игнорируют.
prash 15.12.2016 09:24:10
Дюжина секунд - это довольно много, даже если у вас есть только несколько сотен тестов этой длины ...
Toby Speight 20.06.2018 15:41:25
@TobySpeight Тесты длинные по сравнению с обычными юнит-тестами. Я обнаружил, что полдюжины тестов более чем достаточно, если многопоточный код правильно спроектирован так, чтобы быть как можно более простым - хотя необходимость в нескольких сотнях многопоточных тестов почти наверняка указала бы на слишком сложную организацию потоков.
Warren Dew 21.06.2018 17:33:30
Это хороший аргумент для того, чтобы сохранить логику вашего потока как можно более отделимой от функциональности (я знаю, гораздо легче сказать, чем сделать). И, если возможно, разбить набор тестов на наборы «каждое изменение» и «предварительная фиксация» (чтобы ваши поминутные тесты не слишком сильно влияли).
Toby Speight 22.06.2018 07:11:22

Для кода J2E я использовал SilkPerformer, LoadRunner и JMeter для параллельного тестирования потоков. Все они делают одно и то же. По сути, они предоставляют вам относительно простой интерфейс для администрирования своей версии прокси-сервера, необходимый для анализа потока данных TCP / IP и моделирования одновременного выполнения запросов несколькими пользователями на сервер приложений. Прокси-сервер может дать вам возможность выполнять такие вещи, как анализ выполненных запросов, представляя всю страницу и URL-адрес, отправленный на сервер, а также ответ от сервера после обработки запроса.

Вы можете найти некоторые ошибки в небезопасном режиме http, где вы можете по крайней мере проанализировать отправляемые данные формы и систематически изменять их для каждого пользователя. Но настоящие тесты - это когда вы работаете в https (Secure Socket Layers). Затем вам также придется бороться с систематическим изменением данных сеанса и файлов cookie, что может быть немного более запутанным.

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

В конце концов, вам или кому-то, вероятно, придется сгибаться и анализировать код на наличие ошибок, подобных тому, который я только что упомянул. И наиболее полезными являются открытые дискуссии между департаментами, подобные тем, которые произошли, когда мы раскрыли описанную выше проблему. Но эти инструменты являются лучшим решением для тестирования многопоточного кода. JMeter является открытым исходным кодом. SilkPerformer и LoadRunner являются собственностью. Если вы действительно хотите знать, является ли ваше приложение потокобезопасным, именно так поступают большие мальчики. Я сделал это для очень крупных компаний профессионально, так что я не думаю. Я говорю из личного опыта.

Предостережение: для понимания этих инструментов требуется некоторое время. Это не будет вопросом простой установки программного обеспечения и запуска графического интерфейса, если вы уже не знакомы с многопоточным программированием. Я попытался определить 3 критические категории областей для понимания (формы, данные сеансов и файлы cookie), надеясь, что, по крайней мере, начиная с понимания этих тем, вы сможете сосредоточиться на быстрых результатах, а не на том, чтобы читать через Вся документация.

0
25.09.2017 23:17:02

Если вы тестируете простой новый Thread (runnable) .run () Вы можете смоделировать Thread для последовательного запуска runnable

Например, если код тестируемого объекта вызывает новый поток, подобный этому

Class TestedClass {
    public void doAsychOp() {
       new Thread(new myRunnable()).start();
    }
}

Затем может помочь насмешка над новыми потоками и последовательный запуск аргумента runnable

@Mock
private Thread threadMock;

@Test
public void myTest() throws Exception {
    PowerMockito.mockStatic(Thread.class);
    //when new thread is created execute runnable immediately 
    PowerMockito.whenNew(Thread.class).withAnyArguments().then(new Answer<Thread>() {
        @Override
        public Thread answer(InvocationOnMock invocation) throws Throwable {
            // immediately run the runnable
            Runnable runnable = invocation.getArgumentAt(0, Runnable.class);
            if(runnable != null) {
                runnable.run();
            }
            return threadMock;//return a mock so Thread.start() will do nothing         
        }
    }); 
    TestedClass testcls = new TestedClass()
    testcls.doAsychOp(); //will invoke myRunnable.run in current thread
    //.... check expected 
}
-1
27.12.2018 15:23:22

Параллелизм - это сложное взаимодействие между моделью памяти, оборудованием, кэшем и нашим кодом. В случае Java, по крайней мере, такие тесты были частично рассмотрены в основном jcstress . Известно, что создатели этой библиотеки являются авторами многих функций параллелизма JVM, GC и Java.

Но даже эта библиотека нуждается в хороших знаниях спецификации Java Memory Model, чтобы мы точно знали, что мы тестируем. Но я думаю, что основное внимание в этом усилии уделяется миробенчмаркам. Не огромные бизнес-приложения.

0
20.06.2018 08:15:26

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

0
21.03.2019 09:24:46