Пометить юнит-тест как ожидаемый сбой в JUnit - PullRequest
40 голосов
/ 29 октября 2010

Как пометить тест как ожидаемый сбой в JUnit 4?

В этом случае я хочу продолжать запускать этот тест, пока что-то не будет исправлено. Игнорирование теста заходит слишком далеко, и тогда я могу забыть об этом. Возможно, я смогу добавить аннотацию @expected и отловить исключение, выданное assertThat, но это, похоже, также относится к ожидаемому поведению.

Вот как выглядит мой текущий тест:

@Test
public void unmarshalledDocumentHasExpectedValue() 
{
    doc = unmarshaller.unmarshal(getResourceAsStream("mydoc.xml"));
    final ST title = doc.getTitle();
    assertThat(doc.getTitle().toStringContent(), equalTo("Expected"));
}

Это утверждение должно быть успешным, но из-за ошибки в исходной версии это не так. Тем не менее, этот тест является правильным; это должно преуспеть. Практически все альтернативы, которые я нашел, вводят в заблуждение. Сейчас я думаю, что @Ignore("This test should pass once fixed upstream") - моя лучшая ставка, но я все еще должен помнить, чтобы вернуться к ней. Я бы предпочел, чтобы тестовый прогон.

В Python я могу использовать декоратор Ожидаемый сбой :

class ExpectedFailureTestCase(unittest.TestCase):
    @unittest.expectedFailure
    def test_fail(self):
        self.assertEqual(1, 0, "broken")

С Qt's QTestLib в C ++ вы можете использовать QEXPECT_FAIL :

QEXPECT_FAIL("", "Will be fixed next version", Continue);
QCOMPARE(i, 42);

В обоих случаях выше, запускается модульное тестирование, что я и надеюсь. Я что-то упустил в JUnit?

Ответы [ 6 ]

24 голосов
/ 29 октября 2010

Я не совсем понимаю специфику вашего сценария, но вот как я обычно проверяю ожидаемый сбой:

Новый способ:

@Test(expected=NullPointerException.class)
public void expectedFailure() {
    Object o = null;
    o.toString();
}

для более старых версий JUnit:

public void testExpectedFailure() {
    try {
        Object o = null;
        o.toString();
        fail("shouldn't get here");
    }
    catch (NullPointerException e) {
        // expected
    }
}

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

17 голосов
/ 11 ноября 2011

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

Самый простой способ сделать это - использовать TestRule .TestRule дает возможность выполнить код до и после запуска тестового метода.Вот пример:

public class ExpectedFailureTest {
    public class ExpectedFailure implements TestRule {
        public Statement apply(Statement base, Description description) {
            return statement(base, description);
        }

        private Statement statement(final Statement base, final Description description) {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    try {
                        base.evaluate();
                    } catch (Throwable e) {
                        if (description.getAnnotation(Deprecated.class) != null) {
                            // you can do whatever you like here.
                            System.err.println("test failed, but that's ok:");
                        } else {
                            throw e;
                        }
                    }
                }
            };
        }
    }

    @Rule public ExpectedFailure expectedFailure = new ExpectedFailure();

    // actually fails, but we catch the exception and make the test pass.
    @Deprecated
    @Test public void testExpectedFailure() {
        Object o = null;
        o.equals("foo");
    }

    // fails
    @Test public void testExpectedFailure2() {
        Object o = null;
        o.equals("foo");
    }
}

Во-первых, обратите внимание, что первый метод помечен как @Deprecated.Я использую это как маркер для метода, для которого я хочу игнорировать любые ошибки утверждения.Вы можете делать все, что захотите, чтобы определить методы, это всего лишь пример.

Далее, в ExpectedFailure#apply(), когда я делаю base.evaluate (), я ловлю любой Throwable (который включает в себяAssertionError), и если метод помечен аннотацией @Deprecated, я игнорирую ошибку.Вы можете выполнить любую логику, какую захотите, чтобы решить, следует ли игнорировать ошибку или нет, основываясь на номере версии, некотором тексте и т. Д. Вы также можете передать динамически определенный флаг в ExpectedFailure, чтобы он не работал для определенных номеров версий:

