multithreading - примеры - тестирование многопоточных приложений java




Как я должен тестировать многопоточный код? (16)

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

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

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


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


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

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

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

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

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


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

Цитата:

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

...

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

...

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


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

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

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

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

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

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


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

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

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

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

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


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

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


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

Проектирование класса испытаний для пользовательского барьера

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

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

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

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

Удачи!


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

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

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

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

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

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


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

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

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

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

  • this : структура, которая позволяет принудительно запускать чередование потоков, а затем проверять инварианты
  • jMock Blitzer : синхронизация напряжений
  • assertConcurrent : версия синхронности стресс-тестирования JUnit
  • Тестирование параллельного кода : Краткий обзор двух основных методов грубой силы (стресс-теста) или детерминированных (для инвариантов)

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

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


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

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

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

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

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

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


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

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

  • 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());
}

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


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

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

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

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

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

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

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

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

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

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


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




unit-testing