使用许多模拟对象创建测试?

Creating tests with many mock objects?

我怀疑是否应该创建具有许多模拟对象的测试。

我最近读了 When should I mock?,我感到很困惑。

看看我有一个方法(只是为了说明问题)

@Override
protected void validate() throws WTException {
    Either<ImportError, RootFinderResult> rootPart = getDataValidator().getRootPart();
    if (rootPart.isLeft()) {
        addValidationMessage(ROOT_PART_NOT_FOUND);
    } else if (rootPart.isRight()) {
        getObjectsToValidate().forEach(Lambda.uncheckedBiConsumer((part, epmDocuments) -> {
            LocalizableMessage rootRevision = getRevision(part);

            Optional<EPMDocument> wrongRevisionEPM = epmDocuments.stream()
                    .filter(epmDocument -> !isSameRevision(rootRevision, epmDocument))
                    .findAny();

            wrongRevisionEPM.ifPresent(epmDocument -> addValidationMessage("blabla"));
        }));
    }
}

以下所有方法都需要连接到服务器才能工作,否则会抛出错误

getDataValidator().getRootPart();
getRevision(part)
!isSameRevision(rootRevision, epmDocument))

此外,我无法创建 'real' part 或 epm 文档的对象。这也需要连接到服务器。


所以在这一点上,我真正想测试的其实是这部分代码的逻辑

    Optional<EPMDocument> wrongRevisionEPM = epmDocuments.stream()
            .filter(epmDocument -> !isSameRevision(rootRevision, epmDocument))
            .findAny();

    wrongRevisionEPM.ifPresent(epmDocument -> addValidationMessage("blabla"));

但是为了测试它,我需要模拟很多对象

@Spy
@InjectMocks
private SameRevision sameRevision;
@Mock
private WTPartRelatedObjectDataValidator wTPartRelatedObjectDataValidator;
@Mock
private ValidationEntry validationEntry;
@Mock
private WTPart rootPart1, rootPart2;
@Mock
private EPMDocument epmDocument1, epmDocument2, epmDocument3;
@Mock
private Either<ImportError, RootFinderResult> rootPart;
@Mock
private LocalizableMessage rootPartRevisionOne, rootPartRevisionTwo;

所以我终于可以测试逻辑了:

@Test
@DisplayName("Should contain error message when part -> epms revisions are not the same")
void shoulHaveErrorMessagesWhenDifferentRevisions() throws Exception {
    doReturn(getMockObjectsToValidate()).when(sameRevision).getObjectsToValidate();

    doReturn(rootPart).when(liebherrWTPartRelatedObjectDataValidator).getRootPart();
    doReturn(false).when(rootPart).isLeft();
    doReturn(true).when(rootPart).isRight();

    doReturn(rootPartRevisionOne).when(sameRevision).getRevision(rootPart1);
    doReturn(rootPartRevisionTwo).when(sameRevision).getRevision(rootPart2);

    doReturn(true).when(sameRevision).isSameRevision(rootPartRevisionOne, epmDocument1);
    doReturn(false).when(sameRevision).isSameRevision(rootPartRevisionOne, epmDocument2);
    doReturn(true).when(sameRevision).isSameRevision(rootPartRevisionTwo, epmDocument3);

    validationEntry = sameRevision.call();

    assertEquals(1, validationEntry.getValidationMessageSet().size());
}

其中

    doReturn(rootPart).when(liebherrWTPartRelatedObjectDataValidator).getRootPart();
    doReturn(false).when(rootPart).isLeft();
    doReturn(true).when(rootPart).isRight();

    doReturn(rootPartRevisionOne).when(sameRevision).getRevision(rootPart1);
    doReturn(rootPartRevisionTwo).when(sameRevision).getRevision(rootPart2);

可以移动到@BeforeEach。


最后,我进行了测试并且成功了。它验证了我想要验证的内容,但为了达到这一点,我不得不付出很多努力来完成需要与服务器交互的整个 API。

你们怎么看,创建这样的测试值得吗?我想这是一个开放性的话题,因为许多尝试进入 'test world' 的新手都会遇到类似的问题,所以请不要因为基于意见的判断而关闭该话题,并就此话题提供您的反馈.

您应该模拟要测试的 class 所依赖的其他依赖项,并设置您需要的行为。 需要这样做来测试您的独立方法而不依赖于第三方 classes 您可以编写包含模拟行为的私有 void 方法并在测试中使用它们, 在@BeforeEach 注释方法中,您可以模拟在所有测试中都相同的行为,或者在所有测试中模拟相同的模拟行为

在你的无效方法中,你可以拥有间谍对象,如果它们像 Mockito.verify()

是的,当你必须模拟这么多东西时,是时候投资了。 在我看来,如果你在测试某个东西的时候增加了一些价值,那么它就值得测试,问题当然可能是你会消耗多少时间。

在你的具体情况下,我会测试不同的 "layers"。

例如方法: getDataValidator().getRootPart(); getRevision(部分) !isSameRevision(rootRevision, epmDocument))

它们可以独立测试,在你的情况下只是模拟它们的结果,这意味着你并不真正关心那里的参数,你只关心在某个 return 值的情况下会发生什么。

所以,在一层你真正测试功能,在下一层你只是模拟你需要的结果,以测试其他功能。

我希望现在更清楚了...

你是对的。模拟所有这些依赖关系是一项巨大的努力。让我回顾一下可能会使事情更清楚的几点:

  • 将编写测试视为一项投资: 所以,是的,有时编写测试比编写实际代码更费力。但是,稍后当您引入错误并且测试可以捕获它时,您会感谢自己。拥有良好的测试可以让您在修改代码时有信心您没有破坏任何东西,如果您这样做了,您的测试就会发现问题。它会随着时间的推移得到回报。

  • 让您的测试专注于特定的 class。模拟其余部分: 当你模拟除被测 class 之外的所有内容时,你可以确定当问题发生时它来自被测 class,而不是来自其中之一它的依赖。这使得故障排除变得容易得多。

  • 在编写新代码时考虑可测试性: 有时可能无法避免一段复杂的代码难以测试。但是,通常,可以通过将依赖项的数量保持在最低限度并编写可测试的代码来避免这种情况。例如,如果一个方法需要 5 或 6 个更多的依赖项来完成它的工作,那么该方法可能做的太多了,可能会被分解。同样的事情可以在 class 级别、模块等上说。