Попробуйте поймать в тесте JUnit

Я пишу модульные тесты для приложения, которое уже давно существует. Некоторые из методов, которые мне нужно протестировать, построены следующим образом:

public void someMethod() throws Exception { 
   //do something 
}

Если я хочу протестировать эти методы, я должен написать что-то вроде этого в своем модульном тесте:

@Test
public void someTest() {
   try {
      someMethod();
   }
   catch (Exception e) {
      e.printStackTrace();
   }
}

Это хорошая практика? Или есть другой способ проверить эти методы?

Я провел некоторое исследование в Интернете и нашел несколько решений с аннотацией @Rule и @Test(expected=Exception.class), но это не работает (Eclipse продолжает показывать строку someMethod() в тесте как неправильную). Я не знаю, хорошие ли это решения, потому что я новичок во всей истории модульного тестирования.

Если бы кто-то, кто много знает об этом, мог помочь мне, я был бы очень благодарен.


person Nelsch    schedule 15.07.2015    source источник
comment
Не подавляйте исключение, если вы не хотите, чтобы тест прошел независимо от того, было ли выброшено исключение. Обратите внимание, что тест, который проходит независимо от того, возникает ли что-то вроде исключения, может быть не таким уж полезным.   -  person Andy Turner    schedule 15.07.2015
comment
Вы можете просто позволить JUnit позаботиться об исключении, добавив его в свой метод sig: public void someTest() throws Exception. Однако, если вы хотите поймать исключение самостоятельно, чтобы утверждать, что оно было обнаружено, пример, который вы привели, хорош.   -  person s.ijpma    schedule 15.07.2015
comment
@Makoto JUnit позаботится об этом, распечатав трассировку стека и провалив тест.   -  person user253751    schedule 15.07.2015
comment
@immibis: Да, это правда. Однако по соглашению объявление исключения, которое будет выдано, позволяет либо какой-либо структуре более высокого уровня обрабатывать его, либо JVM. Я должен был быть более ясным в этом вопросе.   -  person Makoto    schedule 15.07.2015
comment
Является ли это хорошей идеей, зависит от того, что вы пытаетесь сделать.   -  person Raedwald    schedule 17.07.2015


Ответы (7)


Поскольку Exception является проверенным исключением, вы либо:

  • Должны поймать исключение в операторе try...catch или
  • Объявите, что исключение будет выброшено в самом методе.

То, что у вас есть, отлично работает, но я лично предпочитаю объявить исключение. Таким образом, если во время выполнения теста возникнет неожиданное исключение, тест провалится.

@Test
public void someTest() throws Exception {
    // dodgy code here
}

Если нам нужно увидеть, выдается ли конкретное исключение, у вас есть возможность использовать @Rule или добавить значение непосредственно в аннотацию @Test.

@Test(expected = FileNotFoundException.class)
public void someTest() throws Exception {
    // dodgy code here
}

В JUnit 5 вы можете использовать Assertions.assertThrows, чтобы выполнить то же самое. В целом я менее знаком с этим, поскольку на момент редактирования он еще не был GA, но, похоже, он принимает Executable из JUnit 5.

@Test
public void someTest() {
    assertThrows(FileNotFoundException.class, () ->
         { dodgyService.breakableMethod() };
}
person Makoto    schedule 15.07.2015
comment
Дело в том, что я не ожидаю, что исключение будет выброшено. Разве мой тест не провалится, если не будет выдано исключение? - person Nelsch; 15.07.2015
comment
Неа! Если исключение не выброшено, тест пройдет нормально, если он удовлетворяет другим вашим утверждениям. - person Makoto; 15.07.2015
comment
Ой, ладно, видимо я не совсем понял. Спасибо! - person Nelsch; 15.07.2015
comment
Помните, что только потому, что вы не ожидаете, что исключение когда-либо будет сгенерировано в вашей производственной системе, не означает, что не стоит проверять, чтобы оно было сгенерировано в тесте и чтобы обработка была разумной. запись в журнале, уведомление пользователя об ошибке и т. д., полное закрытие системы, закрытие потоков ~ вы никогда не знаете, когда сбой в сети приведет к тому, что ваш файл не будет найден или принтер будет недоступен и т. д. Не позволяйте вашим пользователям ПК зависает из-за плохо обработанного исключения, ожидающего освобождения потока или повторного предоставления ресурса. - person DaveM; 15.07.2015
comment
то что у тебя там работает нормально? ни в коем случае, это невыносимо ужасно. - person Nathan Hughes; 16.07.2015
comment
expected в @Test не допускается начиная с JUnit 5. - person Akash Agarwal; 05.08.2017
comment
@AkashAggarwal: Учитывая, что JUnit5 еще не GA, я был в счастливом неведении об этом факте. Однако я пересмотрел свой ответ. - person Makoto; 05.08.2017
comment
@Makoto То же самое, я тоже не знал. Мне очень понравился ожидаемый подход, и я потратил около 10 минут, чтобы заставить его работать, пока не понял, почему. Просто хотел, чтобы никто не столкнулся с той же проблемой. - person Akash Agarwal; 05.08.2017
comment
@AkashAggarwal: ... вы могли бы просто опубликовать свой собственный ответ, знаете ли ... - person Makoto; 05.08.2017
comment
@Makoto Мне захотелось внести свой вклад в ответ с наибольшим количеством голосов, так как его легче заметить :) - person Akash Agarwal; 05.08.2017

