Я знаю, что один из способов сделать это будет:
@Test
public void foo(){
try{
//execute code that you expect not to throw Exceptions.
}
catch(Exception e){
fail("Should not have thrown any exception");
}
}
Есть ли более чистый способ сделать это?. (Вероятно, используя Junit @ Rule
?)
Вы подходите к этому неправильно. Просто проверьте свою функциональность: если будет сделано исключение, тест автоматически завершится неудачей. Если не будет исключения, все ваши тесты будут зелеными.
Я заметил, что этот вопрос время от времени вызывает интерес, поэтому я немного расширю.
< h1 > Предыстория для модульного тестирования < / h1 >Когда вы тестируете единицу, важно определить для себя, что вы считаете единицей работы. В основном: извлечение вашей кодовой базы, которая может включать или не включать несколько методов или классов, которые представляют собой одну часть функциональности.
Или, как определено в [Искусство модульного тестирования, 2-е издание Роя Ошерова][1], стр. 11:
- Unit-тест * - это автоматизированный фрагмент кода, который вызывает тестируемую единицу работы, а затем проверяет некоторые предположения об одном конечном результате этого устройства. Единичный тест почти всегда пишется с использованием фреймворка модульного тестирования. Это может быть легко написано и работает быстро. Это заслуживает доверия, читается и поддерживается. Это согласуется в его результатах, пока производственный код не изменился.
Важно понимать, что одна единица работы обычно представляет собой не один метод, а на самом базовом уровне это один метод, после которого он инкапсулируется другой единицей работ.
введите описание изображения здесь!
В идеале у вас должен быть метод тестирования для каждой отдельной единицы работы, чтобы вы всегда могли сразу увидеть, где что-то идет не так. В этом примере есть базовый метод, называемый getUserById ()
, который вернет пользователя, и в общей сложности 3 единицы работ.
Первая единица работы должна проверить, возвращается ли действительный пользователь в случае действительного и недопустимого ввода. Любые исключения, которые выбрасываются источником данных, должны обрабатываться здесь: если пользователя нет, должен быть тест, который демонстрирует, что исключение выбрасывается, когда пользователя не найти. Примером этого может быть «Незаконное исключение», которое фиксируется аннотацией «@Test (ожидается = Незаконное действиеException.class)».
После того, как вы обработали все свои варианты использования для этой основной единицы работы, вы повышаете уровень. Здесь вы делаете то же самое, но вы обрабатываете только исключения, которые приходят с уровня прямо ниже текущего. Это сохраняет ваш код тестирования хорошо структурированным и позволяет быстро проходить через архитектуру, чтобы найти, где что-то идет не так, вместо того, чтобы прыгать повсюду.
< h1 > Обработка допустимого и ошибочного ввода тестов < / h1 >На данный момент должно быть ясно, как мы будем обрабатывать эти исключения. Существует 2 типа ввода: действительный ввод и безошибочный ввод (вход действителен в строгом смысле, но это не правильно).
Когда вы работаете с valid входом, вы устанавливаете неявную ожидаемую продолжительность, которую будет работать любой тест, который вы напишите.
Такой вызов метода может выглядеть следующим образом: существующийUserById_ShouldReturn_UserObject
. Если этот метод не работает (например,.: исключение брошено) тогда вы знаете, что что-то пошло не так, и вы можете начать копать.
Добавляя еще один тест (nonExistingUserById_ShouldThrow_IllegalArgumentException
), который использует ввод faulty и ожидает исключения, вы можете увидеть, делает ли ваш метод то, что он должен делать с неправильным вводом.
Вы пытались сделать две вещи в своем тесте: проверить действительный и ошибочный ввод. Разделив это на два метода, каждый из которых делает одну вещь, вы получите гораздо более четкие тесты и гораздо лучший обзор того, где что-то идет не так.
Помня о многоуровневой единице работ, вы также можете уменьшить количество тестов, которые вам нужны для слоя, который выше в иерархии, потому что вы не делаете 'необходимо учитывать все, что могло пойти не так в нижних слоях: слои ниже текущего являются виртуальной гарантией того, что ваши зависимости работают, и если что-то пойдет не так, это 's в вашем текущем слое (при условии, что нижние слои не 'не бросайте никаких ошибок сами).
[1]: http://www.amazon.com/The-Art-Unit-Testing-examples/dp/1617290890?SubscriptionId = AKIAIIBINOD46VC3JCLQ & tag = stackoverfl08-20 & linkCode = xm2 & camp = 2025 & creative = 165953 & creativeASIN = 1617290890
Я наткнулся на это из-за правила SonarQube «squid: S2699»: «Добавьте хотя бы одно утверждение в этот тестовый случай.«
У меня был простой тест, единственной целью которого было пройти без исключения.
Рассмотрим этот простой код:
public class Printer {
public static void printLine(final String line) {
System.out.println(line);
}
}
Какое утверждение можно добавить, чтобы проверить этот метод? Конечно, вы можете сделать пробный улов вокруг него, но это только вздутие кода.
Решение исходит от самого JUnit.
Если не указано никаких исключений и вы хотите явно проиллюстрировать это поведение, просто добавьте «ожидаемый», как в следующем примере:
@Test(expected = Test.None.class /* no exception expected */)
public void test_printLine() {
Printer.printLine("line");
}
Test.None.class
является значением по умолчанию для ожидаемого значения.
Java 8 делает это намного проще, а Kotlin / Scala вдвойне.
Мы можем написать немного полезного класса
class MyAssertions{
public static void assertDoesNotThrow(FailingRunnable action){
try{
action.run()
}
catch(Exception ex){
throw new Error("expected action not to throw, but it did!", ex)
}
}
}
@FunctionalInterface interface FailingRunnable { void run() throws Exception }
и тогда ваш код становится просто:
@Test
public void foo(){
MyAssertions.assertDoesNotThrow(() -> {
//execute code that you expect not to throw Exceptions.
}
}
Если у вас нет доступа к Java-8, я бы использовал болезненно старое средство Java: блоки произвольного кода и простой комментарий
//setup
Component component = new Component();
//act
configure(component);
//assert
/*assert does not throw*/{
component.doSomething();
}
И, наконец, с котлином, языком, в который я недавно влюбился:
fun (() -> Any?).shouldNotThrow()
= try { invoke() } catch (ex : Exception){ throw Error("expected not to throw!", ex) }
@Test fun `when foo happens should not throw`(){
//...
{ /*code that shouldn't throw*/ }.shouldNotThrow()
}
Хотя есть много места, чтобы возиться с тем, как именно вы хотите выразить это, я всегда был поклонником свободных утверждений.
Относительно
Вы подходите к этому неправильно. Просто проверьте свою функциональность: если будет сделано исключение, тест автоматически завершится неудачей. Если не будет исключения, все ваши тесты будут зелеными.
Это в принципе правильно, но неверно в заключение.
Java допускает исключения для потока управления. Это делается самим временем выполнения JRE в API, таких как Double.parseDouble
через NumberFormatException
и Paths.get
через InvalidPathException
.
Учитывая, что вы написали компонент, который проверяет строки Number для Double.ParseDouble
, возможно, с помощью Regex, возможно, рукописного анализатора или, возможно, что-то, что встраивает некоторые другие правила домена, которые ограничивают диапазон двойного до чего-то конкретного, как лучше всего проверить этот компонент? Я думаю, что очевидным тестом было бы утверждать, что, когда результирующая строка анализируется, исключение не бросается. Я бы написал этот тест, используя вышеупомянутый блок assertDoesNotThrow
или / * comment * / {code}
. Что-то вроде
@Test public void given_validator_accepts_string_result_should_be_interpretable_by_doubleParseDouble(){
//setup
String input = "12.34E+26" //a string double with domain significance
//act
boolean isValid = component.validate(input)
//assert -- using the library 'assertJ', my personal favourite
assertThat(isValid).describedAs(input + " was considered valid by component").isTrue();
assertDoesNotThrow(() -> Double.parseDouble(input));
}
Я также призываю вас параметризовать этот тест в input
, используя Theories
или [Parameterized
](https:// github.com/junit-team/junit4/wiki/Parameterized-tests), чтобы вам было легче. Кроме того, если вы хотите стать экзотикой, вы можете выбрать инструмент тестового поколения (и this). TestNG имеет лучшую поддержку для параметризованных тестов.
Что я нахожу особенно неприятным, так это рекомендация использования @ Test (expectedException = IllegalArgumentException.class)
, это исключение опасно широкое . Если ваш код изменяется так, что тестируемый компонент 'Конструктор имеет if(конструкторАргумент <= 0) бросить незаконное действие исключение(
, и ваш тест давал 0 для этого аргумента, потому что это было удобно - и это очень распространено, потому что хорошая генерация тестовых данных - удивительно сложная проблема, тогда ваш тест будет зеленым, хотя он ничего не проверяет. Такой тест хуже, чем бесполезен.
С AssertJ свободно говорит 3.7.0:
Assertions.assertThatCode(() -> toTest.method())
.doesNotThrowAnyException();
JUnit 5 (Jupiter) предоставляет три функции для проверки отсутствия / присутствия исключения:
assertAll ()
](https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assertions.html#assertAll(java.lang.String, java.util.Collection))& emsp; Утверждает , что все поставляют "исполняемые" & Emsp; не бросайте исключения.
assertDoesNotThrow ()
)& Emsp; Утверждает , что выполнение
& emsp; поставляется исполняемым
/ поставщик
& emsp; не бросает какой-либо вид [исключения](https://docs.oracle.com/javase/8/docs/api/java/lang/Throwable.html?is-external = true).
& Emsp; Эта функция доступна & emsp; начиная с JUnit 5.2.0 (29 апреля 2018 года).
assertThrows ()
& emsp; Утверждает , что выполнение поставленного исполняемого
& emsp; бросает исключение из expectedType
& emsp; и возвращает [исключение](https://docs.oracle.com/javase/8/docs/api/java/lang/Throwable.html?is-external = true).
package test.mycompany.myapp.mymodule;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class MyClassTest {
@Test
void when_string_has_been_constructed_then_myFunction_does_not_throw() {
String myString = "this string has been constructed";
assertAll(() -> MyClass.myFunction(myString));
}
@Test
void when_string_has_been_constructed_then_myFunction_does_not_throw__junit_v520() {
String myString = "this string has been constructed";
assertDoesNotThrow(() -> MyClass.myFunction(myString));
}
@Test
void when_string_is_null_then_myFunction_throws_IllegalArgumentException() {
String myString = null;
assertThrows(
IllegalArgumentException.class,
() -> MyClass.myFunction(myString));
}
}
Если вам не повезло, чтобы уловить все ошибки в вашем коде. Вы можете тупо сделать
class DumpTest {
Exception ex;
@Test
public void testWhatEver() {
try {
thisShouldThroughError();
} catch (Exception e) {
ex = e;
}
assertEquals(null,ex);
}
}
JUnit5 добавляет метод assertAll () для этой точной цели.
assertAll( () -> foo() )
Вы можете ожидать, что исключение не будет добавлено путем создания правила.
@Rule
public ExpectedException expectedException = ExpectedException.none();
Используйте assertNull (...)
@Test
public void foo() {
try {
//execute code that you expect not to throw Exceptions.
} catch (Exception e){
assertNull(e);
}
}
Если вы хотите проверить, использует ли ваша цель теста исключение. Просто оставьте тест как (насмешливый сотрудник, использующий jMock2):
@Test
public void consumesAndLogsExceptions() throws Exception {
context.checking(new Expectations() {
{
oneOf(collaborator).doSth();
will(throwException(new NullPointerException()));
}
});
target.doSth();
}
Тест пройдет, если ваша цель использует брошенное исключение, в противном случае тест не пройдёт.
Если вы хотите проверить свою логику потребления исключений, все становится сложнее. Я предлагаю делегировать потребление сотруднику, над которым можно издеваться. Поэтому тест может быть:
@Test
public void consumesAndLogsExceptions() throws Exception {
Exception e = new NullPointerException();
context.checking(new Expectations() {
{
allowing(collaborator).doSth();
will(throwException(e));
oneOf(consumer).consume(e);
}
});
target.doSth();
}
Но иногда это слишком продуманно, если вы просто хотите войти в систему. В этом случае эта статья (http://java.dzone.com/articles/monitoring-declarative-transac, http://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there- a-easy-way /) может помочь, если вы настаиваете tdd в этом случае.
Вы можете сделать это, используя @Rule, а затем вызвать метод reportMissingExceptionWithMessage, как показано ниже: Это код Скала.
Это может быть не лучшим способом, но он определенно гарантирует, что исключение не будет выброшено из тестируемого блока кода.
import org.assertj.core.api.Assertions;
import org.junit.Test;
public class AssertionExample {
@Test
public void testNoException(){
assertNoException();
}
private void assertException(){
Assertions.assertThatThrownBy(this::doNotThrowException).isInstanceOf(Exception.class);
}
private void assertNoException(){
Assertions.assertThatThrownBy(() -> assertException()).isInstanceOf(AssertionError.class);
}
private void doNotThrowException(){
//This method will never throw exception
}
}
Следующее не проходит тест для всех исключений, проверенных или не отмеченных:
@Test
public void testMyCode() {
try {
runMyTestCode();
} catch (Throwable t) {
throw new Error("fail!");
}
}
Вы можете создавать любые свои собственные утверждения на основе утверждений из junit:
static void assertDoesNotThrow(Executable executable) {
assertDoesNotThrow(executable, "must not throw");
}
static void assertDoesNotThrow(Executable executable, String message) {
try {
executable.execute();
} catch (Throwable err) {
fail(message);
}
}
И тест:
//the following will succeed
assertDoesNotThrow(()->methodMustNotThrow(1));
assertDoesNotThrow(()->methodMustNotThrow(1), "fail with specific message: facepalm");
//the following will fail
assertDoesNotThrow(()->methodMustNotThrow(2));
assertDoesNotThrow(()-> {throw new Exception("Hello world");}, "Fail: must not trow");
Вообще говоря, есть возможность мгновенно провалить («bla bla bla») тест в любых сценариях, в любом месте, где это имеет смысл. Например, используйте его в блоке try / catch, чтобы потерпеть неудачу, если что-то брошено в тестовый случай:
try{methodMustNotThrow(1);}catch(Throwable e){fail("must not throw");}
//or
try{methodMustNotThrow(1);}catch(Throwable e){Assertions.fail("must not throw");}
Это образец метода, который мы тестируем, если предположить, что у нас есть такой метод, который не должен потерпеть неудачу при определенных обстоятельствах, но может потерпеть неудачу:
void methodMustNotThrow(int x) throws Exception{
if (x == 1) return;
throw new Exception();
}
Вышеуказанный метод является простым образцом. Но это работает для сложных ситуаций, когда неудача не так очевидна. Есть импорт:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.*;