Як перевірити, що не виняток?


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

Чи є чистіший спосіб зробити це. (Можливо, використовуєте Джуніта @Rule?)


10
Як вважається, тест JUnit не вдався, якщо він викидає будь-який виняток, окрім очікуваного винятку. Зазвичай ніяких винятків не передбачається.
Raedwald

Чи не існує різниці між помилкою та помилкою в JUnit? Перший означає тест не вдався, другий означає, що сталося щось несподіване.
Vituel

Відповіді:


198

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

Я помітив, що час від часу це питання цікавить збирачів, тому я трохи розширюся.

Передумови для одиничного тестування

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

Або, як визначено у мистецтві одиничного тестування, 2-е видання Роя Ошерове , стор. 11:

Тест одиниці - це автоматизований фрагмент коду, який викликає одиницю роботи, що перевіряється, а потім перевіряє деякі припущення про єдиний кінцевий результат цього блоку. Тест одиниці майже завжди пишеться за допомогою одиничної рамки тестування. Це можна легко записати і швидко працювати. Це надійно, читабельно та легко ремонтувати. Постійний у своїх результатах, доки виробничий код не змінився.

Важливо усвідомити, що одна одиниця роботи, як правило, не є лише одним методом, але на самому базовому рівні це один метод, а після цього він інкапсульований іншим блоком робіт.

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

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

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

Після того, як ви обробляєте всі ваші шаблони використання для цієї основної одиниці роботи, ви переходите на рівень. Тут ви робите точно так само, але ви обробляєте лише винятки, що надходять із рівня прямо нижче поточного. Це підтримує ваш тестовий код добре структурованим і дозволяє вам швидко пройти архітектуру, щоб знайти, де все піде не так, замість того, щоб скакати всюди.

Обробка дійсного та несправного введення тестів

На даний момент має бути зрозуміло, як ми будемо поводитися з цими винятками. Існує 2 типи введення: дійсний вхід і несправний вхід (вхідний в строгому сенсі, але він невірний).

Під час роботи з коректним введенням ви встановлюєте неявну тривалість того, що тест, який ви пишете, буде працювати.

Такий спосіб виклику може виглядати наступним чином : existingUserById_ShouldReturn_UserObject. Якщо цей метод не вдається (наприклад: викид винятку), знаєте, що щось пішло не так, і ви можете почати копати.

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

TL; DR

У вашому тесті ви намагалися зробити дві речі: перевірити правильність та несправність введення. Розділивши це на два способи, що кожен робить по одній справі, у вас будуть набагато чіткіші тести та набагато кращий огляд того, де все піде не так.

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


2
Вся справа в тому, що я намагаюся TDD, і один із співробітників, який я використовую, кидає виняток. Тому мені потрібно перевірити той факт, що я
вживаю

6
Ви кажете, що ваша функціональність залежить від обробки виключення? Це кодовий запах: є винятки, щоб вишукано дозволяти вирішувати проблеми; вони не використовуються для контролю потоку. Якщо ви хочете перевірити сценарій, у якому слід винести виняток, тоді слід використовувати expectedанотацію. Якщо ви хочете перевірити сценарій, коли ваш код не вдається, і ви хочете перевірити, чи помилка правильно обробляється: використовуйте expectedі, можливо, використовуйте твердження, щоб визначити, чи вона була усунена.
Jeroen Vannevel

Річ у тім, що я не можу відновитись із винятку, який стався у співпрацівнику, і все, що я роблю, це лише зафіксувати проблему за допомогою log.debug ("Повідомлення про помилку"). Тому немає ніяких побічних ефектів, які є частиною блоку вилову, про який я можу стверджувати.
Ankit Dhingra

5
@JeroenVannevel цілком справедливо перевірити, чи правильно обробляється ситуація з помилками, яка спричиняє викид виключення.
Thorbjørn Ravn Andersen

