Junit утверждает что-то после ожидания и обработки исключения

метод, который выдает при первом и втором вызове:

public void foo() throws Exception

контрольная работа:

@test
public void testFooThrowsAtFirstAndSecondTime(){
    boolean thrown;
    try {
       foo();
    } catch (Exception e) {
       thrown = true;
    }
    assertTrue(thrown);

    thrown = false;
    try {
       foo();
    } catch (Exception e) {
       thrown = true;
    }
    assertTrue(thrown);

    foo();
}

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

Под «лучше» я имею в виду, если бы я мог избежать попытки/поймать или даже нескольких попыток/поймать в своем тесте. На других языках или в jAssert я думаю, что даже весной есть такие утверждения, как:

assertThrows(method(..)) //PseudoCode

Я думал, что с Mockito или JUnit 4.x есть нечто подобное.

я знаю о

@Test(expected=Exception)

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


person Gobliins    schedule 03.02.2015    source источник
comment
В чем проблема? В чем вопрос? Вам нужно решение именно для ЧЕГО?   -  person Mike Nakis    schedule 03.02.2015
comment
В jAssert или в других языках есть некоторые вещи, такие как assertThrow, которые, помимо того, что они намного более читабельны, я бы нашел еще и как-то более правильным подходом.   -  person Gobliins    schedule 04.02.2015


Ответы (5)


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

Что работает:

  • Проверенная fail() идиома, которую вы и nrainier цитируете, которую я предпочитаю:

    try {
      foo();
      fail("foo did not throw an exception");
    } catch (Exception ex) { } 
    
  • catch-exception — это библиотека, которая, как и Mockito, оборачивает переданный объект и помещает блок try вокруг каждого метода. Предостережения Mockito о конечных методах и классах применимы и здесь, поэтому это не всегда будет работать.

    List myList = new ArrayList();
    catchException(myList).get(1);  // myList is wrapped here
    assert caughtException() instanceof IndexOutOfBoundsException;
    

    Обратите внимание, что catch-exception находится в режиме обслуживания, потому что решение Java 8 (ниже) намного надежнее.

  • Любое решение, например assertThrows(() -> methodThatThrows()) (Java 8) или:

    assertThrows(new Runnable() {
      @Override public void run() { methodThatThrows(); }
    });
    

    ... в Java 6/7. Важно отметить, что assertThrows вызывается перед methodThatThrows, поэтому он может вызывать methodThatThrows. Спасибо Стефану за указание на Fishbowl, но вы могли бы легко написать эквивалент самостоятельно:

    public void assertThrows(Runnable block) {
      try {
        block.run();
        fail("Block didn't throw.");
      } catch (Exception ex) { }
    }
    

Вещи, которые не работают:

  • @Test(expected=YourException.class) поднимется по стеку к блоку try, в который JUnit заключает ваш тестовый метод. После этого управление никогда не возвращается к тестовому методу.

  • Правило ExpectedException @Rule в JUnit4 выглядит заманчиво, но поскольку оно охватывает весь тестовый метод, перед вызовом метода, выдающего исключение, необходимо установить ожидания.

  • Все, что похоже на assertThrows(methodCallThatThrows()). Java попытается получить возвращаемое значение из methodCallThatThrows до того, как будет вызван assertThrows, поэтому любой блок try здесь не может помочь.

person Jeff Bowman    schedule 04.02.2015

Я не думаю, что однострочный вызов метода возможен.

Я бы написал тест так:

@Test
public void testFooThrowsAtFirstAndSecondTime() throws Exception {
  try {
    foo();
    fail("foo did not throw an exception");
  } catch (Exception ex) { }

  try{
    foo(); 
    fail("foo did not throw an exception");
  } catch (Exception ex) { }

  foo();
}
person nrainer    schedule 03.02.2015
comment
Это также было бы правильным решением, но я искал что-то без try/catch внутри моего теста. Может быть, я собираюсь реорганизовать процедуру try/catch в другой функции... - person Gobliins; 04.02.2015

В Java 8 вы можете использовать библиотеку Fishbowl.

@Test
public void testFooThrowsAtFirstAndSecondTime(){
  Throwable firstException = exceptionThrownBy(() -> foo());
  assertEquals(Exception.class, firstException.getClass());

  Throwable secondException = exceptionThrownBy(() -> foo());
  assertEquals(Exception.class, secondException.getClass());

  foo()
}

Эту библиотеку также можно использовать с Java 6 и 7. Но тогда вам придется использовать анонимные классы.

@Test
public void testFooThrowsAtFirstAndSecondTime(){
  Throwable firstException = exceptionThrownBy(new Statement() {
    public void evaluate() throws Throwable {
      foo();
    }
  });
  assertEquals(Exception.class, firstException.getClass());

  Throwable secondException = exceptionThrownBy(new Statement() {
    public void evaluate() throws Throwable {
      foo();
    }
  });
  assertEquals(Exception.class, secondException.getClass());

  foo()
}
person Stefan Birkner    schedule 03.02.2015
comment
В настоящее время я использую Junit 4 и Mockito (PowerMock), я обнаружил, что в Spring или в jAssert есть утверждения assertThrow(..). Но я бы предпочел остаться на моей текущей конфигурации, если это возможно. Мы также работаем над Java 7, кстати. - person Gobliins; 04.02.2015

Если вам не повезло, и вам пришлось кодировать какую-то версию Java до 8, вы не можете сделать это с одной строкой на исключение.

Но если вы используете java 8, вы можете сделать это, как предложил Стефан Биркнер.

Теперь, если вы не хотите включать всю библиотеку только для одного метода, вот метод, который вам подойдет, скопированный из моего блога

public final <T extends Throwable> T expectException( Class<T> exceptionClass, Runnable runnable )
{
    try
    {
        runnable.run();
    }
    catch( Throwable throwable )
    {
        if( throwable instanceof AssertionError && throwable.getCause() != null )
            throwable = throwable.getCause();
        assert exceptionClass.isInstance( throwable ) : throwable; //exception of the wrong kind was thrown.
        assert throwable.getClass() == exceptionClass : throwable; //exception thrown was a subclass, but not the exact class, expected.
        @SuppressWarnings( "unchecked" )
        T result = (T)throwable;
        return result;
    }
    assert false; //expected exception was not thrown.
    return null; //to keep the compiler happy.
}

Итак, ваш тестовый код становится примерно таким:

@Test
public void testFooThrowsAtFirstAndSecondTime()
{
    expectException( Exception.class, this::foo );
    expectException( Exception.class, this::foo );
    foo();
}
person Mike Nakis    schedule 04.02.2015

person    schedule
comment
Итак, выдает ли ваш метод foo() исключения при первом и втором вызове и проходит без исключений при третьем? Я понятия не имею, что делает foo(), но если вам это действительно нужно, ваш тестовый код решит вашу проблему. Решение Mockito будет немного сложнее. Также рассмотрите возможность изменения вашего метода foo(). - person Everv0id; 03.02.2015
comment
Итак, выдает ли ваш метод foo() исключения при первом и втором вызове и проходит без исключений при третьем? Да - person Gobliins; 03.02.2015