Написание первого теста JUnit - PullRequest
10 голосов
/ 05 октября 2011

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

Так что, если мой WidgetUnitTest тестирует цель с именем Widget, я предполагаю, что мне нужно будет создать достаточное количество Widget s для использования во всех методах тестирования.Должен ли я создавать эти Widget с в конструкторе WidgetUnitTest или в методе setUp()?Должно ли быть отношение 1: 1 Widget с к методам тестирования или передовые практики требуют повторного использования Widget с в максимально возможной степени?

Наконец, какая степень детализации должна существовать между утверждениями / сбоями иметоды испытаний?Пурист может утверждать, что утверждения 1-and-only-1 должны существовать в методе тестирования , однако в соответствии с этой парадигмой, если Widget имеет метод получения с именем getBuzz(), я получу 20различные методы тестирования для getBuzz() с именами, такими как

@Test
public void testGetBuzzWhenFooIsNullAndFizzIsNonNegative() { ... }

В отличие от 1 метода, который проверяет множество сценариев и содержит множество утверждений:

@Test
public void testGetBuzz() { ... }

Спасибо за любую информациюот какого-то маэстро Юнит!

Ответы [ 5 ]

17 голосов
/ 05 октября 2011

шаблон

Интересный вопрос. Прежде всего - мой окончательный тестовый шаблон, настроенный в IDE:

@Test
public void shouldDoSomethingWhenSomeEventOccurs() throws Exception
{
    //given

    //when

    //then
}

Я всегда начинаю с этого кода (умные люди называют его BDD ).

  • В given Я размещаю настройку теста, уникальную для каждого теста.

  • when в идеале - одна строка - то, что вы тестируете.

  • then должен содержать утверждения.

Я не единственное утверждение защитник, однако вы должны проверить только один аспект поведения. Например, если метод должен что-то возвращать, а также имеет некоторые побочные эффекты, создайте два теста с одинаковыми разделами given и when.

Также тестовая таблица включает в себя throws Exception. Это для обработки надоедливых проверенных исключений в Java. Если вы протестируете какой-то код, который их генерирует, компилятор вас не побеспокоит. Конечно, если тест выдает исключение, он терпит неудачу.

Настройка

Тестовая настройка очень важна. С одной стороны, разумно извлечь общий код и поместить его в метод setup() / @Before. Тем не менее, обратите внимание, что при чтении теста ( и удобочитаемость является наибольшим значением в модульном тестировании! ), легко пропустить установочный код, висящий где-то в начале тестового примера. Таким образом, соответствующая настройка теста (например, вы можете создать виджет различными способами) должна идти к методу теста, но инфраструктура (настройка общих макетов, запуск встроенной тестовой базы данных и т. Д.) Должна быть извлечена. Еще раз для улучшения читаемости.

Также известно ли вам, что JUnit создает новый экземпляр класса тестового набора для каждого теста? Таким образом, даже если вы создадите свой CUT ( тестируемый класс ) в конструкторе, конструктор вызывается перед каждым тестом. Вид раздражает.

* * Зернистость тысяча сорок-девять

Назовите ваш тест и подумайте, какой вариант использования или функциональность вы хотите протестировать, никогда не думайте с точки зрения:

это класс Foo с методами bar() и buzz(), поэтому я создаю FooTest с testBar() и testBuzz(). О, дорогой, мне нужно протестировать два пути выполнения на всем протяжении bar() - поэтому давайте создадим testBar1() и testBar2().

shouldTurnOffEngineWhenOutOfFuel() - это хорошо, testEngine17() - это плохо.

Подробнее о наименовании

Что имя testGetBuzzWhenFooIsNullAndFizzIsNonNegative говорит о тесте? Я знаю это тесты что-то, но почему? И вы не думаете, что детали слишком интимны? Как насчет:

