4

We are arguing about this approach with my colleagues. They say to use SpringRunner only on integration or functional levels.

The question is what pros and cons of using it in level below?

For example I have simple bean:

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();
    }

}

that is doing some calculation logic. For it I have also spring config:

@Configuration
public class RewardDurationCalculatorConfiguration {

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

}

So why can't I write unit test for it like this:

@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());
    }

}

What cons I can face with using such approach?

4

2 回答 2

11

我倾向于同意你的同事的观点。

单元测试应该只测试小的代码单元,通常是单个类。他们应该只执行被测单元,而不执行单元依赖项中的任何代码。

这样做的一个原因是单元测试应该尽可能快地执行。在测试驱动开发 (TDD) 中,您希望能够运行套件,或者至少是其中的一个相关子集,通常,在您进行的每个小更改之后,以验证您没有破坏任何现有行为。这只有在来自测试的反馈是即时的时才可行。如果您必须等待测试结果的时间过长,您会减少运行它们的频率,这意味着反馈循环会变长。

在较小的 Spring 项目中,Spring 上下文加载速度很快,因此您可能认为差别不大,但如果您经常运行测试,即使是短暂的延迟也会增加。在大型和较旧的代码库中,增加 Spring 上下文可能需要一段时间,并且在某些时候它会变得非常慢,以至于您不想每天运行整个测试套件超过一次,这会大大损害您的能力练习 TDD。

坚持测试小代码单元的另一个方面是它迫使您将代码构造成高度解耦(即可测试)的单元。如果您不能为只练习该类而不执行其他任何内容的类编写单元测试,则可能暗示有机会改进代码的设计。

总而言之,当你为一个测试增加整个 Spring 上下文时,根据这个定义,它不再是一个单元测试,而是一个集成测试,因为你也在测试整个 Spring 配置、引导、自动装配等,即集成将您的类放入 Spring 应用程序中。

于 2017-10-13T07:53:41.527 回答
1

如果您的类使用任何 bean 依赖项,则应使用 @SpringRunner,如果您的类是独立的,没有任何应用程序上下文依赖项,最好不要使用 @SpringRunner。如果我看到你的类'RewardDurationCalculator'它不使用任何依赖项,它不应该使用@SpringRunner,甚至事实上Mocks......

进一步阅读:

  1. https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/context/junit4/SpringRunner.html
于 2017-10-13T07:56:35.213 回答