Можно ли использовать SpringRunner в модульных тестах?

Мы спорим об этом подходе с моими коллегами. Говорят, использовать SpringRunner только на уровне интеграции или функциональности.

Вопрос в том, какие плюсы и минусы использования его на уровне ниже?

Например, у меня есть простой боб:

public class RewardDurationCalculator {

    private Clock clock;

    public OptionalLong calculate(DurationType durationType, List<Pass> passes) {
        long now = Instant.now(clock).getEpochSecond();
        switch (durationType) {
            case FULL_PASS:
                return getCurrentPassDuration(passes, now);
            case TILL_THE_END_OF_THE_CURRENT_ACTIVE_PASS:
                return getTimeInCurrentPassLeft(passes, now);
        }
        return OptionalLong.empty();
    }

    private OptionalLong getCurrentPassDuration(List<Pass> passes, long now) {
        return passes.stream()
                .filter(currentPass(now))
                .mapToLong(Pass::getDuration)
                .findFirst();
    }

    private OptionalLong getTimeInCurrentPassLeft(List<Pass> passes, long now) {
        return passes.stream()
                .filter(currentPass(now))
                .mapToLong(pass -> getEndTs(pass) - now)
                .findFirst();
    }

    private Predicate<Pass> currentPass(long now) {
        return pass -> pass.getStartTs() >= now && now <= getEndTs(pass);
    }

    private long getEndTs(Pass pass) {
        return pass.getStartTs() + pass.getDuration();
    }

}

это делает некоторую логику расчета. Для этого у меня есть и весенний конфиг:

@Configuration
public class RewardDurationCalculatorConfiguration {

    @Bean
    public RewardDurationCalculator rewardDurationCalculator(Clock clock) {
        return new RewardDurationCalculator(clock);
    }

}

Так почему я не могу написать для него модульный тест:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = RewardDurationCalculatorConfiguration.class)
public class RewardDurationCalculatorTest {

    @MockBean
    private Clock clock;
    @Autowired
    private RewardDurationCalculator rewardDurationCalculator;

    @Test
    public void testCalculateCurrentPassDurationShouldBeReturnedIfPassWasCreatedRightNow() {
        rewardDurationCalculator.calculate(DurationType.FULL_PASS, Collections.emptyList());
    }

}

С какими минусами я могу столкнуться при использовании такого подхода?

2 ответа

Решение

Я склонен согласиться с вашими коллегами.

Модульные тесты должны проверять только небольшие блоки кода, обычно это методы, в идеале работающие только с тестируемым модулем без выполнения какого-либо другого кода (за исключением частных методов).

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

Еще одна причина придерживаться этого правила заключается в том, что оно заставляет вас писать классы с высокой степенью разделения. Если вы не можете написать модульные тесты для класса, в котором используется только этот класс и ничего более, это может быть признаком того, что вам следует переосмыслить дизайн своего класса.

Когда вы наращиваете весь контекст Spring для теста, по этому определению это уже не модульный тест, а интеграционный тест, потому что вы также тестируете всю конфигурацию Spring, самозагрузку, автоматическое подключение и т. Д., То есть интеграцию ваших классов. в приложение Spring.

@SpringRunner следует использовать, если ваш класс использует какие-либо зависимости bean-компонентов, если ваш класс независимый, без каких-либо зависимостей контекста приложения, лучше не использовать @SpringRunner. Если я вижу ваш класс 'RewardDurationCalculator', он не использует никаких зависимостей, он не должен использовать @SpringRunner и даже на самом деле Mocks...

Для дальнейшего чтения:

  1. https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/context/junit4/SpringRunner.html
Другие вопросы по тегам