Как мне использовать unit test код с резьбой?

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

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

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

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

Это идея Джерарда Мезардоса о книге " xUnit Test Patterns " и называется "Humble Object" (стр. 695): вам нужно отделить основной логический код и все, что пахнет асинхронным кодом друг от друга. Это приведет к классу для основной логики, который работает синхронно.

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

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

Все, что выше этого (тестовое взаимодействие между классами), - это компонентные тесты. Также в этом случае вы должны иметь полный контроль над временем, если придерживаться шаблона "Хамбл-объект".

Ответ 3

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

  • Единичные тесты для классов, которые работают в одном потоке и не знакомы с потоком - легко, как обычно.

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

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

В стороне:

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

Ответ 4

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

Запись многопоточного кода с возможностью тестирования

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

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

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

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

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

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

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

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

Ответ 5

У меня также были серьезные проблемы с тестированием многопоточного кода. Затем я нашел действительно классное решение в "XUnit Test Patterns" Жерара Мезароса. Шаблон, который он описывает, называется Humble object.

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

Ответ 6

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

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

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

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

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

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

Ответ 7

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

Цитата:

Испытание Fuzz или fuzzing - это метод тестирования программного обеспечения, который предоставляет случайные данные ("fuzz") для входа в программу. Если программа выйдет из строя (например, сбой или отказ от встроенных кодовых утверждений), можно отметить дефекты. Большим преимуществом тестирования fuzz является то, что тестовый дизайн чрезвычайно прост и свободен от предвзятых представлений о поведении системы.

...

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

...

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

Ответ 8

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

Ответ 9

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

Некоторые советы:

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

Листинг 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());
}

Ответ 10

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

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

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

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

Ответ 11

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

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

Ответ 12

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

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

Ответ 13

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

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

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

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

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

Ответ 14

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

Ответ 15

Взгляните на мой ответ на

Проектирование класса Test для настраиваемого барьера

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

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

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

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

Удачи!

Ответ 16

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

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

Если вы пишете многопоточную Java сделайте снимок.

Ответ 17

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

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

Ответ 18

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

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

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

Ответ 19

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

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

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

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

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

Ответ 20

В Java: пакет java.util.concurrent содержит некоторые известные классы, которые могут помочь в определении детерминированных JUnit-Tests.

Посмотрите

Ответ 21

У вас есть книга в книге Clean Code ГЛАВА 13 существует целая секция, посвященная тестированию многопоточного кода, а также concurrency в который может помочь вам разработать лучший многопоточный код.

Ответ 22

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

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

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

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

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

Ответ 23

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

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

Ответ 24

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 25

Если вы тестируете простой новый 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 
}

Ответ 26

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

Ответ 27

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