1
@dpk так можна. Ви додаєте throws IllegalArgumentExceptionдо свого тесту. Зрештою, ви хочете, щоб ваш тест став червоним, якщо є виняток. Ну, вгадайте, що? Писати не потрібно fail(). Як писав @Jeroen Vannevel: "якщо буде виключено виняток, тест автоматично провалиться".
Амедей Ван Гассе

132

Я натрапив на це через правило 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 (Юпітер) надає три функції для перевірки відсутності / присутності виключень:

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;перед тим, як ви можете її протестувати.
Denees

4
Це не чудове рішення. Якщо метод, який не повинен кидати виняток, все-таки кине, ви не отримаєте корисне повідомлення про помилку. Просто зателефонуйте методу, який не повинен викидати виняток, і протестуйте його повернене значення (або побічні ефекти, як-от реєстрація винятку). Якщо пізніше несподівано викине виняток, тест не вдасться.
NamshubWriter

3
Або просто помістіть Assert.fail () у вилов, простіший та гарніший IMO.
isaac.hazan

Так, я згоден з вами. Ще один спосіб - додати анотацію поверх методу @Test (очікувано = InvalidRequestException.class)
Бен Теннісон

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


7

Хоча цій посаді зараз 6 років, проте у світі Джуніт багато що змінилося. З 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
Я б сказав, що це вводить в оману. Блок лову ніколи не досягається, тому і assertNullніколи не виконується. Однак у швидкого читача складається враження, що зроблено твердження, яке дійсно підтверджує справу, що не кидає. Іншими словами: якщо досягнутий блок ловлі, виняток завжди є недійсним - він може бути замінений простим fail.
Андреас

вводячи в оману, дійсно, ... але зачекайте, ... о, я бачу ... assertNull(e)повідомлять про тест як про невдале, оскільки, як заявлено, eне може бути nullв catchблоці ... Майк це просто дивне програмування: - /. .. да , по крайней мере використовувати fail()як Андреас каже
Жюльєн

1

Можна очікувати, що виняток не буде викинуто, створивши правило.

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

Очікуванівиключення використовуються для ствердження викинутих винятків. Код, який ви надаєте, - це просто ініціалізувати правило, щоб ви могли додати свої вимоги до тверджень. Цей код сам по собі взагалі не додає ніякого значення. Javadoc також констатує це: "/ ** * Повертає {@linkplain TestRule правило}, яке очікує, що викид не буде виключенням (ідентичне поведінці без цього правила). * /" Таким чином, він матиме такий самий результат, як і без нього .
Pim Hazebroek

Я погоджуюся з вами, і не використовував би це таким чином, але можна стверджувати, що не було викинуто жодного винятку. Якщо тест пройде, повинно бути достатньо, щоб сказати, що виняток не був кинутий, але, з іншого боку, якщо є питання, у ньому повинна бути потреба. І рідко, але все ж іноді буває добре, щоб це було видно. Що робити, якщо код і обставини змінилися, і ми не маємо тесту на певний крайній випадок?
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 І будь ласка, не вказуйте код як скріншот, це не вітається.
Андремоній

Я бачу, що ви згадали, що це Скала, але сказати, що це можна легко зробити на Java - це не сильний аргумент, мені шкода
Андремоній

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

-1

Далі не вдалося перевірити всі винятки, перевірені чи не перевірені:

@Test
public void testMyCode() {

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

-1

Ви можете створити будь-який тип власних тверджень на основі тверджень з Джуніт:

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{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 Я вказав на метод створення власних тверджень таким чином, як це розроблено JUnit, спеціально для цілей створення власних тверджень. JUnit передбачає, що розробляючи, спеціально для цієї мети, створення власних правил, розширюйте поведінку JUnit на твердження, які ще не реалізовані. Тому що не все реалізовано в цьому світі. Ці твердження працюють ідентично, як твердження JUnit працює як з точки зору проходження чи відмови, так і звіт про помилки.
armagedescu
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.