Зачем создавать механизм для ожидания неудачного теста?
Наступает время, когда хочется и ожидать, что дело 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 может охватывать дополнительные утверждения, и до тех пор, пока не будут введены будущие усовершенствования, рекомендуется использовать эту методологию с умом.