Питання Як ви стверджуєте, що певне виключення було викинуто в тести JUnit 4?


Як я можу використовувати ідіоматично JUnit4 для перевірки того, що якийсь код видає виняток?

Хоча, звичайно, я можу зробити щось на зразок цього:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  boolean thrown = false;

  try {
    foo.doStuff();
  } catch (IndexOutOfBoundsException e) {
    thrown = true;
  }

  assertTrue(thrown);
}

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


1624
2017-10-01 06:56


походження


Проблема з будь-яким іншим підходом, але це те, що вони незмінно закінчають тест, як тільки було виключено виключення. З іншого боку, я часто хочу ще дзвонити org.mockito.Mockito.verify з різними параметрами, щоб переконатися, що відбуваються певні речі (наприклад, виклик служби реєстрації з правильними параметрами), перш ніж викинути виключення. - ZeroOne
Ви можете подивитися, як випробувати виключення на сторінці wiki JUnit github.com/junit-team/junit/wiki/Exception-testing - PhoneixS
@ZeroOne - Для цього я маю два різні тести - один для винятку і один для перевірки взаємодії з вашим макетом. - tddmonkey
Існує спосіб це зробити з JUnit 5, я оновив мою відповідь нижче. - Dilini Rajapaksha


Відповіді:


Підтримка JUnit 4:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);
}

Довідка: https://junit.org/junit4/faq.html#atests_7


1945
2017-10-01 07:12



Цей фрагмент коду не буде працювати, якщо ви очікуєте виключення лише в коді, а не в ковдрі, подібному до цього. - Oh Chin Boon
@Saffman Це не буде працювати з org.junit.experimental.theories. Theory запущений org.junit.experimental.theories. Theories - Artem Oboturov
Рой Ошерову не рекомендує такого виду випробувань в Росії Мистецтво одиничного тестування, оскільки виняток може бути де завгодно всередині тесту, а не тільки всередині випробуваного пристрою. - Kevin Wittek
Я не погоджуюсь з @ Kiview / Roy Oherove. На мій погляд, тести повинні бути для поведінки, а не для реалізації. Випробувавши, що конкретний метод може викинути помилку, ви безпосередньо прив'язуєте свої тести до реалізації. Я б стверджував, що тестування в описаному вище методі дає більш цінний тест. Застереження, яке я хотів би додати, полягає в тому, що в цьому випадку я б протестувати за власним винятком, так що я знаю, що отримую виключення, яке я дійсно хочу. - nickbdyer
Ні. Я хочу перевірити поведінку класу. Важливо те, що, якщо я намагаюся знайти те, що там немає, я отримую виняток. Той факт, що структура даних є ArrayList що відповідає get() це не має значення. Якщо я вирішив в майбутньому перейти до примітивного масиву, мені доведеться змінити цю тестову реалізацію. Структура даних повинна бути прихованою, щоб тест могло зосередити увагу на поведінці клас. - nickbdyer


Редагувати Тепер, коли JUnit5 випустив, найкращим варіантом буде використання Assertions.assertThrows() (побачити моя інша відповідь)

Якщо ви не перейшли до JUnit 5, але можете використовувати JUnit 4.7, ви можете скористатись ExpectedException Правило:

public class FooTest {
  @Rule
  public final ExpectedException exception = ExpectedException.none();

  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    exception.expect(IndexOutOfBoundsException.class);
    foo.doStuff();
  }
}

Це набагато краще, ніж @Test(expected=IndexOutOfBoundsException.class) тому що тест не зможе, якщо IndexOutOfBoundsException кидається раніше foo.doStuff()

Побачити Ця стаття для подробиць


1166
2018-05-29 17:16



@sahffman - Якщо я це правильно зрозумів, то виглядає як виняток. expect застосовується лише протягом одного тесту, а не всього класу. - bacar
Якщо виключення, яке, як ми очікуємо, буде викинути, є перевіреним винятком, чи слід нам додати кидки або спроби захоплення або перевірити цю ситуацію іншим способом? - MJafar Mash
@MartinTrummer Код не повинен запускатися після foo.doStuff (), оскільки виключення виключено, і метод виходить із системи. Наявність коду після очікуваного винятку (за винятком закриття ресурсів нарешті) все одно непридатне, оскільки воно ніколи не повинно бути виконане, якщо виключено. - Jason Thompson
Це найкращий підхід. Тут є дві переваги порівняно з рішенням Шальфмана. По-перше, ExpectedException клас має способи узгодження повідомлення з виключенням або навіть написання власного макета, який залежить від класу винятку. По-друге, ви можете встановити очікування безпосередньо перед рядком коду, який, як ви очікуєте, викидає виняток - це означає, що ваш тест не буде виконаний, якщо невірна рядок коду скине виняток; тоді як з рішенням Шальфмана неможливо це зробити. - Dawood ibn Kareem
@MJafarMash, якщо виявлено виключення, яке ви очікуєте кинути, тоді ви додаєте це виключення з поля "throws" методу перевірки. Ви робите те ж саме в будь-який час, коли ви проводите тестування методу, який оголошується викидом перевіреного винятку, навіть якщо виняток не спрацьовує в конкретному тестовому випадку. - NamshubWriter