@Test shouldReturnDisabledBuzzWhenFooNotProvidedAndFizzNotNegative`

Он описывает смысл ввода и ваше намерение (при условии, что отключенное жужжание является своего рода buzz статус / тип). Также обратите внимание, что у нас больше нет жесткого кода getBuzz() имени метода и null контракта для Foo (вместо этого мы говорим: , когда Foo не предоставлено ). Что если в будущем вы замените null на шаблон нулевого объекта ?

Также не бойтесь 20 различных методов испытаний для getBuzz(). Вместо этого подумайте о 20 различных вариантах использования, которые вы тестируете. Однако, если ваш класс тестового примера становится слишком большим (поскольку он обычно намного больше, чем тестируемый класс), разбейте его на несколько тестовых случаев. Еще раз: FooHappyPathTest, FooBogusInput и FooCornerCases - это хорошо, Foo1Test и Foo2Test - это плохо.

читаемость

Стремитесь к коротким и описательным именам. Несколько строк в given и несколько строк в then. Вот и все. Создавайте компоновщики и внутренние DSL, извлекайте методы, пишите пользовательские сопоставления и утверждения. Тест должен быть даже более читабельным, чем производственный код. Не издевайся.

Я считаю полезным сначала написать серию пустых, хорошо названных методов тестового примера. Затем я возвращаюсь к первому. Если я все еще понимаю, что я должен был тестировать при каких условиях, я тем временем реализую тест, строящий API класса. Затем я реализую этот API. Умные люди называют это TDD (см. Ниже).

Рекомендуемое чтение:

1 голос
/ 05 октября 2011

Вместо методов тестирования старайтесь сосредоточиться на тестировании поведения.Задайте вопрос "что должен делать виджет?"Затем напишите тест, который подтверждает ответ.Например.Компиляция "Виджет должен быть непослушным"

public void setUp() throws Exception {
   myWidget = new Widget();
}

public void testAWidgetShouldFidget() throws Exception {
  myWidget.fidget();
}

, увидеть ошибки "Не определено ни одного метода", исправить ошибки, перекомпилировать тест и повторить.Затем задайте вопрос, каким должен быть результат каждого поведения, в нашем случае, что происходит в результате непоседы?Возможно, есть какой-то наблюдаемый результат, например, новая двухмерная координата.В этом случае предполагается, что наш виджет находится в заданной позиции, а когда он суетится, его позиция каким-то образом изменяется.

public void setUp() throws Exception {
   //Given a widget
   myWidget = new Widget();
   //And it's original position
   Point initialWidgetPosition = widget.position();
}


public void testAWidgetShouldFidget() throws Exception {
  myWidget.fidget();
}

public void testAWidgetPositionShouldChangeWhenItFidgets() throws Exception {
  myWidget.fidget();
  assertNotEquals(initialWidgetPosition, widget.position());
}

Некоторые могут поспорить с тем, что оба теста работают одинаково, но имеет смыслВыделите поведение fidget независимо от того, как оно влияет на widget.position ().Если какое-либо поведение нарушается, один тест точно определяет причину сбоя.Также важно заявить, что поведение может быть выполнено само по себе как выполнение спецификации (у вас есть спецификации программы, не так ли?), Которая предполагает, что вам нужен беспокойный виджет.В конце концов, это все о реализации спецификаций вашей программы в виде кода, который реализует ваши интерфейсы, которые демонстрируют как вы завершили спецификацию, так и, во-вторых, как вы взаимодействуете с вашим продуктом.По сути, так должен работать TDD.Любая попытка устранить ошибки или протестировать продукт обычно приводит к разочаровывающим бессмысленным спорам о том, какую платформу использовать, уровень охвата и уровень детализации вашего пакета.Каждый тестовый пример должен быть упражнением по разбиению вашей спецификации на компонент, где вы можете начать формулировку с Given / When / Then.Дано {некоторое состояние приложения или предварительное условие} Когда {вызывается поведение} Тогда {утверждают некоторый наблюдаемый результат}.

1 голос
/ 05 октября 2011

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

Я бы порекомендовал иметь отдельный тест для каждого сценария / поведения / логического потока, который нужно протестировать, а не один массивный тест для всего в getBuzz ().Вы хотите, чтобы у каждого теста была целевая цель того, что вы хотите проверить в getBuzz ().

0 голосов
/ 05 октября 2011

Я полностью второй ответ Томаша Нуркевича, поэтому я скажу это, а не повторю все, что он сказал.

Еще пара очков:

Не забудьте проверить ошибки. Вы можете рассмотреть что-то вроде этого:

@Test
public void throwExceptionWhenConditionOneExist() {
    // setup
    // ...
    try {
       classUnderTest.doSomething(conditionOne);
       Assert.fail("should have thrown exception");
    } catch (IllegalArgumentException expected) {
       Assert.assertEquals("this is the expected error message", expected.getMessage());
    } 
}

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

  • Вы думаете о том, какой пользовательский сценарий соответствует вашим требованиям
  • Вы пишете базовый первый тест для него
  • Вы делаете его компилируемым (создавая необходимые классы, включая тестируемый класс и т. Д.)
  • Вы запускаете его: оно должно дать сбой
  • Теперь вы реализуете функциональность тестируемого класса, что позволит ему пройти (и ничего более )
  • Сполосните и повторите с новым требованием

Когда все ваши требования пройдут тестирование, все готово. Вы НИКОГДА не пишете в своем производственном коде ничего, что раньше не проверялось (исключение составляет код регистрации и не намного).

TDD имеет неоценимое значение в создании кода хорошего качества, а не в чрезмерных технических требованиях и в обеспечении 100% функционального покрытия (а не покрытия линии, что обычно бессмысленно). Это требует изменения в том, как вы рассматриваете кодирование, поэтому важно изучать технику одновременно с тестированием. Как только вы получите это, оно станет естественным.

Следующий шаг - изучение стратегий насмешек:)

Веселого тестирования.

0 голосов
/ 05 октября 2011

Прежде всего, методы setUp и tearDown будут вызываться до и после каждого теста, поэтому метод setUp должен создавать объекты, если они вам нужны в каждом тесте, и в тесте могут выполняться определенные для теста вещи.сам.

Во-вторых, вам решать, как вы хотите протестировать свою программу.Очевидно, что вы могли бы написать тест для каждой возможной ситуации в вашей программе и в итоге получить gazillion тестов для каждого метода.Или вы можете написать только один тест для каждого метода, который проверяет все возможные сценарии.Я бы порекомендовал смесь между двумя способами.Вам действительно не нужен тест для тривиальных методов получения / установки, но написание только одного теста для метода может привести к путанице в случае сбоя теста.Вы должны решить, какие методы стоит протестировать, а какие сценарии стоит протестировать.Но в принципе каждый сценарий должен иметь свой собственный тест.

В большинстве случаев я получаю кодовое покрытие от 80 до 90 процентов с помощью своих тестов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...