Как зайти в Даркнет?!
25th January, 01:11
5
0
Как в tkinter из поля ввода Entry получить значение в одну переменную и обновить строку кнопкой, затем получить ещё одно введённое значение и затем сложить их. Ниже пример кода
21st July, 19:00
893
0
Программа, которая создает фейковые сервера в поиске игровых серверов CS 1.6 Steam
21st March, 17:43
948
0
Очень долго работает Update запрос Oracle
27th January, 09:58
912
0
не могу запустить сервер на tomcat HTTP Status 404 – Not Found
21st January, 18:02
905
0
Где можно найти фрилансера для выполнения поступающих задач, на постоянной основе?
2nd December, 09:48
938
0
Разработка мобильной кроссплатформенной военной игры
16th July, 17:57
1724
0
период по дням
25th October, 10:44
3955
0
Пишу скрипты для BAS только на запросах
16th September, 02:42
3720
0
Некорректный скрипт для закрытия блока
14th April, 18:33
4613
0
прокидывать exception в блоках try-catch JAVA
11th March, 21:11
4381
0
Помогите пожалуйста решить задачи
24th November, 23:53
6086
0
Не понимаю почему не открывается детальное описание продукта
11th November, 11:51
4350
0
Нужно решить задачу по программированию на массивы
27th October, 18:01
4396
0
Метода Крамера С++
23rd October, 11:55
4309
0
помогите решить задачу на C++
22nd October, 17:31
4002
0
Помогите решить задачу на python с codeforces
22nd October, 11:11
4492
0
Python с нуля: полное руководство для начинающих
18th June, 13:58
2599
0
Как я должен тестировать модульный потоковый код?
До сих пор я избегал кошмара, который испытывает многопоточный код, поскольку он просто кажется слишком большим минным полем. Я хотел бы спросить, как люди начали тестировать код, который полагается на потоки для успешного выполнения, или просто как люди начали тестировать те виды проблем, которые появляются только тогда, когда два потока взаимодействуют определенным образом?
Это кажется действительно ключевой проблемой для программистов сегодня, было бы полезно объединить наши знания по этому имхо.
Послушай, это не так просто сделать. Я работаю над проектом, который по своей сути является многопоточным. События приходят из операционной системы, и я должен обрабатывать их одновременно.
Самый простой способ справиться с тестированием сложного многопоточного кода приложения заключается в следующем: если он слишком сложен для тестирования, вы делаете это неправильно. Если у вас есть один экземпляр, который имеет несколько потоков, действующих на него, и вы не можете проверить ситуации, когда эти потоки перешагивают друг через друга, то ваш дизайн должен быть переделан. Это и так просто, и так сложно, как это.
Существует много способов программирования многопоточности, которые позволяют избежать одновременного выполнения потоков через экземпляры. Самое простое-сделать все ваши объекты неизменными. Конечно, обычно это невозможно. Таким образом, вы должны определить те места в своем дизайне, где потоки взаимодействуют с одним и тем же экземпляром, и уменьшить количество этих мест. Таким образом, вы изолируете несколько классов, в которых действительно происходит многопоточность, что снижает общую сложность тестирования вашей системы.
Но вы должны понимать, что даже делая это, вы все равно не можете проверить каждую ситуацию, когда два потока наступают друг на друга. Для этого вам нужно будет запустить два потока одновременно в одном тесте, а затем точно контролировать, какие строки они выполняют в любой данный момент. Лучшее, что вы можете сделать, это смоделировать эту ситуацию. Но это может потребовать от вас специального кода для тестирования, и это в лучшем случае полшага к истинному решению.
Вероятно, лучший способ проверить код на наличие проблем с потоками-это статический анализ кода. Если ваш потоковый код не соответствует конечному набору потокобезопасных шаблонов, то у вас может возникнуть проблема. Я считаю, что анализ кода в VS действительно содержит некоторые знания о потоковой передаче, но, вероятно, не очень много.
Посмотрите, как обстоят дела в настоящее время (и, вероятно, будут стоять еще долго), лучший способ протестировать многопоточные приложения-это максимально снизить сложность потокового кода. Сведите к минимуму области взаимодействия потоков, протестируйте их как можно лучше и используйте анализ кода для выявления опасных областей.
Прошло уже некоторое время, когда этот вопрос был опубликован, но он до сих пор не получил ответа ...
ответ клеолб02-хороший ответ. Я постараюсь вдаваться в подробности.
Есть способ, который я практикую для кода C#. Для модульных тестов вы должны уметь программировать воспроизводимые тесты, что является самой большой проблемой в многопоточном коде. Поэтому мой ответ направлен на то, чтобы заставить асинхронный код работать в тестовом жгуте, который работает синхронно .
Это идея из книги Жерара Месардоса "xUnit тестовые шаблоны" и называется "Humble Object" (стр. 695): вы должны отделить основной логический код и все, что пахнет асинхронным кодом, друг от друга. Это приведет к классу для основной логики, которая работает синхронно .
Это дает вам возможность проверить основной логический код синхронным способом. У вас есть абсолютный контроль над временем вызовов, которые вы делаете по основной логике, и, таким образом, вы можете сделать воспроизводимые тесты. И это ваша выгода от разделения основной логики и асинхронной логики.
Эта основная логика должна быть обернута вокруг другого класса, который отвечает за прием вызовов основной логики асинхронно и делегирует эти вызовы основной логике. Производственный код будет иметь доступ только к основной логике через этот класс. Поскольку этот класс должен только делегировать вызовы, это очень "dumb" класс без особой логики. Таким образом, вы можете сохранить свои юнит-тесты для этого асинхронного рабочего класса как минимум.
Все, что выше этого (тестирование взаимодействия между классами), являются компонентными тестами. Кроме того, в этом случае вы должны иметь абсолютный контроль над временем, если вы придерживаетесь шаблона "Humble Object".
Действительно, крепкий орешек! В моих модульных тестах (C++) я разбил это на несколько категорий в соответствии с используемым шаблоном параллелизма:
Модульные тесты для классов, которые работают в одном потоке и не знают потоков - легко, тест как обычно.
Модульные тесты для объектов монитора (те, которые выполняют синхронизированные методы в потоке управления вызывающих объектов), которые предоставляют синхронизированный общедоступный API -- создают несколько фиктивных потоков, которые выполняют API. Постройте сценарии, реализующие внутренние условия пассивного объекта. Включите один более продолжительный запуск теста, который в основном выбивает все дерьмо из него из нескольких потоков в течение длительного периода времени. Это ненаучно, я знаю, но это действительно укрепляет доверие.
Модульные тесты для активных объектов (те, которые инкапсулируют свой собственный поток или потоки управления) -- аналогично #2 выше с вариациями в зависимости от дизайна класса. Public API может быть блокирующим или неблокирующим, вызывающие абоненты могут получать фьючерсы, данные могут поступать в очереди или должны быть удалены из очереди. Здесь возможно множество комбинаций; белый ящик далеко. По-прежнему требуется несколько фиктивных потоков для выполнения вызовов тестируемого объекта.
Как бы в сторону:
Во внутреннем обучении разработчиков, которое я провожу, я учу основам параллелизма и этим двум шаблонам как основной структуре для размышления о проблемах параллелизма и их декомпозиции. Там, очевидно, есть более продвинутые концепции, но я обнаружил, что этот набор основ помогает держать инженеров подальше от супа. Это также приводит к коду, который более поддается модульному тестированию, как описано выше.
В последние годы я несколько раз сталкивался с этой проблемой при написании кода обработки потоков для нескольких проектов. Я даю запоздалый ответ, потому что большинство других ответов, хотя и предоставляют альтернативные варианты, на самом деле не отвечают на вопрос о тестировании. Мой ответ адресован тем случаям, когда нет альтернативы многопоточному коду; я действительно рассматриваю вопросы проектирования кода для полноты, но также обсуждаю модульное тестирование.
Написание тестирования многопоточного кода
Первое, что нужно сделать, это отделить код обработки производственного потока от всего кода, который выполняет фактическую обработку данных. Таким образом, обработка данных может быть проверена как однопоточный код, и единственное, что делает многопоточный код, - это координирует потоки.
Второе, что следует помнить, - это то, что ошибки в многопоточном коде являются вероятностными; ошибки, которые проявляются наименее часто, - это ошибки, которые будут проникать в производство, будут трудно воспроизводиться даже в производстве и, таким образом, вызовут самые большие проблемы. По этой причине стандартный подход к кодированию, заключающийся в быстром написании кода и последующей отладке его до тех пор, пока он не будет работать, является плохой идеей для многопоточного кода; это приведет к коду, в котором легкие ошибки будут исправлены, а опасные ошибки все еще существуют.
Вместо этого, при написании многопоточного кода, Вы должны написать код с таким отношением, что вы собираетесь избежать написания ошибок в первую очередь. Если вы правильно удалили код обработки данных, код обработки потока должен быть достаточно маленьким - предпочтительно несколько строк, в худшем случае несколько десятков строк, - чтобы у вас был шанс написать его без написания ошибки и, конечно, без написания многих ошибок, если вы понимаете потоковую обработку, не торопитесь и осторожны.
Написание модульных тестов для многопоточного кода
Как только многопоточный код будет написан как можно более тщательно, все равно стоит написать тесты для этого кода. Основная цель тестов состоит не столько в том, чтобы проверить наличие сильно зависящих от времени ошибок в состоянии гонки - это невозможно проверить для таких условий гонки повторно - но скорее в том, чтобы проверить, что ваша стратегия блокировки для предотвращения таких ошибок позволяет нескольким потокам взаимодействовать так, как это предусмотрено.
Чтобы правильно проверить корректное поведение блокировки, тест должен запустить несколько потоков. Чтобы сделать тест повторяемым, мы хотим, чтобы взаимодействие между потоками происходило в предсказуемом порядке. Мы не хотим внешне синхронизировать потоки в тесте, потому что это будет маскировать ошибки, которые могут произойти в рабочей среде, где потоки не синхронизированы извне. Это оставляет использование временных задержек для синхронизации потоков, что является методом, который я успешно использовал всякий раз, когда мне приходилось писать тесты многопоточного кода.
Если задержки слишком малы, то тест становится хрупким, потому что незначительные различия во времени - скажем, между различными машинами, на которых могут выполняться тесты, - могут привести к тому, что время будет отключено и тест провалится. Как правило, я начинаю с задержек, которые вызывают сбои тестирования, увеличиваю задержки, чтобы тест прошел надежно на моей машине разработки, а затем удваиваю задержки после этого, чтобы тест имел хорошие шансы пройти на других машинах. Это действительно означает, что тест займет макроскопическое количество времени, хотя, по моему опыту, тщательный дизайн теста может ограничить это время не более чем десятком секунд. Поскольку в вашем приложении не должно быть очень много мест, требующих кода координации потоков, это должно быть приемлемо для вашего набора тестов.
Наконец, следите за количеством ошибок, пойманных вашим тестом. Если ваш тест имеет покрытие кода 80%, можно ожидать, что он поймает около 80% ваших ошибок. Если ваш тест хорошо разработан, но не обнаруживает ошибок, есть разумный шанс, что у вас нет дополнительных ошибок, которые будут отображаться только в рабочей среде. Если тест поймает одну или две ошибки, вам все равно может повезти. Кроме того, вы можете рассмотреть возможность тщательного анализа или даже полного перезаписи вашего кода обработки потоков, поскольку вполне вероятно, что код все еще содержит скрытые ошибки, которые будет очень трудно найти, пока код не будет запущен в производство, и очень трудно исправить.
У меня также были серьезные проблемы с тестированием многопоточного кода. Затем я нашел действительно классное решение в "xUnit Test Patterns" от Жерара Месароша. Модель, которую он описывает, называется скромным объектом .
В основном он описывает, как вы можете извлечь логику в отдельный компонент easy-to-test, который отделен от своей среды. После того как вы протестировали эту логику, вы можете проверить сложное поведение (многопоточность, асинхронное выполнение и т. д...)
Есть несколько инструментов вокруг, которые довольно хороши. Вот краткое изложение некоторых из них Java.
Некоторые хорошие инструменты статического анализа включают FindBugs (дает некоторые полезные подсказки), JLint, Java Pathfinder (JPF & JPF2) и Bogor .
MultithreadedTC- это довольно хороший инструмент динамического анализа (интегрированный в JUnit), где вам нужно настроить свои собственные тестовые случаи.
ConTest от IBM Research-это интересно. Он инструментирует ваш код, вставляя все виды потоков, изменяющих поведение (например, sleep & yield), чтобы попытаться случайно обнаружить ошибки.
SPIN- это действительно классный инструмент для моделирования ваших Java (и других) компонентов, но вам нужно иметь какую-то полезную структуру. Его трудно использовать как есть, но он очень мощный, если вы знаете, как его использовать. Довольно много инструментов используют SPIN под капотом.
MultithreadedTC, вероятно, является наиболее распространенным, но некоторые из перечисленных выше инструментов статического анализа определенно заслуживают внимания.
Еще один способ (своего рода) тестирования потокового кода и очень сложных систем в целом-это тестирование Fuzz . Это не очень хорошо, и он не найдет все, но, вероятно, будет полезен и его просто сделать.
Цитата:
Fuzz testing или fuzzing-это метод тестирования программного обеспечения, который предоставляет случайные данные("fuzz") на входы программы. Если программа выходит из строя (например, в результате сбоя или сбоя встроенных утверждений кода), дефекты могут быть замечены. Большое преимущество fuzz-тестирования заключается в том, что его дизайн чрезвычайно прост и свободен от предвзятых представлений о поведении системы. ...
Fuzz-тестирование часто используется в крупных проектах разработки программного обеспечения, которые используют тестирование черного ящика. Эти проекты обычно имеют бюджет для разработки тестовых инструментов, и fuzz-тестирование является одним из методов, который предлагает высокое соотношение выгоды и затрат. ...
Однако fuzz-тестирование не является заменой исчерпывающего тестирования или формальных методов: оно может предоставить только случайную выборку поведения системы, и во многих случаях прохождение теста fuzz может только продемонстрировать, что часть программного обеспечения обрабатывает исключения без сбоев, а не ведет себя правильно. Таким образом, fuzz-тестирование можно рассматривать только как инструмент поиска ошибок, а не как гарантию качества.
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
Я много раз это делал, и да, это отстой.
Некоторые советы:
- 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());
}
Проверка кода MT на корректность-это, как уже говорилось, довольно сложная задача. В конечном итоге все сводится к тому, чтобы убедиться, что в вашем коде нет неправильно синхронизированных гонок данных. Проблема с этим заключается в том, что существует бесконечно много возможностей выполнения потоков (перемежений), над которыми у вас нет особого контроля (хотя обязательно прочитайте эту статью). В простых сценариях можно было бы действительно доказать правильность рассуждений, но это обычно не так. Особенно если вы хотите избежать/минимизировать синхронизацию и не идти на самый очевидный/самый простой вариант синхронизации.
Подход, которому я следую, состоит в том, чтобы написать очень параллельный тестовый код, чтобы сделать потенциально незамеченные гонки данных вероятными. А потом я запускаю эти тесты в течение некоторого времени :) Однажды я наткнулся на разговор, в котором какой-то компьютерный ученый демонстрирует инструмент, который делает это (случайно придумывая тест из спецификаций, а затем дико запускает их одновременно, проверяя, не нарушаются ли определенные инварианты).
Кстати, я думаю, что этот аспект тестирования кода MT здесь не упоминался: определите инварианты кода, которые вы можете проверить случайным образом. К сожалению, найти эти инварианты-тоже довольно сложная задача. Кроме того, они могут не удерживаться все время во время выполнения, поэтому вам нужно найти/принудительно выполнить точки выполнения, где вы можете ожидать, что они будут истинными. Доведение выполнения кода до такого состояния также является трудной проблемой (и само по себе может привести к проблемам параллелизма. Фью, это чертовски трудно!
Некоторые интересные ссылки для чтения:
- Детерминированное чередование: фреймворк, позволяющий форсировать определенные чередования потоков и затем проверять наличие инвариантов
- jMock Blitzer : синхронизация стресс-тестов
- assertConcurrent : JUnit версия синхронизации стресс-тестирования
- Тестирование параллельного кода : краткий обзор двух основных методов грубой силы (стресс-тест) или детерминированного (переход к инвариантам)
Пит Goodliffe имеет ряд на модульное тестирование многопоточности .
Это очень тяжело. Я выбираю более легкий путь и стараюсь абстрагировать потоковый код от фактического теста. Пит упоминает, что то, как я это делаю, неправильно, но я либо правильно отделился, либо мне просто повезло.
Для Java проверьте Главу 12 из JCIP . Есть несколько конкретных примеров написания детерминированных многопоточных модульных тестов, чтобы, по крайней мере, проверить правильность и инварианты параллельного кода.
"Proving" потокобезопасность с модульными тестами гораздо сложнее. Я считаю, что этому лучше всего служит автоматизированное интеграционное тестирование на различных platforms/configurations.
Взгляните на мой связанный ответ в
Проектирование тестового класса для пользовательского барьера
Он смещен в сторону Java, но имеет разумное резюме вариантов.
В общем, хотя (IMO) это не использование какой-то причудливой структуры, которая обеспечит правильность, но то, как вы идете на проектирование многопоточного кода. Разделение проблем (параллелизм и функциональность) имеет огромное значение для повышения доверия. Растущее объектно-ориентированное программное обеспечение, управляемое тестами , объясняет некоторые варианты лучше, чем я могу.
Статический анализ и формальные методы (см. параллелизм: модели состояний и программы Java) - это вариант, но я обнаружил, что они имеют ограниченное применение в коммерческой разработке.
Не забывайте, что любые тесты стиля load / soak редко гарантированно выявляют проблемы.
Удачи вам!
Я обрабатываю модульные тесты резьбовых компонентов так же, как и любой модульный тест, то есть с инверсией рамок управления и изоляции. Я развиваюсь на .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() вызовов для координации порядка вызовов из разных потоков, но это не совсем надежно. Это также намного медленнее, потому что вы должны спать достаточно долго, чтобы время обычно работало.
Я нашел многопоточную библиотеку TC Java из той же группы, которая написала FindBugs. Он позволяет задать порядок событий без использования Sleep(), и это надежно. Я его еще не пробовал.
Самое большое ограничение этого подхода заключается в том, что он позволяет тестировать только те сценарии, которые, как вы подозреваете, вызовут проблемы. Как уже говорили другие, вам действительно нужно изолировать свой многопоточный код на небольшое количество простых классов, чтобы иметь хоть какую-то надежду на их тщательное тестирование.
После того как вы тщательно протестировали сценарии, которые вы ожидаете вызвать проблемы, ненаучный тест, который бросает кучу одновременных запросов в класс на некоторое время, является хорошим способом поиска неожиданных проблем.
Update: я немного поиграл с многопоточной библиотекой TC Java, и она хорошо работает. Я также перенес некоторые из его функций в версию .NET, которую я называю TickingTest .
Я только недавно обнаружил (для Java) инструмент под названием Threadsafe. Это инструмент статического анализа, очень похожий на findbugs, но специально предназначенный для обнаружения многопоточных проблем. Это не замена для тестирования, но я могу рекомендовать его как часть написания надежного многопоточного Java.
Он даже улавливает некоторые очень тонкие потенциальные проблемы вокруг таких вещей, как подсуммирование классов, доступ к небезопасным объектам через параллельные классы и обнаружение отсутствующих летучих модификаторов при использовании парадигмы блокировки с двойной проверкой.
Если вы пишете многопоточный Java, дайте ему шанс.
В следующей статье предлагается 2 решения. Накрутка семафор (CountDownLatch) и добавляет функции, такие как копирование данных с внутренней резьбой. Еще одним способом достижения этой цели является использование пула потоков (см. пункты интереса).
Спринклер-расширенный объект синхронизации
У меня была неудачная задача тестирования потокового кода, и это, безусловно, самые сложные тесты, которые я когда-либо писал.
При написании тестов я использовал комбинацию делегатов и событий. В основном это все об использовании событий PropertyNotifyChanged с WaitCallback или каким-то ConditionalWaiter , который опрашивает.
Я не уверен, что это был лучший подход, но он сработал для меня.
Большую часть прошлой недели я провел в университетской библиотеке, изучая отладку параллельного кода. Главная проблема заключается в том, что параллельный код недетерминирован. Как правило, академическая отладка здесь попала в один из трех лагерей:
- Event-trace/replay. Это требует наличия монитора событий и последующего просмотра отправленных событий. В рамках UT это будет включать в себя ручную отправку событий как часть теста, а затем выполнение посмертных проверок.
- Можно написать сценарий. Именно здесь вы взаимодействуете с запущенным кодом с помощью набора триггеров. "На x > foo, baz()". Это может быть интерпретировано в фреймворк UT, где у вас есть система времени выполнения, запускающая данный тест при определенном условии.
- Интерактивный. Это, очевидно, не будет работать в ситуации автоматического тестирования. ;)
Теперь, как заметили выше комментаторы, вы можете сконструировать свою параллельную систему в более детерминированное состояние. Однако, если вы не сделаете этого должным образом, вы просто снова вернетесь к разработке последовательной системы.
Мое предложение состояло бы в том, чтобы сосредоточиться на том, чтобы иметь очень строгий протокол проектирования о том, что получает резьбу и что не получает резьбу. Если вы ограничите свой интерфейс так, чтобы между элементами было минимальное количество зависимостей, это будет намного проще.
Удачи вам, и продолжайте работать над этой проблемой.
Для кода J2E я использовал SilkPerformer, LoadRunner и JMeter для тестирования параллелизма потоков. Они все делают одно и то же. В основном они предоставляют вам относительно простой интерфейс для администрирования своей версии прокси-сервера, необходимый для анализа потока данных TCP/IP и моделирования одновременных запросов нескольких пользователей к вашему серверу приложений. Прокси-сервер может дать вам возможность делать такие вещи, как анализировать сделанные запросы, представляя всю страницу и URL отправленную на сервер, а также ответ от сервера, после обработки запроса.
Вы можете найти некоторые ошибки в небезопасном режиме http, где вы можете, по крайней мере, анализировать данные формы, которые отправляются, и систематически изменять их для каждого пользователя. Но истинные тесты-это когда вы работаете в https (защищенные слои сокетов). Затем вам также придется бороться с систематическим изменением данных сеанса и файлов cookie, которые могут быть немного более запутанными.
Лучшая ошибка, которую я когда-либо находил, тестируя параллелизм, была тогда, когда я обнаружил, что разработчик полагался на сборку мусора Java, чтобы закрыть запрос на соединение, который был установлен при входе на сервер LDAP, при входе в систему. Это привело к тому, что пользователи были подвержены сеансам других пользователей и очень запутанным результатам, когда они пытались проанализировать, что произошло, когда сервер был поставлен на колени, едва в состоянии выполнить одну транзакцию каждые несколько секунд.
В конце концов, вам или кому-то еще, вероятно, придется пристегнуться и проанализировать код на предмет ошибок, подобных той, о которой я только что упомянул. И открытая дискуссия между отделами, подобная той, которая произошла, когда мы развернули описанную выше проблему, очень полезна. Но эти инструменты являются лучшим решением для тестирования многопоточного кода. JMeter - это открытый исходный код. SilkPerformer и LoadRunner являются собственностью компании. Если вы действительно хотите знать, является ли ваше приложение потокобезопасным, именно так делают большие мальчики. Я делал это для очень больших компаний профессионально, так что я не предполагаю. Я говорю Из личного опыта.
Одно предостережение: чтобы понять эти инструменты, действительно требуется некоторое время. Это не будет простой установкой программного обеспечения и запуском GUI, если только вы уже не имели некоторого воздействия на многопоточное программирование. Я попытался определить 3 критические категории областей для понимания (формы, сеансы и данные cookie), надеясь, что, по крайней мере, начиная с понимания этих тем, вы сможете сосредоточиться на быстрых результатах, а не читать всю документацию.
Параллелизм - это сложное взаимодействие между моделью памяти, аппаратным обеспечением, кэшем и нашим кодом. В случае Java по крайней мере такие тесты были частично рассмотрены главным образом jcstress . Создатели этой библиотеки, как известно, являются авторами многих функций параллелизма JVM, GC и Java.
Но даже эта библиотека нуждается в хорошем знании спецификации модели памяти Java, чтобы мы точно знали, что мы тестируем. Но я думаю, что фокус этих усилий - миркобенчмарки. Не огромные бизнес-приложения.
Предполагая, что под кодом "multi-threaded" подразумевалось что-то, что есть
- состояния и изменяемые
- AND доступ / изменение несколькими потоками одновременно
Другими словами, речь идет о тестировании пользовательских статусных потокобезопасных class/method/unit -которые в наши дни должны быть очень редким зверем.
Поскольку этот зверь редок, прежде всего мы должны убедиться, что есть все веские причины, чтобы написать его.
Шаг 1. Рассмотрите возможность изменения состояния в том же контексте синхронизации.
Сегодня легко написать сочиняемый параллельный и асинхронный код, где IO или другие медленные операции выгружаются в фоновый режим, но общее состояние обновляется и запрашивается в одном контексте синхронизации. например, асинхронные / ожидающие задачи и Rx в .NET и т. д. - все они тестируемы по дизайну, "real" задачи и планировщики могут быть заменены, чтобы сделать тестирование детерминированным (однако это выходит за рамки вопроса).
Это может показаться очень ограниченным, но такой подход работает на удивление хорошо. Можно писать целые приложения в этом стиле без необходимости делать какое-либо состояние потокобезопасным (я это делаю).
Шаг 2. Если манипулирование общим состоянием на одном контексте синхронизации абсолютно невозможно.
Убедитесь, что колесо не изобретается заново / определенно нет стандартной альтернативы, которая может быть адаптирована для этой работы. Вполне вероятно, что код является очень связным и содержится в одной единице, например: с большой долей вероятности это частный случай какой-то стандартной потокобезопасной структуры данных, такой как hash map или collection или что-то еще.
Примечание: если код является большим / охватывает несколько классов AND нуждается в многопоточной манипуляции состоянием, то есть очень высокая вероятность того, что дизайн не очень хорош, пересмотрите Шаг 1
Шаг 3. Если этот шаг достигнут, то нам нужно протестировать наш собственный потокобезопасный class/method/unit с настраиваемым состоянием .
Я буду предельно честен : мне никогда не приходилось писать правильные тесты для такого кода. Большую часть времени я ухожу на Шаг 1, иногда на Шаг 2. Последний раз мне приходилось писать пользовательский потокобезопасный код так много лет назад, что это было до того, как я принял модульное тестирование / вероятно, мне все равно не пришлось бы писать его с текущими знаниями.
Если бы мне действительно нужно было проверить такой код ( наконец, фактический ответ ), то я бы попробовал пару вещей ниже
Недетерминированное стресс-тестирование. например, запустить 100 потоков одновременно и проверить, что конечный результат непротиворечив. Это более типично для более высокого уровня / интеграционного тестирования сценариев с несколькими пользователями, но также может использоваться на уровне единицы.
Предоставьте некоторый тест 'hooks', где тест может ввести некоторый код, чтобы помочь сделать детерминированные сценарии, где один поток должен выполнить операцию перед другим. Как бы это ни было уродливо, я не могу придумать ничего лучше.
Управляемое задержкой тестирование для выполнения потоков и выполнения операций в определенном порядке. Строго говоря, такие тесты тоже недетерминированы (есть вероятность заморозки системы / stop-the-world GC коллекции, которая может исказить иначе организованные задержки), также это некрасиво, но позволяет избежать крючков.
Если вы тестируете простой Новый Поток (runnable).run() вы можете имитировать поток для последовательного выполнения 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
}