Будьте обережні, використовуючи очікуване виключення, оскільки він лише стверджує, що метод кинув це виняток, а не особлива лінія коду в тесті

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

try {
    methodThatShouldThrow();
    fail( "My method didn't throw when I expected it to" );
} catch (MyException expectedException) {
}

Застосувати судження


409
2017-10-01 09:31



Може, я стара школа, але я все таки віддаю перевагу цьому. Це також дає мені місце для перевірки самого винятку: іноді у мене є винятки з getter для певних значень, або я можу просто шукати певне значення в повідомленні (наприклад, шукати "xyz" у повідомленні "невизнаний код" xyz " ") - Rodney Gitzel
Я думаю, підхід NamshubWriter дає вам краще з обох світів. - Eddie
+1 корисно в деяких сценаріях, де очікується = xx не відповідає вимогам. - Oh Chin Boon
Використовуючи ExpectedException, ви можете викликати N exception.expect для кожного методу, щоб перевірити, як цей виняток. Expect (IndexOutOfBoundsException.class); foo.doStuff1 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff2 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff3 (); - user1154664
@ user1154664 Насправді, ви не можете. Використовуючи ExpectedException, ви можете перевірити, що один метод видає виняток, тому що, коли цей метод викликається, тест буде припинено, оскільки він викинув очікуване виключення! - NamshubWriter


Як відповіли раніше, у JUnit існує безліч способів розгляду винятків. Але з Java 8 є ще один: використовуючи Lambda Expressions. Використовуючи Lambda Expressions, ми можемо досягти такого синтаксису:

@Test
public void verifiesTypeAndMessage() {
    assertThrown(new DummyService()::someMethod)
            .isInstanceOf(RuntimeException.class)
            .hasMessage("Runtime exception occurred")
            .hasMessageStartingWith("Runtime")
            .hasMessageEndingWith("occurred")
            .hasMessageContaining("exception")
            .hasNoCause();
}

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

Це відносно проста, але потужна техніка.

Подивіться на це повідомлення блогу, що описує цю техніку: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

Вихідний код можна знайти тут: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

Розкриття інформації: Я є автором блогу та проекту.


189
2017-07-07 22:35



Мені подобається це рішення, але чи можу я завантажити це з Maven repo? - Selwyn
@Airduster одна реалізація цієї ідеї, яка доступна на Maven є stefanbirkner.github.io/vallado - NamshubWriter
Вони повинні подати це як потяг до JUnit ... - Cristiano Fontes
@ CristianoFontes простий варіант цього API передбачений для JUnit 4.13. Побачити github.com/junit-team/junit/commit/... - NamshubWriter
@ Рафал Боровець технічно new DummyService()::someMethod це MethodHandle, але цей підхід працює однаково добре з лямбда-вираженнями. - Andy


в юніті є чотири способи перевірки винятку.

  • для junit4.x використовуйте необов'язковий "очікуваний" атрибут тестової аноніфікації

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
    
  • Для junit4.x використовуйте правило ExpectedException

    public class XxxTest {
        @Rule
        public ExpectedException thrown = ExpectedException.none();
    
        @Test
        public void testFooThrowsIndexOutOfBoundsException() {
            thrown.expect(IndexOutOfBoundsException.class)
            //you can test the exception message like
            thrown.expectMessage("expected messages");
            foo.doStuff();
        }
    }
    
  • ви також можете використовувати класичний метод try / catch, який широко використовується під структурою junit 3

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        try {
            foo.doStuff();
            fail("expected exception was not occured.");
        } catch(IndexOutOfBoundsException e) {
            //if execution reaches here, 
            //it indicates this exception was occured.
            //so we need not handle it.
        }
    }
    
  • Нарешті, для junit5.x ви також можете використовувати assertThrows наступним чином

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        Throwable exception = assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff());
        assertEquals("expected messages", exception.getMessage());
    }
    
  • так

    • Перший спосіб використовується, коли потрібно лише перевірити тип винятку
    • інші три способи використовуються, коли ви хочете далі тестового повідомлення про виключення
    • якщо ви використовуєте junit 3, то треба вибрати третє
    • якщо вам подобається junit 5, тоді вам сподобався четвертий
  • для отримання додаткової інформації ви можете прочитати цей документ і junit5 керівництво користувача для подробиць.


84
2017-08-05 08:05



Для мене це найкраща відповідь, вона охоплює всі шляхи дуже чітко, дякую! Особисто я продовжую користуватися третьою опцією навіть з Junit4 для читабельності, щоб уникнути порожнього блоку вилову, ви також можете спіймати Throwable і стверджувати тип e - Nicolas Cornette
Чи можна використовувати ExpectedException, щоб чекати перевірений виняток? - miuser
Все це - накопичення трьох основних відповідей. ІМО, ця відповідь не повинна навіть бути опублікована, якщо вона не додає нічого нового. Просто відповідь (популярне запитання) для респ. Доволі даремно. - Paul Samsotha
впевнені, тому що ви можете пройти будь-який тип, отриманий з Trowable до методу ExpectedException.expect. будь ласка, дивіться це підпис. @miuser - walsh


тл; др

  • pre-JDK8: Я буду рекомендувати старе добре try-catch блок

  • post-JDK8: Використовуйте AssertJ або спеціальні лямбдази, щоб стверджувати винятковий поведінка

Незалежно від Junit 4 або JUnit 5.

довга історія

Можна написати себе a Зроби це сам  try-catch заблокувати або використовувати інструменти JUnit (@Test(expected = ...) або @Rule ExpectedException Функція правила JUnit);

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

  1. The try-catch заблокувати ви повинні написати блок навколо перевіреної поведінки та написати твердження в блоці зловити, що може бути добре, але багато хто вважає, що цей стиль перериває потік читання тесту. Також вам потрібно написати Assert.fail в кінці кінця try в іншому випадку тест може пропустити одну сторону тверджень; PMD, findbugs або Сонар з'явиться така проблема.

  2. The @Test(expected = ...) Особливість цікава, оскільки ви можете писати менше коду, і тоді написання цього тесту, мабуть, менш схильні до кодування помилок. Але Цей підхід відсутній в деяких областях.

    • Якщо тестові потрібно перевірити додаткові речі за винятком, як причину або повідомлення (хороші повідомлення про виключення дійсно важливі, точного виняткового типу може бути недостатньо).
    • Також, як сподівається, що в методі, залежно від того, як написано перевірені коди, то неправильна частина тестового коду може викинути виняток, що призведе до хибного позитивного тесту, і я не впевнений, що PMD, findbugs або Сонар дасть натяки на такий код.

      @Test(expected = WantedException.class)
      public void call2_should_throw_a_WantedException__not_call1() {
          // init tested
          tested.call1(); // may throw a WantedException
      
          // call to be actually tested
          tested.call2(); // the call that is supposed to raise an exception
      }
      
  3. The ExpectedException правило також є спробою виправити попередні застереження, але це відчуває себе трохи незручно використовувати, оскільки воно використовує стиль очікування, EasyMock Користувачі дуже добре знають цей стиль. Це може бути зручно для деяких, але якщо ви будете слідувати Поведінка, керована розвитком (BDD) або Упорядкувати заяву (AAA) принципи ExpectedException правило не вписується в ці стилі письма. Крім того, він може постраждати з тієї самої проблеми, що і в @Test спосіб, залежно від того, де ви покладете очікування.

    @Rule ExpectedException thrown = ExpectedException.none()
    
    @Test
    public void call2_should_throw_a_WantedException__not_call1() {
        // expectations
        thrown.expect(WantedException.class);
        thrown.expectMessage("boom");
    
        // init tested
        tested.call1(); // may throw a WantedException
    
        // call to be actually tested
        tested.call2(); // the call that is supposed to raise an exception
    }
    

    Навіть очікуване виняток ставиться до тестового викладу, він порушує ваш потік читання, якщо тести слідують за BDD або AAA.

    Також див. Це коментар випуск на JUnit автора ExpectedException.

Отже, ці вищевикладені варіанти мають всі свої застереження, і, безумовно, не застраховані від помилок кодера.

  1. У мене є проект, який я зрозумів після створення цієї відповіді, яка виглядає багатообіцяючою вилучення зловити.

    Як пояснюється в описі проекту, він дозволяє кодеру писати у вільному рядку коду, що ловить виняток, і запропонувати це виключення для подальшого підтвердження. І ви можете використовувати будь-яку бібліотеку тверджень типу Hamcrest або AssertJ.

    Швидкий приклад з домашньої сторінки:

    // given: an empty list
    List myList = new ArrayList();
    
    // when: we try to get the first element of the list
    when(myList).get(1);
    
    // then: we expect an IndexOutOfBoundsException
    then(caughtException())
            .isInstanceOf(IndexOutOfBoundsException.class)
            .hasMessage("Index: 1, Size: 0") 
            .hasNoCause();
    

    Як видно, що код дійсно простий, ви спіймаєте виняток у певній рядку then API - псевдонім, який буде використовувати API AssertJ (подібно до використання assertThat(ex).hasNoCause()...) В якийсь момент проект спирався на FEST-Assert, предка AssertJ. EDIT: Схоже, що проект запускає підтримку Java 8 Lambda.

    В даний час ця бібліотека має два недоліки:

    • На момент написання цього листа варто відзначити, що ця бібліотека заснована на Mockito 1.x, оскільки вона створює зловмисник випробуваного об'єкта за сценою. Оскільки Mockito ще не оновлено ця бібліотека не може працювати з остаточними класами або кінцевими методами. І навіть якщо це було засновано на mockito 2 у поточній версії, для цього потрібно було б оголосити глобального виробника (inline-mock-maker), щось може бути не те, що ви хочете, тому що цей маклер має різні недоліки, що є звичайними мак-мешканцями.

    • Це вимагає ще однієї тестової залежності.

    Ці проблеми не застосовуватимуться, коли бібліотека буде підтримувати лямбдази, однак функціональність буде дублюватися набором інструментів AssertJ.

    Враховуючи все це, якщо ви не хочете використовувати інструмент вилучення, я рекомендую старий хороший спосіб try-catch блок, принаймні до JDK7. І для користувачів JDK 8, ви можете вважають за краще використовувати AssertJ, як це пропонує, більше, ніж просто затвердження винятків.

  2. З JDK8, лямбдади входять на сцену тесту, і вони виявилися цікавим способом утвердження виняткової поведінки. AssertJ був оновлений, щоб забезпечити приємний вільно API для підтвердження виняткової поведінки.

    І зразок тесту з AssertJ :

    @Test
    public void test_exception_approach_1() {
        ...
        assertThatExceptionOfType(IOException.class)
                .isThrownBy(() -> someBadIOOperation())
                .withMessage("boom!"); 
    }
    
    @Test
    public void test_exception_approach_2() {
        ...
        assertThatThrownBy(() -> someBadIOOperation())
                .isInstanceOf(Exception.class)
                .hasMessageContaining("boom");
    }
    
    @Test
    public void test_exception_approach_3() {
        ...
        // when
        Throwable thrown = catchThrowable(() -> someBadIOOperation());
    
        // then
        assertThat(thrown).isInstanceOf(Exception.class)
                          .hasMessageContaining("boom");
    }
    
  3. З майже повним переписом JUnit 5, були твердження покращився Трохи, вони можуть виявитися цікавими, як з коробкового способу правильно стверджувати виключення. Але насправді апеляційний API все ще трохи бідний, немає нічого на вулиці assertThrows.

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }
    

    Як ви помітили assertEquals все ще повертається void, і як така не дозволяє з'єднувати твердження, як AssertJ.

    Також, якщо ви пам'ятаєте назву конфлікту з Matcher або Assert, будьте готові зустріти таку ж сутичку Assertions.

Я хотів би зробити висновок, що сьогодні (2017-03-03) AssertJлегкість використання, відкривається API, швидкі темпи розвитку і як де факто тестова залежність є найкращим рішенням з JDK8 незалежно від тестової структури (JUnit чи ні), попередні JDK повинні замість цього спиратися try-catch блоки, навіть якщо вони почуваються незграбними.

Ця відповідь була скопійована з інше питання які не мають такої ж видимості, я той самий автор.


58
2017-12-07 14:19



Додавання org.junit.jupiter: junit-jupiter-engine: 5.0.0-RC2 залежність (на додаток до вже існуючого junit: junit: 4.12), щоб мати можливість використовувати assertThrows, можливо, не найкращим рішенням, але не викликало жодного питання для мене. - anre
Я шанувальник використання правила ExpectedException, але це завжди мене турбувало, що він порушує AAA. Ви написали відмінну статтю, щоб описати всі різні підходи, і ви напевно рекомендували мені спробувати AssertJ :-) Спасибі! - Pim Hazebroek
@PimHazebroek спасибі AssertJ API досить багатий. Краще, на мій погляд, те, що пропонує Юніт з коробки. - Brice


BDD Стиль Рішення: Юніт 4 + Спіймати виняток + AssertJ

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(foo).doStuff();

    then(caughtException()).isInstanceOf(IndexOutOfBoundsException.class);

}

Вихідний код

Залежності

eu.codearte.catch-exception:catch-exception:1.3.3

31
2017-11-15 19:17





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

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

  try {
    foo.doStuff();
  } catch (Throwable ex) {
    e = ex;
  }

  assertTrue(e instanceof IndexOutOfBoundsException);
}

30
2017-10-01 07:03



Це те, що ви робите в JUnit 3. Junit 4 робить це краще. - skaffman
Крім того, ви не побачите, який випадок Exception коли-небудь в результатах тесту, коли день прийде, де тест не вдається. - jontejj


Використання а AssertJ твердження, яке можна використовувати разом з JUnit:

import static org.assertj.core.api.Assertions.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  Foo foo = new Foo();

  assertThatThrownBy(() -> foo.doStuff())
        .isInstanceOf(IndexOutOfBoundsException.class);
}

Це краще, ніж @Test(expected=IndexOutOfBoundsException.class)тому що він гарантує, що очікувана лінія в тесті викинула виняток, і ви зможете перевірити докладнішу інформацію про виключення, таке як повідомлення, простіше:

assertThatThrownBy(() ->
       {
         throw new Exception("boom!");
       })
    .isInstanceOf(Exception.class)
    .hasMessageContaining("boom");

Maven / Видаліть інструкції тут.


30
2018-03-05 11:07



найкоротший спосіб, і ніхто не цінує це, дивно ... У мене є тільки одна проблема з бібліотекою assertJ, стверджую, що конфлікти з ім'ям junit's. більше про те, як стверджувати: JUnit: Тестування винятків з Java 8 і AssertJ 3.0.0 ~ Codeleak.pl - ycomp
@ycomp Ну, це нова відповідь на дуже старе питання, тому різниця в балах є оманливою. - weston
Це, мабуть, найкраще рішення, якщо можна використовувати Java 8 і AssertJ! - Pierre Henry
@ycomp Я підозрюю, що це конфлікт назви може бути за дизайном: бібліотека AssertJ чітко рекомендує вам ніколи не використовувати JUnit assertThat, завжди Одержуй один. Крім того, метод JUnit повертає лише "звичайний" тип, тоді як метод AssertJ повертає AbstractAssert підклас ... дозволяючи накладати наведені вище методи (або будь-які технічні умови для цього ...). - mike rodent
@Weston я просто використовував вашу техніку в AssertJ 2.0.0. Ніякого виправдання для не модернізації, без сумніву, але хоча ви, можливо, хотіли б знати. - mike rodent


Щоб вирішити таку ж проблему, я створив невеликий проект: http://code.google.com/p/catch-exception/

Використовуючи цей маленький помічник, ви пишете

verifyException(foo, IndexOutOfBoundsException.class).doStuff();

Це менш вербально, ніж правило ExpectedException з JUnit 4.7. У порівнянні з рішенням, наданим скафманом, ви можете вказати, в якій рядку коду ви очікуєте виняток. Я сподіваюсь, це допоможе.


29
2017-10-28 09:26



Я думав про те, що робити щось подібне, але, нарешті, виявив, що справжня сила ExpectedException полягає в тому, що ви можете не тільки вказати очікуване виключення, але також можна вказати певні властивості виключення, такі як очікувана причина або очікуване повідомлення. - Jason Thompson
Ви також можете це зробити caughtException - Martin Trummer
Я думаю, що це рішення має ті ж недоліки, як моторошні? Наприклад, якщо foo є final це не спрацює, оскільки ви не можете проксі-сервер foo? - Tom
Том, якщо doStuff () є частиною інтерфейсу, проксі-підхід буде працювати. Інакше цей підхід буде невдалим, ви маєте рацію. - rwitzel