Иногда было бы полезно иметь возможность запустить тест JUnit в отдельном потоке. В частности, при написании интеграционных тестов, которые взаимодействуют с инкапсулированными ThreadLocal
и т.п., это может пригодиться. Отдельный поток неявно гарантирует, что связанная с потоком ссылка на локальный поток неинициализируется для каждого запуска теста. Этот пост представляет правило JUnit, которое предоставляет такую функциональность, и объясняет, как его использовать.
Для начала взглянем на следующий пример. Он изображает контрольный пример, который вызывает периодические сбои testB
. Причина этого заключается в том, что результат зависит от порядка выполнения всех тестов из-за побочных эффектов 1 . Точнее, Display.getDefault()
в принципе возвращает ленивый экземпляр синглтона, а Display.getCurrent()
— простой метод доступа к этому синглтону. Как следствие, testB
дает сбой, если он запускается после testA
2 .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
public class FooTest { @Test public void testA() { Display actual = Display.getDefault(); assertThat( actual ).isNotNull(); } @Test public void testB() { Display actual = Display.getCurrent(); assertThat( actual ).isNull(); } } |
Чтобы избежать некоторого закулисного волшебства, которое несет риск сделать код менее понятным, мы могли бы гарантировать, что существующее отображение будет расположено до фактического выполнения теста 3 .
1
2
3
4
5
6
|
@Before public void setUp() { if ( Display.getCurrent() != null ) { Display.getCurrent().dispose(); } } |
К сожалению, этот подход не может быть использован в комплекте тестов интеграции, который, например, запускает тесты PDE. Среда выполнения PDE создает один экземпляр Display
, время жизни которого охватывает все тестовые прогоны. Таким образом, удаление дисплеев не будет возможным вариантом, и testB
будет постоянно не работать при выполнении набора тестов PDE 4 .
На этом этапе важно помнить, что синглтон Display
связан с его потоком создания (quasi ThreadLocal
) 5 . Из-за этого testB
должен работать надежно, если выполняется в своем собственном потоке.
Однако обработка потоков обычно в лучшем случае несколько обременительна и создает много беспорядка, снижая читабельность методов тестирования. Это дало мне идею создать реализацию TestRule, которая инкапсулирует обработку потоков и поддерживает чистоту тестового кода:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
public class FooTest { @Rule public RunInThreadRule runInThread = new RunInThreadRule(); @Test public void testA() { Display actual = Display.getDefault(); assertThat( actual ).isNotNull(); } @Test @RunInThread public void testB() { Display actual = Display.getCurrent(); assertThat( actual ).isNull(); } } |
Класс RunInThreadRule
позволяет запускать один метод тестирования в своем собственном потоке. Он заботится о создании потока демона, выполнении теста, ожидании завершения потока и пересылке результата теста в основной поток. Чтобы отметить тест для запуска в отдельном потоке, метод теста должен быть аннотирован @RunInThread
как показано выше.
Благодаря этому testB
теперь не зависит от порядка выполнения тестов и становится надежным. Но следует быть осторожным, чтобы не злоупотреблять RunInThreadRule
. Хотя аннотация @RunInThread
указывает на то, что тест выполняется в отдельном потоке, он не объясняет почему. Это может легко запутать реальный объем такого теста. Следовательно, я использую это обычно только в качестве крайней меры. Например, это может быть целесообразно в случае, когда сторонняя библиотека использует инкапсулированный ThreadLocal
который не может быть очищен или сброшен с помощью функций API.
Для тех, кто любит проверять реализацию RunInThreadRule
я создал RunInThreadRule
GitHub:
https://gist.github.com/fappel/65982e5ea7a6b2fde5a3
Для практического использования вы также можете взглянуть на реализацию нашего проекта Gonsole PgmResourceBundlePDETest, расположенную по адресу:
https://github.com/rherrmann/gonsole .
- Обратите внимание, что JUnit по умолчанию сортирует методы тестирования в детерминированном, но не предсказуемом порядке.
- Также рассмотрите возможность того, что
testA
может быть в другом тестовом примере, и проблема возникает только при запуске большого набора - Опять же, мне тоже не нравится этот вид практики, поэтому для более изощренного решения вы можете взглянуть на статью «Правило JUnit для облегчения настройки теста SWT».
- В то же время вы, вероятно, признали, что упрощенный пример тестового примера не очень полезен, но я надеюсь, что этого достаточно для объяснения мотивации.
- Это делает такой поток потоком пользовательского интерфейса в SWT. SWT реализует однопотоковую модель пользовательского интерфейса, часто называемую квартирой
Ссылка: | Правило JUnit для запуска теста в его собственной теме от нашего партнера JCG Фрэнка Аппеля в блоге Code Affine . |