0

I'm having doubts about if I should create tests that have many mock objects or not.

I recently read When should I mock? and I'm feeling confused.

Let's take a look at a method I have (it is just to illustrate the problem)

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

All of the following methods need to have a connection to a server in order to work, otherwise they will throw errors

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

In addition I can't create 'real' objects of part or epm documents. This also requires to have a connection to a server.


So at this point, what I really want to test is actually logic of this part of code

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

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

But to test it I need to mock really many objects

@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;

so finally I can test the logic:

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

where

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

can be moved to @BeforeEach.


At last, I have my test and it works. It validates what I wanted to be validated but in order to come to this point I had to put a lot of effort to come through the whole API which needs interactions with a server.

What do you guys think, is it worth it to create tests like this? I guess this is a wide-open topic 'cause many newbies that try to come into the 'test world' will have similar a problem, so please do not close the topic because of opinion-based judgement and give your feedback on this topic.

4

3 回答 3

1

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

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

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

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

于 2018-11-14T10:39:49.667 回答
1

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

在您的无效方法中,您可以拥有可以验证的间谍对象,如果它们被称为 Mockito.verify()

于 2018-11-14T09:24:19.770 回答
0

是的,当你不得不嘲笑这么多事情时,这是相当投入的时间。在我看来,如果你在测试某些东西时增加了一些价值,那么值得测试,问题当然可能是你会消耗多少时间。

在您的具体情况下,我会在不同的“层”上进行测试。

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

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

因此,在一层您真正测试功能,在下一层您只需模拟您需要的结果,以便测试其他功能。

我希望现在更清楚...

于 2018-11-14T09:27:21.533 回答