Как проверить, что исключение не выбрасывается?

Я знаю, что один из способов сделать это:

@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?)

Ответ 1

Вы подходите к этому неправильно. Просто проверьте свою функциональность: если сгенерировано исключение, тест автоматически провалится. Если исключение не выдано, все ваши тесты станут зелеными.

Я заметил, что этот вопрос вызывает интерес время от времени, поэтому я буду немного расширяться.

Предыстория юнит-тестирования

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

Или, как определено в "Искусстве модульного тестирования", 2-е издание Роя Ошерова, стр. 11:

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

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

enter image description here

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

Первая единица работы должна проверить, возвращается ли действительный пользователь в случае правильного и недействительного ввода.
Любые исключения, которые вызываются источником данных, должны быть обработаны здесь: если ни один пользователь не присутствует, должен быть тест, который демонстрирует, что исключение выдается, когда пользователь не может быть найден. @Test(expected = IllegalArgumentException.class) этого может быть IllegalArgumentException который перехватывается с @Test(expected = IllegalArgumentException.class).

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

Обработка правильного и ошибочного ввода теста

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

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

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

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

TL; DR

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

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

Ответ 2

Я наткнулся на это из-за правила SonarQube "squid: S2699": "Добавьте хотя бы одно утверждение в этот контрольный пример".

У меня был простой тест, единственной целью которого было пройти без исключения.

Рассмотрим этот простой код:

public class Printer {

    public static void printLine(final String line) {
        System.out.println(line);
    }
}

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

Решение исходит от самого JUnit.

Если исключение не выдается, и вы хотите явно проиллюстрировать это поведение, просто добавьте expected как в следующем примере:

@Test(expected = Test.None.class /* no exception expected */)
public void test_printLine() {
    Printer.printLine("line");
}

Test.None.class является значением по умолчанию для ожидаемого значения.

Ответ 3

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: aribitrary блоки кода и простой комментарий

//setup
Component component = new Component();

//act
configure(component);

//assert 
/*assert does not throw*/{
  component.doSomething();
}

И наконец, с kotlin, языком, с которым я недавно влюбился:

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.

Учитывая, что вы написали компонент, который проверяет числовые строки для 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, чтобы вы могли более легко повторно использовать этот тест для других входов. В качестве альтернативы, если вы хотите пойти экзотично, вы можете пойти на инструмент генерации тестовэто). TestNG имеет лучшую поддержку параметризованных тестов.

То, что я нахожу особенно неприятным, - это рекомендация использовать @Test(expectedException=IllegalArgumentException.class), , это исключение опасно велико. Если ваш код изменяется таким образом, что компонент в конструкторе теста имеет if(constructorArgument <= 0) throw IllegalArgumentException(), и ваш тест снабжал 0 для этого аргумента, потому что это было удобно - и это очень распространено, потому что хорошие тестовые данные - это удивительно тяжелая проблема, то ваш тест будет зеленым, даже если он ничего не тестирует. Такой тест хуже, чем бесполезный.

Ответ 5

Если вам не повезло, чтобы поймать все ошибки в вашем коде. Вы можете глупо делать

class DumpTest {
    Exception ex;
    @Test
    public void testWhatEver() {
        try {
            thisShouldThroughError();
        } catch (Exception e) {
            ex = e;
        }
        assertEquals(null,ex);
    }
}

Ответ 6

JUnit 5 (Jupiter) предоставляет три функции для проверки отсутствия/присутствия исключений:

assertAll​()

Утверждает, что все поставляемые executables
не бросайте исключения.

assertDoesNotThrow​()

Утверждает, что исполнение
поставляемый executable/supplier
не бросает никаких исключений.

Эта функция доступна
начиная с JUnit 5.2.0 (29 апреля 2018 года).

assertThrows​()

Утверждает, что выполнение предоставленного executable
выдает исключение expectedType
и возвращает исключение.

пример

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));
    }

}

Ответ 7

JUnit5 добавляет метод assertAll() именно для этой цели.

assertAll( () -> foo() )

Источник: JUnit 5 API

Ответ 8

Если вы хотите проверить, использует ли ваша тестовая цель исключение. Просто оставьте тест как (moker-коллаборатор с помощью 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-an-easy-way/) может помочь, если вы настаиваете на tdd в этом случае.

Ответ 9

Использовать assertNull (...)

@Test
public void foo() {
    try {
        //execute code that you expect not to throw Exceptions.
    } catch (Exception e){
        assertNull(e);
    }
}

Ответ 10

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

@Rule
public ExpectedException expectedException = ExpectedException.none();

Ответ 11

Вы можете сделать это с помощью @Rule, а затем вызвать метод reportMissingExceptionWithMessage, как показано ниже: Это код Scala.

enter image description here

Ответ 12

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

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
    }
}

Ответ 13

Вы можете создавать любые собственные утверждения на основе утверждений из 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");

Вообще говоря, есть возможность мгновенно провалить ("бла бла бла") тест в любых сценариях, в любом месте, где это имеет смысл. Например, используйте его в блоке 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.*;

Ответ 14

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

@Test
public void testMyCode() {

    try {
        runMyTestCode();
    } catch (Throwable t) {
        throw new Error("fail!");
    }
}

Ответ 15

Аннотация @Test предлагает ожидаемый параметр.

@Test(expected=IllegalArgumentException.class)
public void foo() {
    // Your test --> Will fail if not IlleArgumentException is thrown
}