Статьи

AssertJ’s SoftAssertions — они нам нужны?

Одно из правил написания хорошего модульного теста состоит в том, что он должен потерпеть неудачу по одной причине, поэтому модульный тест должен проверить одну логическую концепцию. Иногда довольно сложно иметь одно утверждение на тест. Чтобы следовать правилу, мы можем иметь несколько утверждений на объект в одном тесте.

Однако проблема с несколькими утверждениями в одном тесте заключается в том, что если первое по какой-либо причине не удается, мы фактически не знаем о других утверждениях, поскольку они не будут выполнены. И вы знаете упражнение: вы проверяете причину ошибки утверждения, исправляете ее и повторно запускаете тест. Может быть, вам повезет, и испытание пройдет. Но, может быть, это не удастся с другим утверждением. С очень быстрыми юнит-тестами это не является большой проблемой, но когда дело касается, например, анализа тестов Selenium и обнаружения сбоев, они могут стать громоздкими и требующими много времени.

К счастью, мы можем переосмыслить способ создания утверждений в наших тестах благодаря SoftAssertions SoftAssertions .

Одно утверждение, чтобы управлять ими всеми!

В гипотетической игре в Dice есть объект Score который содержит значение счета, комбинацию игральных костей и напоминание. В модульных тестах мы можем захотеть проверить, как рассчитывается оценка для другой комбинации костей.

В приведенном ниже примере проверяется одна концепция — объект оценки:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@Test
public void verifiesScore() {
    Score score = Score.scoreBuilder()
                       .withValue(11)
                       .withCombination(dice(1, 1, 3, 4))
                       .withReminder(dice(6))
                       .build();
 
    assertThat(score.getValue())
        .as("Has score")
        .isEqualTo(8);
 
    assertThat(score.getCombination())
        .as("Has combination")
        .isEqualTo(dice(1, 1, 3, 3));
 
    assertThat(score.getReminder())
        .as("Has reminder")
        .isEqualTo(dice(5));
}

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

1
2
3
org.junit.ComparisonFailure: [Has score]
Expected :8
Actual   :11

Представляем SoftAssertions

Чтобы исправить это, мы можем использовать SoftAssertions которые будут собирать результат всех утверждений сразу после вызова assertAll() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void verifiesScoreSoftly() {
    Score score = Score.scoreBuilder()
                       .withValue(11)
                       .withCombination(dice(1, 1, 3, 4))
                       .withReminder(dice(6))
                       .build();
 
    SoftAssertions softAssertions = new SoftAssertions();
 
    softAssertions.assertThat(score.getValue())
                  .as("Has score")
                  .isEqualTo(8);
    softAssertions.assertThat(score.getCombination())
                  .as("Has combination")
                  .isEqualTo(dice(1, 1, 3, 3));
    softAssertions.assertThat(score.getReminder())
                  .as("Has reminder")
                  .isEqualTo(dice(5));
 
    softAssertions.assertAll();
}

Теперь мы можем проверить все ошибки утверждений в тесте:

1
2
3
4
5
org.assertj.core.api.SoftAssertionError:
The following 3 assertions failed:
1) [Has score] expected:<[8]> but was:<[11]>
2) [Has combination] expected:<...alue=3}, Dice{value=[3]}]> but was:<...alue=3}, Dice{value=[4]}]>
3) [Has reminder] expected:<[Dice{value=[5]}]> but was:<[Dice{value=[6]}]>

JUnitSoftAssertions @Rule

Вместо того, чтобы вручную создавать SoftAssertions и вызывать его assertAll() мы можем использовать JUnit @Rule :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
@Rule
public JUnitSoftAssertions softAssertions = new JUnitSoftAssertions();
 
@Test
public void verifiesScoreSoftlyUsingRule() {
    Score score = Score.scoreBuilder()
                       .withValue(11)
                       .withCombination(dice(1, 1, 3, 4))
                       .withReminder(dice(6))
                       .build();
 
    softAssertions.assertThat(score.getValue())
                  .as("Has score")
                  .isEqualTo(8);
    softAssertions.assertThat(score.getCombination())
                  .as("Has combination")
                  .isEqualTo(dice(1, 1, 3, 3));
    softAssertions.assertThat(score.getReminder())
                  .as("Has reminder")
                  .isEqualTo(dice(5));
}

Нам не только не нужно помнить о вызове assertAll() но мы также можем видеть потенциальные ошибки в редакторе сравнения в IntelliJ:

0B0b09VuqaAG8WGxhUUhxdmZfa3M

Пользовательский SoftScoreAssertion

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@Test
public void verifiesScoreSoftlyWithCustomAssertion() {
 
    Score score = Score.scoreBuilder()
                       .withValue(11)
                       .withCombination(dice(1, 1, 3, 4))
                       .withReminder(dice(6))
                       .build();
 
    SoftScoreAssertion.assertThat(score)
                      .hasValue(8)
                      .hasCombination(dice(1, 1, 3, 3))
                      .hasReminder(dice(5))
                      .assertAll();
}

SoftScoreAssertion использует SoftAssertions и поэтому мы все равно увидим все ошибки утверждений одновременно. И код:

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
class SoftScoreAssertion extends AbstractAssert<SoftScoreAssertion, Score> {
 
    private SoftAssertions softAssertions = new SoftAssertions();
 
    protected SoftScoreAssertion(Score actual) {
        super(actual, SoftScoreAssertion.class);
    }
 
    public static SoftScoreAssertion assertThat(Score actual) {
        return new SoftScoreAssertion(actual);
    }
 
    public SoftScoreAssertion hasValue(int scoreValue) {
        isNotNull();
        softAssertions.assertThat(actual.getValue())
                      .as("Has score")
                      .isEqualTo(scoreValue);
        return this;
    }
 
    public SoftScoreAssertion hasReminder(List<Dice> expected) {
        isNotNull();
        softAssertions.assertThat(actual.getReminder())
                      .as("Has reminder")
                      .isEqualTo(expected);
        return this;
    }
 
   public SoftScoreAssertion hasCombination(List<Dice> expected) {
        isNotNull();
        softAssertions.assertThat(actual.getCombination())
                      .as("Has combination")
                      .isEqualTo(expected);
        return this;
    }
 
    @Override
    public SoftScoreAssertion isNotNull() {
        softAssertions.assertThat(actual).isNotNull();
        return this;
    }
 
    public void assertAll() {
        this.softAssertions.assertAll();
    }
}

Ресурсы

Исходный код