2013-07-18 4 views
109

Я знаю, что один из способов сделать это будет:Как проверить, что исключение не выбрасывается?

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

+6

тест JUnit оценивается несостоявшимся, если он выбрасывает любое исключение, кроме ожидаемого исключения. Обычно исключений не ожидается. – Raedwald

+0

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

+0

Возможный дубликат [Как проверить, не вызвано ли какое-то конкретное исключение?] (Http://stackoverflow.com/questions/8575653/how-can-i-test-if-a-particular-exception-is-not -thrown) –

ответ

124

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

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

фон для модульного тестирования

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

Или, как это определено в The art of Unit Testing, 2nd Edition by Roy Osherove, страница 11:

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

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

enter image description here

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

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

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

Обращение с ИСПЫТАНИЙ действительный и неисправный вход

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

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

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

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

TL; DR

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

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

+1

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

+5

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

+0

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

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 в этом случае.

14

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

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

Просто небольшое предложение 'Exception ex' должно быть' = null; 'перед тем, как вы сможете его протестировать. – Denees

+4

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

+3

Или просто поместите Assert.fail() в catch, проще и красивее IMO. –

25

Java 8 делает это намного проще, и Котлин/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() 
} 

Хотя есть много места, чтобы играть с, как именно вы хотите, чтобы выразить это, я всегда был поклонником fluent assertions.


Что касается

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

Это верно в принципе, но неверно в заключение.

Java допускает исключения для потока управления. Это делается самим временем выполнения JRE в API, например Double.parseDouble, через NumberFormatException и Paths.get через InvalidPathException.

Учитывая, что вы написали компонент, который проверяет числовые строки для Double.ParseDouble, возможно, с использованием регулярного выражения, возможно, написанного вручную анализатора или, возможно, что-то, что включает в себя некоторые другие правила домена, которые ограничивают диапазон двойным чем-то конкретным, как лучше всего проверить этот компонент? Я думаю, что очевидным тестом было бы утверждать, что, когда результирующая строка анализируется, исключение не генерируется. Я бы написал этот тест, используя либо вышеописанный блок 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, так что вы можете легко повторно использовать этот тест для других входов. Кроме того, если вы хотите пойти экзотично, вы можете пойти на test-generation toolthis). TestNG имеет лучшую поддержку параметризованных тестов.

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

+1

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

2

Использование assertNull(...)

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

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

+0

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

0

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

@Test 
public void testMyCode() { 

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

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

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

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

+4

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

+0

@ SvenDöring как указать это в 'testng' –

0

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

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

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

+0

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

+0

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

1

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

enter image description here

Смежные вопросы