Лучшая практика: инициализировать поля класса JUnit в setUp() или декларации?

Должен ли я инициализировать поля класса в объявлении вроде этого?

public class SomeTest extends TestCase
{
    private final List list = new ArrayList();

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

Или в setUp(), как это?

public class SomeTest extends TestCase
{
    private List list;

    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        this.list = new ArrayList();
    }

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

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

Разъяснение: JUnit будет создавать экземпляр тестового класса один раз для каждого тестового метода. Это означает, что list будет создаваться один раз за тест независимо от того, где я его объявляю. Это также означает отсутствие временных зависимостей между тестами. Поэтому кажется, что нет никаких преимуществ при использовании setUp(). Однако в FAQ JUnit есть много примеров, которые инициализируют пустую коллекцию в setUp(), поэтому я считаю, что должна быть причина.

Ответ 1

Если вам интересно узнать примеры из JUnit FAQ, например базовый тестовый шаблон, я думаю, что лучшая практика будет показана что тестируемый класс должен быть создан в вашем методе setUp (или в методе тестирования).

Когда примеры JUnit создают ArrayList в методе setUp, все они продолжают проверять поведение этого ArrayList с такими случаями, как testIndexOutOfBoundException, testEmptyCollection и т.п. Перспектива заключается в том, что кто-то пишет класс и уверен, что он работает правильно.

Вы должны, вероятно, сделать то же самое при тестировании своих собственных классов: создать свой объект в setUp или в методе тестирования, чтобы вы могли получить разумный результат, если вы его сломали позже.

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

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

Ответ 2

Я начал копать себя, и я нашел одно потенциальное преимущество использования setUp(). Если во время выполнения setUp() исключены какие-либо исключения, JUnit напечатает очень полезную трассировку стека. С другой стороны, если во время построения объекта возникает исключение, сообщение об ошибке просто говорит, что JUnit не смог создать экземпляр тестового примера, и вы не видите номер строки, где произошел сбой, вероятно, потому, что JUnit использует отражение для создания экземпляра теста классы.

Ничто не относится к примеру создания пустой коллекции, поскольку это никогда не будет бросать, но это преимущество метода setUp().

Ответ 3

В дополнение к ответу Alex B.

Также требуется использовать метод setUp для создания ресурсов в определенном состоянии. Выполнение этого в конструкторе не только вопрос таймингов, но из-за того, как JUnit запускает тесты, каждое состояние теста будет стерто после запуска.

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

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

Жизненный цикл JUnits:

  • Создайте другой экземпляр testclass для каждого метода тестирования
  • Повторите для каждого экземпляра testclass: вызов setup + вызов testmethod

С некоторыми входами в тесте с двумя методами тестирования вы получаете: (number is hashcode)

  • Создание нового экземпляра: 5718203
  • Создание нового экземпляра: 5947506
  • Настройка: 5718203
  • TestOne: 5718203
  • Настройка: 5947506
  • TestTwo: 5947506

Ответ 4

В JUnit 3 инициализаторы полей будут запускаться один раз для каждого тестового метода до запуска любых тестов. Пока значения вашего поля невелики в памяти, мало времени на установку и не влияют на глобальное состояние, использование инициализаторов полей технически хорошо. Однако, если они не выполняются, вы можете в конечном итоге потреблять много памяти или времени, настраивая свои поля до запуска первого теста и, возможно, даже исчерпать память. По этой причине многие разработчики всегда задают значения полей в методе setUp(), где он всегда безопасен, даже если он не является строго необходимым.

Обратите внимание, что в JUnit 4 инициализация тестового объекта происходит непосредственно перед запуском теста, поэтому использование инициализаторов полей более безопасно и рекомендуется для стиля.

Ответ 5

В вашем случае (создание списка) на практике нет никакой разницы. Но, как правило, лучше использовать setUp(), потому что это поможет Junit правильно сообщать об исключениях. Если в конструкторе/инициализаторе теста возникает исключение, это ошибка теста. Однако, если во время установки возникает исключение, естественно думать об этом как о некоторой проблеме при настройке теста, а junit сообщает об этом соответствующим образом.

Ответ 6

В JUnit 4:

  • Для класса Under Test, инициализируйте метод @Before, чтобы поймать сбои.
  • Для других классов инициализируйте в объявлении...
    • ... для краткости и пометить поля final, как указано в вопросе,
    • ... если это не сложная инициализация, которая может выйти из строя, и в этом случае использовать @Before для обнаружения сбоев.
  • Для глобального состояния (например, медленная инициализация, как и база данных) используйте @BeforeClass, но будьте осторожны с зависимостями между тестами.
  • Инициализация объекта, используемого в одном тесте, должна, конечно, выполняться в самом методе тестирования.

Инициализация в методе @Before или методе тестирования позволяет получить лучшую отчетность об ошибках при сбоях. Это особенно полезно для создания экземпляра класса Under Test (который вы можете сломать), но также полезно для вызова внешних систем, таких как доступ к файловой системе ( "файл не найден" ) или подключение к базе данных ( "отказ в соединении" ).

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

Инициализация в setUp является реликвией JUnit 3, где все тестовые экземпляры были запрограммированы с нетерпением, что вызывает проблемы (скорость, память, исчерпание ресурсов), если вы выполняете дорогостоящую инициализацию. Таким образом, лучшей практикой было сделать дорогостоящую инициализацию в setUp, которая запускалась только при выполнении теста. Это больше не применяется, поэтому гораздо менее необходимо использовать setUp.

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

Ответ 7

Я предпочитаю сначала читаемость, которая чаще всего не использует метод установки. Я делаю исключение, когда базовая операция установки занимает много времени и повторяется в каждом тесте.
В этот момент я переместил эту функциональность в метод установки, используя аннотацию @BeforeClass (оптимизируйте позже).

Пример оптимизации с использованием метода установки @BeforeClass: я использую dbunit для некоторых функциональных тестов базы данных. Метод установки отвечает за помещение базы данных в известное состояние (очень медленное... 30 секунд - 2 минуты в зависимости от объема данных). Я загружаю эти данные в метод установки, аннотированный с помощью @BeforeClass, а затем запускаю 10-20 тестов против одного и того же набора данных, в отличие от повторной загрузки/инициализации базы данных в каждом тесте.

Использование Junit 3.8 (расширение TestCase, как показано в вашем примере) требует написания немного большего количества кода, чем просто добавления аннотации, но "запуск еще до настройки класса" по-прежнему возможен.

Ответ 8

Поскольку каждый тест выполняется независимо, со свежим экземпляром объекта, не так много указывает на объект Test, имеющий какое-либо внутреннее состояние, за исключением разделяемого между setUp() и индивидуальным тестом и tearDown(). Это одна из причин (в дополнение к другим причинам), что полезно использовать метод setUp().

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

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

В вашем случае, когда вы просто создаете пустой список, я бы сделал то же самое, что вы предлагаете: Назначьте новый список в точке объявления. Тем более, что у вас есть возможность маркировать его final, если это имеет смысл для вашего тестового класса.