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 обгръща предавания обект и поставя блок за опит около всеки метод. Предупрежденията на 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 в друга функция... - 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 btw. - 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