Рекомендация: инициализируйте поля класса JUnit в setUp () или при объявлении? - PullRequest
106 голосов
/ 04 февраля 2009

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

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 (). Однако в JUnit FAQ есть много примеров, которые инициализируют пустую коллекцию в setUp (), поэтому я думаю, что должна быть причина.

Ответы [ 9 ]

90 голосов
/ 08 февраля 2009

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

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

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

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

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

42 голосов
/ 04 февраля 2009

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

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

18 голосов
/ 04 февраля 2009

В дополнение к ответу Алекса Б.

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

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

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

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

  1. Создание отдельного экземпляра класса тестирования для каждого метода тестирования
  2. Повторите для каждого экземпляра класса теста: вызовите установку + вызов метода тестирования

С некоторыми регистрациями в тесте с двумя методами теста вы получаете: (номер - это хэш-код)

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

В JUnit 4:

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

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

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

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

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

7 голосов
/ 01 июня 2011

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

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

6 голосов
/ 07 февраля 2009

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

5 голосов
/ 04 февраля 2009

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

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

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

2 голосов
/ 08 февраля 2009

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

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

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

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

0 голосов
/ 12 августа 2018
  • Постоянные значения (используемые в приборах или утверждениях) должны быть инициализированы в их декларациях и final (как никогда не изменяются)

  • тестируемый объект должен быть инициализирован в методе настройки, потому что мы можем включить его. Конечно, мы не можем установить что-то сейчас, но мы можем установить это позже. Реализация в методе init облегчит изменения.

  • Зависимости тестируемого объекта, если они имитируются, даже не должны создаваться вами самими: сегодня фальшивые фреймворки могут создавать его путем отражения.

Тест без зависимости от макета может выглядеть так:

public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Before
    public void beforeEach() {
       some = new Some(new Foo(), new Bar());
    } 

    @Test
    public void populateList()
         ...
    }
}

Тест с изоляцией зависимостей может выглядеть следующим образом:

@RunWith(org.mockito.runners.MockitoJUnitRunner.class)
public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Mock
    Foo fooMock;

    @Mock
    Bar barMock;

    @Before
    public void beforeEach() {
       some = new Some(fooMock, barMock);
    }

    @Test
    public void populateList()
         ...
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...