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