Статьи

Разрешение тестам JUnit проходить тестовый набор при сбоях

Зачем создавать механизм для ожидания неудачного теста?

Наступает время, когда хочется и ожидать, что дело JUnit @Test завершится неудачей. Хотя это довольно редко, это случается. Мне нужно было определить, когда тест JUnit не проходит, а затем, если ожидается, пройти вместо сбоя. Конкретным случаем было то, что я тестировал фрагмент кода, который мог выдать ошибку Assert внутри вызова объекта. Код был написан как дополнение к популярной новой платформе Fest Assertions , поэтому для тестирования функциональности можно ожидать, что тестовые случаи не пройдут специально.

Решение

Одним из возможных решений является использование функциональности, предоставляемой JUnit @Rule в сочетании с пользовательским маркером в форме аннотации.

,

Зачем использовать @Rule?

Объекты @Rule предоставляют AOP-подобный интерфейс для класса тестирования и каждого теста. Правила сбрасываются перед выполнением каждого тестового примера, и они раскрывают работу тестового примера в стиле рекомендации @Around AspectJ.

Обязательные элементы кода

  • Объект @Rule для проверки состояния каждого случая @Test
  • @ExpectedFailure пользовательская аннотация маркера
  • Тестовые случаи, доказывающие работоспособность кода!
  • Необязательное специальное исключение, которое будет выдано, если аннотированный контрольный пример не завершится неудачей

ПРИМЕЧАНИЕ: рабочий код доступен на моей странице github и скоро будет в Maven Central. Не стесняйтесь, чтобы Форк проекта и отправить запрос на извлечение

Пример использования

В этом примере объект «исключение» — это расширенное утверждение Fest ExpectedException (посмотрите мою следующую публикацию, чтобы раскрыть эту функциональность). Ожидаемое исключение сделает утверждения, и для их проверки тестовый пример должен быть помечен как @ExpectedFailure.

public class ExceptionAssertTest {

    @Rule
    public ExpectedException exception = ExpectedException.none();

    @Rule
    public ExpectedTestFailureWatcher watcher = ExpectedTestFailureWatcher.instance();

    @Test
    @ExpectedFailure("The matcher should fail becasue exception is not a SimpleException")
    public void assertSimpleExceptionAssert_exceptionIsOfType() {
        // expected exception will be of type "SimpleException"
        exception.instanceOf(SimpleException.class);
        // throw something other than SimpleException...expect failure
        throw new RuntimeException("this is an exception");
    }
}

Внедрение решения

Напоминание, последний код доступен на моей странице GitHub .

Код @Rule (ExpectedTestFailureWatcher.java)

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
// YEAH Guava!!
import static com.google.common.base.Strings.isNullOrEmpty;

public class ExpectedTestFailureWatcher implements TestRule {

    /**
     * Static factory to an instance of this watcher
     *
     * @return New instance of this watcher
     */
    public static ExpectedTestFailureWatcher instance() {
        return new ExpectedTestFailureWatcher();
    }

    @Override
    public Statement apply(final Statement base, final Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                boolean expectedToFail = description.getAnnotation(ExpectedFailure.class) != null;
                boolean failed = false;
                try {
                    // allow test case to execute
                    base.evaluate();
                } catch (Throwable exception) {
                    failed = true;
                    if (!expectedToFail) {
                        throw exception; // did not expect to fail and failed...fail
                    }
                }
                // placed outside of catch
                if (expectedToFail && !failed) {
                    throw new ExpectedTestFailureException(getUnFulfilledFailedMessage(description));
                }
            }

            /**
             * Extracts detailed message about why test failed
             * @param description
             * @return
             */
            private String getUnFulfilledFailedMessage(Description description) {
                String reason = null;
                if (description.getAnnotation(ExpectedFailure.class) != null) {
                    reason = description.getAnnotation(ExpectedFailure.class).reason();
                }
                if (isNullOrEmpty(reason)) {
                    reason = "Should have failed but didn't";
                }
                return reason;
            }
        };
    }
}

Пользовательская аннотация @ExpectedFailure (ExpectedFailure.java)

import java.lang.annotation.*;

/**
 * Initially this is just a marker annotation to be used by a JUnit4 Test case in conjunction
 * with ExpectedTestFailure @Rule to indicate that a test is supposed to be failing
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
public @interface ExpectedFailure {
    // TODO: enhance by adding specific information about what type of failure expected
    //Class<!--? extends Throwable--> assertType() default Throwable.class;

    /**
     * Text based reason for marking test as ExpectedFailure
     * @return String
     */
    String reason() default "";
}

Настраиваемое исключение (Необязательно, вы можете легко выбросить RuntimeException или существующее настраиваемое исключение)

public class ExpectedTestFailureException extends Throwable {
    public ExpectedTestFailureException(String message) {
        super(message);
    }
}

Разве нельзя использовать способность отмечать сбой, как ожидалось?

С большой силой приходит большая ответственность , рекомендуется, чтобы вы не отмечали тест как @ExpectedFailure, если вы не понимаете, почему именно тест проваливается. Рекомендуется применять этот метод тестирования с осторожностью. НЕ используйте аннотацию @ExpectedFailure в качестве альтернативы @Ignore

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

Известные вопросы

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