Наступает время, когда хочется и ожидать, что дело JUnit @Test завершится неудачей. Хотя это довольно редко, это случается. Мне нужно было определить, когда тест JUnit не проходит, а затем, если ожидается, пройти вместо сбоя. Конкретным случаем было то, что я тестировал фрагмент кода, который мог выдать ошибку Assert внутри вызова объекта. Код был написан как дополнение к популярной новой платформе Fest Assertions , поэтому для тестирования функциональности можно ожидать, что тестовые случаи не пройдут специально.
Решение
Одним из возможных решений является использование функциональности, предоставляемой JUnit @Rule в сочетании с пользовательским маркером в форме аннотации.
Зачем использовать @Rule?
Объекты @Rule предоставляют AOP-подобный интерфейс для класса тестирования и каждого теста. Правила сбрасываются перед выполнением каждого тестового примера, и они раскрывают работу тестового примера в стиле рекомендации @Around AspectJ.
Обязательные элементы кода
- Объект @Rule для проверки состояния каждого случая @Test
- @ExpectedFailure пользовательская аннотация маркера
- Тестовые случаи, подтверждающие работоспособность кода!
- Необязательное специальное исключение, которое будет выдано, если аннотированный контрольный пример не завершится неудачей
ПРИМЕЧАНИЕ: рабочий код доступен на моей странице GitHub и был добавлен в Maven Central. Не стесняйтесь, чтобы Форк проекта и отправить запрос на извлечение Maven Usage
|
1
2
3
4
5
|
<dependency> <groupId>com.clickconcepts.junit</groupId> <artifactId>expected-failure</artifactId> <version>0.0.9</version></dependency> |
Пример использования
В этом примере объект «исключение» — это ExpectedException с расширенным утверждением Fest (посмотрите мою следующую публикацию, чтобы раскрыть эту функцию). Ожидаемое исключение сделает утверждения, и для их проверки тестовый пример должен быть помечен как @ExpectedFailure.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
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)
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
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)
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
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 assertType() default Throwable.class; /** * Text based reason for marking test as ExpectedFailure * @return String */ String reason() default '';} |
Настраиваемое исключение (Необязательно, вы можете легко выбросить RuntimeException или существующее настраиваемое исключение)
|
1
2
3
4
5
|
public class ExpectedTestFailureException extends Throwable { public ExpectedTestFailureException(String message) { super(message); }} |
Разве нельзя использовать способность отмечать сбой, как ожидалось?
С большой силой приходит большая ответственность , рекомендуется, чтобы вы не отмечали тест как @ExpectedFailure, если вы не понимаете, почему именно тест проваливается. Рекомендуется применять этот метод тестирования с осторожностью. НЕ используйте аннотацию @ExpectedFailure в качестве альтернативы @Ignore
Возможные будущие улучшения могут включать способы указания конкретного утверждения или конкретного сообщения, заявленного во время выполнения тестового примера.
Известные вопросы
В этом текущем состоянии аннотация @ExpectedFailure может охватывать дополнительные утверждения, и до тех пор, пока не будут введены будущие усовершенствования, рекомендуется использовать эту методологию с умом.
Ссылка: Разрешение тестам JUnit пройти тестовый случай на отказ от нашего партнера JCG Майка в блоге сайта Майка .