@Test
public void someTest() {
   try {
     someMethod();
   }
   catch (Exception e) {
     Assert.fail("Exception " + e);
   }
}

Что вы можете сделать, если исключение не должно происходить. Альтернативой может быть создание исключения в подписи следующим образом:

@Test
public void someTest() throws Exception {
     someMethod();
}

Разница в том, что в одном случае тест завершится ошибкой из-за исключения утверждения, а в другом — из-за сбоя теста. (например, где-то в вашем коде вы получаете NPE, и из-за этого тест будет)

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

@Test(expected=Exception.class) предназначен для тестов, которые хотят проверить, будет ли выдано исключение.

@Test(expected=ArrayIndexOutOfBounds.class)
public void testIndex() {
   int[] array = new int[0];
   int var = array[0]; //exception will be thrown here, but test will be green, because we expect this exception

}
person morpheus05    schedule 15.07.2015
comment
Это переопределяет то, как junit будет обрабатывать исключение, но хуже, поскольку вы теряете трассировку стека, что затрудняет определение места возникновения этого исключения. - person Andy Turner; 15.07.2015

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

Потому что, когда JUnit TestRunner находит выброшенное исключение, он автоматически регистрирует его как error для тестового примера.

Только если вы testcase ожидаете, что метод должен вызвать Exception, вы должны использовать @Test(expected=Exception.class) или перехватить исключение.

В других случаях просто бросьте его вверх с помощью

