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

9 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В Юнит 4:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Постоянные значения (используемые в фиксаторах или утверждениях) должны быть инициализированы в их декларациях и 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()
         ...
    }
}
Другие вопросы по тегам