Можно ли использовать 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...
Для дальнейшего чтения: