Статьи

JUnit Pass Тестовый пример на отказы

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

Наступает время, когда хочется и ожидать, что дело 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 Майка в блоге сайта Майка .