Статьи

Понимание архитектуры JUnit’s Runner

Несколько недель назад я начал создавать небольшой JUnit Runner ( Oleaster ), который позволяет вам использовать Jasmine для написания юнит-тестов в JUnit. Я узнал, что создание пользовательских JUnit Runners на самом деле довольно просто. В этом посте я хочу показать вам, как работают JUnit Runners внутри и как вы можете использовать собственные Runners для изменения процесса выполнения теста JUnit.

Так что же такое JUnit Runner?

JUnit Runner — это класс, который расширяет абстрактный класс JUnit Runner . Бегуны используются для запуска тестовых классов. Runner, который следует использовать для запуска теста, можно установить с помощью аннотации @RunWith .

1
2
3
4
5
6
7
8
@RunWith(MyTestRunner.class)
public class MyTestClass {
 
  @Test
  public void myTest() {
    ..
  }
}

Тесты JUnit запускаются с использованием класса JUnitCore . Это можно сделать, запустив его из командной строки или используя один из различных методов run () (это то, что ваша IDE сделает для вас, если вы нажмете кнопку запуска теста ).

1
JUnitCore.runClasses(MyTestClass.class);

Затем JUnitCore использует отражение, чтобы найти подходящий Runner для пройденных тестовых классов. Одним из шагов здесь является поиск аннотации @RunWith в тестовом классе. Если другой Runner не найден, будет использован бегун по умолчанию ( BlockJUnit4ClassRunner ). Будет запущен экземпляр Runner, и тестовый класс будет передан Runner. Теперь задание бегуна — создать экземпляр и запустить пройденный тестовый класс.

Как работают бегуны?

Давайте посмотрим на иерархию классов стандартных JUnit Runners:

JUnit-бегун

Runner — это очень простой класс, который реализует интерфейс Describable и имеет два абстрактных метода:

1
2
3
4
5
6
public abstract class Runner implements Describable {
 
  public abstract Description getDescription();
 
  public abstract void run(RunNotifier notifier);
}

Метод getDescription () унаследован от Describable и должен возвращать описание . Описания содержат информацию, которая впоследствии экспортируется и используется различными инструментами. Например, ваша IDE может использовать эту информацию для отображения результатов теста. run () — это очень общий метод, который запускает что-то (например, тестовый класс или набор тестов). Я думаю, что обычно Runner — это не тот класс, который вы хотите расширить (он слишком щедрый).

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

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

ParentRunner имеет следующие три абстрактных метода:

1
2
3
4
5
6
7
8
public abstract class ParentRunner<T> extends Runner implements Filterable, Sortable {    
 
  protected abstract List<T> getChildren();
 
  protected abstract Description describeChild(T child);
 
  protected abstract void runChild(T child, RunNotifier notifier);
}

Подклассы должны возвращать список универсального типа T в getChildren (). Затем ParentRunner просит подкласс создать описание для каждого дочернего элемента (descriptionChild ()) и, наконец, запустить каждый дочерний класс (runChild ()).

Теперь давайте посмотрим на два стандартных ParentRunner: BlockJUnit4ClassRunner и Suite.

BlockJUnit4ClassRunner — это Runner по умолчанию, который используется, если не предоставлен другой Runner. Так что это Runner, который обычно используется, если вы запускаете один тестовый класс. Если вы посмотрите на источник BlockJUnit4ClassRunner, вы увидите что-то вроде этого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
 
  @Override
  protected List<FrameworkMethod> getChildren() {
    // scan test class for methonds annotated with @Test
  }
 
  @Override
  protected Description describeChild(FrameworkMethod method) {
    // create Description based on method name
  }
 
  @Override
  protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
    if (/* method not annotated with @Ignore */) {
      // run methods annotated with @Before
      // run test method
      // run methods annotated with @After
    }
  }
}

Конечно, это чрезмерно упрощено, но оно показывает, что в основном делается в BlockJUnit4ClassRunner. Параметр универсального типа FrameworkMethod — это, по сути, оболочка java.lang.reflect.Method, предоставляющая некоторые удобные методы. В getChildren () тестовый класс сканируется на наличие методов, аннотированных @Test с использованием отражения. Найденные методы помещаются в объекты FrameworkMethod и возвращаются. descriptionChildren () создает Description из имени метода, а runChild () наконец запускает тестовый метод. BlockJUnit4ClassRunner использует множество защищенных методов внутри. В зависимости от того, что именно вы хотите сделать, полезно проверить BlockJUnit4ClassRunner на наличие методов, которые вы можете переопределить. Вы можете взглянуть на источник BlockJUnit4ClassRunner на GitHub .

Suite Runner используется для создания тестовых наборов. Наборы — это наборы тестов (или других наборов). Простое определение набора выглядит так:

1
2
3
4
5
6
7
@RunWith(Suite.class)
@Suite.SuiteClasses({
  MyJUnitTestClass1.class,
  MyJUnitTestClass2.class,
  MyOtherTestSuite.class
})
public class MyTestSuite {}

Набор тестов создается путем выбора Suite Runner с аннотацией @RunWith. Если вы посмотрите на реализацию Suite, вы увидите, что это на самом деле очень просто. Единственное, что делает Suite, — это создает экземпляры Runner из классов, определенных с помощью аннотации @SuiteClasses. Таким образом, getChildren () возвращает список Runners, а runChild () делегирует выполнение соответствующему Runner.

Примеры

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

Вывод

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

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