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


238

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

@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's @Rule?)


10
Тест JUnit считается неудачным, если он генерирует любое исключение, кроме ожидаемого. Обычно никаких исключений не ожидается.
Raedwald

Разве нет различия между ошибкой и ошибкой в ​​JUnit? Первое означает, что тест не пройден, второе означает, что произошло нечто неожиданное.
Vituel

Ответы:


198

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

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

Предпосылки к юнит-тестированию

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

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

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

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

введите описание изображения здесь

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

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

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

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

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

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

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

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

TL; DR

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

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


2
Дело в том, что я пытаюсь использовать TDD, и один из сотрудников, которых я использую, выбрасывает исключение. Поэтому мне нужно проверить тот факт, что я
принимаю

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

Дело в том, что я не могу восстановиться после исключения, возникшего в соавторе, и все, что я делаю, это просто регистрирую проблему, используя log.debug («Сообщение об ошибке»). Таким образом, нет никаких побочных эффектов, происходящих как часть блока catch, на который я могу утверждать.
Анкит Дхингра

5
@JeroenVannevel совершенно правильно проверить, правильно ли обрабатывается ситуация с ошибкой, которая вызывает исключение.
Турбьерн Равн Андерсен

1
@dpk да, ты можешь. Вы добавляете throws IllegalArgumentExceptionв свой тест. В итоге вы хотите, чтобы ваш тест стал красным, если есть исключение. Ну, угадай что? Вам не нужно писать fail(). Как писал @Jeroen Vannevel: «Если
Амеди Ван Гассе

133

Я наткнулся на это из-за правила 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 по умолчанию для ожидаемого значения.


30
Я думаю, что это лучший ответ. Принятый ответ великолепен, и автор правильно указал на запах кода. Однако он не ответил на конкретный вопрос.
HellishHeat

4
Интересно отметить, что значением по умолчанию для ожидаемого является None, поэтому достаточно сделать аннотирование метода с помощью @Test.
oziomajnr


41

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

}

1
Это лучший ответ сейчас. Другие ответы обсуждают более старые версии JUnit
Tejesh Raut

29

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

И наконец, с 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через a NumberFormatExceptionи Paths.getчерез a 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 для этого аргумента, потому что это было удобно - и это очень часто, потому что хорошая генерация тестовых данных - это удивительно трудная проблема - тогда ваш тест будет зеленая полоса, даже если она ничего не проверяет. Такой тест хуже бесполезного.


2
(относительно использования ожидаемого исключения) Начиная с JUnit 4.13, вы можете использовать, Assert.assertThrowsчтобы проверить, что некоторый код вызывает исключение.
MageWind

22

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

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

1
Просто небольшое предложение, Exception exдолжно быть, = null;прежде чем вы сможете проверить его.
Денис

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

3
Или просто поместите Assert.fail () в ловушку, проще и красивее IMO.
Исаак. Хазан

Да, я согласен с тобой. Еще один способ - добавить аннотацию поверх метода @Test (ожидается = InvalidRequestException.class)
Бен Теннисон

Ваше неправильное написание сбивает с толку: thisShouldThroughError -> thisShouldThrowError .
Оскар Браво


7

Хотя этому посту уже 6 лет, в мире Junit многое изменилось. С Junit5, теперь вы можете использовать

org.junit.jupiter.api.Assertions.assertDoesNotThrow()

Пример:

public void thisMethodDoesNotThrowException(){
   System.out.println("Hello There");
}

@Test
public void test_thisMethodDoesNotThrowException(){
  org.junit.jupiter.api.Assertions.assertDoesNotThrow(
      ()-> thisMethodDoesNotThrowException()
    );
}

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


Я хотел бы, чтобы был способ указать конкретный класс исключений здесь. Я должен сделать это внутри Awaitility«S untilAsserted(ThrowingRunnable assertion). Тестируемая система в настоящий момент выдает конкретное исключение в ThrowingRunnable, который я предоставляю, но я хочу дать ему некоторое время, пока он не прекратит это делать. Однако, если это вызовет другое исключение, я бы хотел, чтобы тест мгновенно провалился.
Убеогеш

1

Если вы хотите проверить, использует ли ваша цель тестирования исключение. Просто оставьте тест как (издевайтесь над сотрудником, используя 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.


1

Используйте assertNull (...)

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

6
Я бы сказал, что это вводит в заблуждение. Блок catch никогда не достигается, поэтому также assertNullникогда не выполняется. Однако у быстрого читателя создается впечатление, что сделано утверждение, которое действительно проверяет случай без броска. Другими словами: если достигнут блок catch, исключение всегда ненулевое - его можно заменить простым fail.
Андреас

В самом деле, вводит в заблуждение, ..... но подождите, ... о, я вижу ... assertNull(e)сообщит о том, что тест не пройден, поскольку, как указано, eне может быть nullв catchблоке ... Майк, это просто странное программирование: - /. .. да хотя бы использовать, fail()как говорит Андреас
Julien

1

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

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

ExpectedExceptions используются для утверждения брошенных исключений. Код, который вы предоставляете, предназначен только для инициализации правила, чтобы вы могли добавить свои требования к утверждениям. Этот код сам по себе не добавляет никакой ценности. Javadoc также заявляет следующее: "/ ** * Возвращает {@linkplain TestRule rule}, который не ожидает выброса * исключений (идентично поведению без этого правила). * /" Таким образом, он будет иметь точно такой же результат, как и без него ,
Пим

Я согласен с вами и не буду использовать это таким образом, но можно утверждать, что не было исключений. Если тест пройден, то должно быть достаточно, чтобы сказать, что исключение не было выдано, но, с другой стороны, если есть вопрос, в этом должна быть необходимость. И редко, но все же иногда хорошо иметь это видимым. Что если код и обстоятельства изменились, и у нас нет теста для какого-то конкретного крайнего случая?
LazerBanana

Мне любопытно посмотреть, как бы вы утверждали это с ожидаемым исключением. И да, если требования меняются и у вас нет теста на конкретный крайний случай, который вы ввернули ;-) всегда охватывайте все угловые случаи.
Пим

что вы имеете в виду? Вы не утверждаете это, вы ожидаете этого. В этом случае вы не ожидаете никаких исключений. Не уверен, о чем ты.
LazerBanana

0

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

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

0

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

введите описание изображения здесь


1
private val? Что это за язык? Ясно, что не Java; p И, пожалуйста, не предоставляйте код в качестве скриншота, это не приветствуется.
Андремоний

Я вижу , что вы упомянули это Scala, но говорят , что это может быть легко сделано в Java не является сильным аргументом, я извиняюсь
Andremoniy

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

-1

Следующее не проходит проверку для всех исключений, отмеченных или не проверенных:

@Test
public void testMyCode() {

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

-1

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

Есть довольно лучший вариант, чтобы проверить, что утверждение не было выброшено, что не связано с созданием пользовательского кода. @Rule является одним из них
Vargan

@Vargan Я указал метод для создания собственных утверждений в том виде, в котором он разработан JUnit специально для целей создания собственных утверждений. JUnit предусматривает, что при разработке, специально для этой цели, для создания ваших собственных правил, расширьте поведение JUnit с помощью утверждений, которые еще не реализованы. Потому что не все реализовано в этом мире. Эти утверждения работают одинаково, так как утверждение JUnit работает с точки зрения передачи или отказа, а также сообщения об ошибках.
armagedescu
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.