public void someTest() throws Exception {
person Codebender    schedule 15.07.2015

Вы можете добавить исключение в сигнатуру метода тестирования. Затем, если вы проверяете, выдается ли исключение, вы должны использовать @Test(expected=Exception.class). В тестовых случаях, когда исключение не должно быть выброшено, тест пройдет успешно.

@Test
public void testCaseWhereExceptionWontBeThrown() throws Exception {
    someMethod(); //Test pass
}

@Test(expected = Exception.class)
public void testCaseWhereExceptionWillBeThrown() throws Exception {
    someMethod(); //Test pass
}
person Héctor    schedule 15.07.2015
comment
Я думаю, что какой-то пример кода поможет вашему ответу, так как я думаю, что он, возможно, уже пробовал что-то подобное, но не понял синтаксис правильно. - person Andy Turner; 15.07.2015
comment
Для меня второй блок кода терпит неудачу, потому что код, который я тестирую, имеет блок try-catch. Я просто пытаюсь проверить, возникает ли конкретное исключение, когда я звоню с использованием неправильного значения. - person shapan dashore; 02.10.2020

Существует два основных правила обработки исключений в тестировщиках Junit:

  1. Если исключение возникло в тестируемом коде:

    • If it was expected, declare it in the expected attribute of the Test annotation. Or, if further checks should be done on the exception object itself, catch it and ignore it. (In this case, there must be also a call to Assert.fail at the end of the try block, to indicate that the expected exception was not produced).
    • Если этого не ожидалось, перехватите его и выполните Assert.fail. (Предыдущий вызов Exception.printStackTrace также полезен).
  2. Если исключение не было создано в тестируемом коде или оно не представляет интереса для теста (например, большинство исключений ввода-вывода создаются на сетевом уровне еще до того, как тест может быть даже завершен), повторите его. в пункте throws.

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

person Little Santi    schedule 15.07.2015

Три момента о JUnit:

  • Тесты должны быть точными, они должны быть однозначно пройдены или не пройдены исключительно в зависимости от того, как настроены тестовые входные данные.

  • Тесты должны сообщать об ошибках обратно в фреймворк.

  • Тесты не должны полагаться на чтение их вывода.

Ваш пример не работает по всем трем пунктам. Если возникнет исключение или нет, тест все равно будет пройден. Если возникает исключение, JUnit никогда не узнает об этом и не может включить его в результаты теста. Единственный способ узнать, что что-то пошло не так, — это прочитать, что тест записывает в стандартный вывод, из-за чего ошибки слишком легко игнорировать. Это не очень удобный способ написания тестов.

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

Если вы ожидаете, что тест завершится успешно без создания исключения, тогда, если что-либо в тесте может вызвать проверенное исключение, добавьте throws Exception к сигнатуре метода тестирования. Добавление throws к сигнатуре не означает, что метод должен что-либо генерировать, он просто позволяет генерировать любые возникающие исключения, чтобы среда тестирования могла их перехватить.

Единственный случай, когда вы действительно поймаете исключение в тесте, — это когда вы хотите проверить утверждения об исключении; например, вы можете проверить, соответствует ли сообщение об исключении тому, что вы ожидаете, или если для исключения установлена ​​причина. В этом случае вы должны добавить Assert.fail() в конец блока try, чтобы отсутствие исключения привело к сбою теста.

Плохо не наличие блока try-catch, а отсутствие чего-либо, что может привести к сбою теста.

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

person Nathan Hughes    schedule 16.07.2015

Что это за исключение? Это

  1. исключение из выполнения чего-то вроде использования потоков, которые не будут происходить в вашем модульном тесте или
  2. исключение, которое может произойти из-за какого-то неправильного ввода?

Если это 1. Я бы просто поместил его на уровень сигнатуры метода, потому что try-catch не служит никакой реальной цели, кроме церемонии.

@Test
public void testFoo() throws Exception {
    // ...
}

Если это 2. это становится немного сложнее. Вы должны спросить себя, что должно происходить, если выдается исключение. Должен ли тест провалиться? Это ожидается? Это не имеет значения? Примеры ниже того, как справиться со всем этим. ВНИМАНИЕ: я использовал Exception только потому, что это сделали вы. Я надеюсь, что на самом деле это не так, потому что, если какое-то другое исключение может быть выброшено, отличное от ожидаемого, то это будет очень шатко. Если возможно, не используйте Exception, используйте что-то более конкретное (в коде junit и).

// The below code assumes you've imported the org.junit.Assert class.

@Test
public void thisShouldFailIfExceptionCaught() {
    //Given...
    try {
        // When...
    } catch (Exception e) {
        Assert.fail();
    }
    // Then...
}

@Test
public void thisShouldPassOnlyIfTheExceptionIsCaught() {
    //Given...
    try {
        // When...
        Assert.fail();
    } catch (Exception expected) {}
    // No "then" needed, the fact that it didn't fail is enough.
}

@Test
public void irrelevantExceptionThatCouldBeThrown() {
    //Given...
    try {
        // When...
    } catch (Exception e) {}
    // Then...
}
person Captain Man    schedule 15.07.2015
comment
Это зависит от метода, который я использую. Это может быть JMSException, IOException или ConfigurationException :) Спасибо за поучительный ответ! - person Nelsch; 16.07.2015
comment
@Нельш, нет проблем! Я бы посоветовал в будущем, задавая вопросы, не использовать Exception, если вы действительно не имеете в виду это (например, тестируя что-то, что throws Exception), даже такие имена, как SomeCheckedException или SomeUncheckedException, действительно объясняют проблему. - person Captain Man; 16.07.2015
comment
Модульные тесты не должны перехватывать исключения только для вызова fail(). На самом деле, для всех модульных тестов совершенно нормально объявлять Exception, поскольку метод тестирования (должен!) никогда не вызывается нигде, кроме среды тестирования. И последний просто позволяет тесту провалиться в случае исключения, что именно то, что вы хотите. - person Michel Jung; 04.05.2017