public void unmarshalledDocumentHasExpectedValue() {
    doc = unmarshaller.unmarshal(getResourceAsStream("mydoc.xml"));

    expectedFailure.setExpectedFailure(doc.getVersionNumber() < 3000);

    final ST title = doc.getTitle();
    assertThat(doc.getTitle().toStringContent(), equalTo("Expected"));
}

Дополнительные примеры см. В ExternalResource и ExpectedException

Игнорирование теста ожидаемого сбоя вместо его прохождения

ЕслиЕсли вы хотите пометить ваши тесты как «Пропущенные», а не как «Успешные», они становятся немного более сложными, потому что тесты игнорируются перед их выполнением, поэтому вам необходимо ретроспективно пометить тест как пропущенный, что потребует создания собственного Runner.Для начала, смотрите мой ответ на Как определить правило метода JUnit в комплекте? .Или задайте другой вопрос.

15 голосов
/ 10 ноября 2011

Как насчет явного ожидания ошибки AssertionError?

@Test(expected = AssertionError.class)
public void unmarshalledDocumentHasExpectedValue() {
    // ...
}

Если вы достаточно уверены, что только механизм JUnit в тесте вызовет AssertionError, это кажется самодокументированным, как и все.

Вы все равно рискуете забыть о таком тесте. Я бы не стал допускать такие тесты для контроля версий надолго, если вообще когда-либо.

7 голосов
/ 29 октября 2010

Один из вариантов - пометить тест как @ Ignore и вставить туда текст, который, возможно, является ошибкой и ожидает исправления.Таким образом, это не будет работать.Затем он будет пропущен.Вы также можете использовать расширения , чтобы удовлетворить ваши потребности потенциально другим способом.

4 голосов
/ 17 сентября 2013

Я сделал еще один шаг к ответу Мэтью и фактически внедрил аннотацию @Optional, которую можно использовать вместо аннотации @Deprecated, которую он упоминает в своем ответе. Несмотря на простоту, я поделюсь с вами кодом, может быть, он кому-нибудь поможет:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Optional {

  /**
   * Specify a Throwable, to cause a test method to succeed even if an exception
   * of the specified class is thrown by the method.
   */
  Class<? extends Throwable>[] exception();
}

С простой переделкой класса Мэтта ExpectedFailure:

public class ExpectedFailure implements TestRule {

  @Override
  public Statement apply(final Statement base, final Description description) {
    return statement(base, description);
  }

  private Statement statement(final Statement base, final Description description) {
    return new Statement() {

      @Override
      public void evaluate() throws Throwable {
        try {
          base.evaluate();
        } catch (Throwable e) {
          // check for certain exception types
          Optional annon = description.getAnnotation(Optional.class);
          if (annon != null && ArrayUtils.contains(annon.exception(), e.getClass())) {
            // ok
          } else {
            throw e;
          }
        }
      }
    };
  }
}

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

public class ExpectedFailureTest {

  @Rule public ExpectedFailure expectedFailure = new ExpectedFailure();

  // actually fails, but we catch the exception and make the test pass.
  @Optional(exception = NullPointerException.class)
  @Test public void testExpectedFailure() {
      Object o = null;
      o.equals("foo");
  }

}

[UPDATE]

Вы также можете переписать свои тесты, используя org.junit.Assume JUnit вместо традиционной org.junit.Assert, если вы хотите, чтобы ваши тесты проходили, даже если предположение не выполняется.

Из Assume JavaDoc:

Набор методов, полезных для формулирования предположений об условиях, в которых тест имеет смысл. Неудачное предположение не означает, что код нарушен, но что тест не дает полезной информации. Средство выполнения JUnit по умолчанию обрабатывает тесты с ошибочными предположениями как игнорируемые.

Assume доступно с JUnit 4.4

1 голос
/ 01 ноября 2010

Используйте по возможности смоделированный восходящий класс. Заглушка с правильным результатом. При желании замените макет реальным объектом после исправления ошибки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...