使用 Java 8 Lambdas 进行单元测试代码

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28688047/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 13:59:49  来源:igfitidea点击:

Unit test code with Java 8 Lambdas

javaunit-testinglambdajava-8

提问by Fdiazreal

I have been using Java 8 for some months, and I have started to use Lambda expressions, which are very convenient for some cases. However, I often come across some problems to unit test the code that uses a Lambda.

我已经使用Java 8几个月了,我开始使用Lambda表达式,这在某些情况下非常方便。但是,我经常遇到一些问题来对使用 Lambda 的代码进行单元测试。

Take as an example the following pseudo-code:

以下面的伪代码为例:

private Bar bar;

public void method(int foo){
    bar.useLambda(baz -> baz.setFoo(foo));
}

One approach would be to just verify the call on bar

一种方法是仅验证 bar 上的调用

verify(bar).useLambda(Matchers.<Consumer<Baz>>.any());

But, by doing that, I don't test Lambda's code.

但是,通过这样做,我不会测试 Lambda 的代码。

Also note that I am not able to replace the Lambda with a method and use method reference:

另请注意,我无法用方法替换 Lambda 并使用方法引用:

bar.useLambda(This::setFooOnBaz);

Because I will not have the foo on that method. Or at least that is what I think.

因为我不会在该方法上使用 foo。或者至少我是这么认为的。

Have you had this problem before? How can I test or refactor my code to test it properly?

你以前有过这个问题吗?如何测试或重构我的代码以正确测试它?



Edit

编辑

Since what I am coding is an unit test, I don't want to instantiate bar, and I will be using a mock instead. So I will not be able to just verify the baz.setFoocall.

由于我正在编码的是一个单元测试,我不想实例化 bar,我将使用模拟来代替。因此,我将无法仅验证baz.setFoo通话。

回答by Stuart Marks

You can't unit test a lambda directly, since it doesn't have a name. There's no way to call it unless you have a reference to it.

您不能直接对 lambda 进行单元测试,因为它没有名称。除非您有对它的引用,否则无法调用它。

The usual alternative is to refactor the lambda into a named method and use a method reference from product code and call the method by name from test code. As you note, this case can't be refactored this way because it captures foo, and the only thing that can be captured by a method reference is the receiver.

通常的替代方法是将 lambda 重构为命名方法,并使用来自产品代码的方法引用,并通过来自测试代码的名称调用该方法。正如您所注意到的,这种情况不能以这种方式重构,因为它捕获foo,并且方法引用唯一可以捕获的是接收者。

But the answer from yshavittouches upon an important point about whether it's necessary to unit test private methods. A lambda can certainly be considered a private method.

但是yshavit回答触及了一个重要的观点,即是否有必要对私有方法进行单元测试。一个 lambda 当然可以被认为是一个私有方法。

There's a larger point here too. One of the priciples of unit testing is that you don't need to unit test anything that's too simple to break. This aligns well with the ideal case for lambda, which is an expression that's so simple it's obviously correct. (At least, that's what I consider ideal.) Consider the example:

这里还有一个更大的点。单元测试的原则之一是您不需要对任何太简单而无法破坏的内容进行单元测试。这与 lambda 的理想情况非常吻合,这是一个非常简单的表达式,显然是正确的。(至少,这是我认为的理想情况。)考虑以下示例:

    baz -> baz.setFoo(foo)

Is there any doubt that this lambda expression, when handed a Bazreference, will call its setFoomethod and pass it fooas an argument? Maybe it's so simple that it doesn't need to be unit tested.

有没有怀疑这个 lambda 表达式,当传递一个Baz引用时,会调用它的setFoo方法并将它foo作为参数传递?也许它是如此简单以至于不需要进行单元测试。

On the other hand, this is merely an example, and maybe the actual lambda you want to test is considerably more complicated. I've seen code that uses large, nested, multi-line lambdas. See this answerand its question and other answers, for example. Such lambdas are indeed difficult to debug and test. If the code in the lambda is complex enough that it warrants testing, maybe that code ought to be refactored out of the lambda, so that it can be tested using the usual techniques.

另一方面,这只是一个示例,可能您要测试的实际 lambda 更复杂。我见过使用大型嵌套多行 lambda 的代码。例如,请参阅此答案及其问题和其他答案。这样的 lambda 确实很难调试和测试。如果 lambda 中的代码足够复杂以至于需要进行测试,那么也许应该从 lambda 中重构该代码,以便可以使用通常的技术对其进行测试。

回答by yshavit

Treat the lambdas like you would a private method; don't test it separately, but rather test the effect it has. In your case, invoking method(foo)should cause bar.setFooto happen -- so, call method(foo)and then verify bar.getFoo().

像对待私有方法一样对待 lambda;不要单独测试它,而是测试它的效果。在您的情况下,调用method(foo)应该导致bar.setFoo发生 - 因此,调用method(foo)然后验证bar.getFoo()

回答by akwizgran

My team recently had a similar issue, and we found a solution that works nicely with jMock. Perhaps something similar would work for whatever mocking library you're using.

我的团队最近遇到了类似的问题,我们找到了一个可以很好地与 jMock 配合使用的解决方案。也许类似的东西适用于您正在使用的任何模拟库。

Let's assume the Barinterface mentioned in your example looks like this:

假设Bar您的示例中提到的界面如下所示:

interface Bar {
    void useLambda(BazRunnable lambda);
    Bam useLambdaForResult(BazCallable<Bam> lambda);
}

interface BazRunnable {
    void run(Baz baz);
}

interface BazCallable<T> {
    T call(Baz baz);
}

We create custom jMock Actions for executing BazRunnables and BazCallables:

我们创建自定义 jMock 操作来执行 BazRunnables 和 BazCallables:

class BazRunnableAction implements Action {

    private final Baz baz;

    BazRunnableAction(Baz baz) {
        this.baz = baz;
    }

    @Override
    public Object invoke(Invocation invocation) {
        BazRunnable task = (BazRunnable) invocation.getParameter(0);
        task.run(baz);
        return null;
    }

    @Override
    public void describeTo(Description description) {
        // Etc
    }
}

class BazCallableAction implements Action {

    private final Baz baz;

    BazCallableAction(Baz baz) {
        this.baz = baz;
    }

    @Override
    public Object invoke(Invocation invocation) {
        BazCallable task = (BazCallable) invocation.getParameter(0);
        return task.call(baz);
    }

    @Override
    public void describeTo(Description description) {
        // Etc
    }
}

Now we can use the custom actions to test interactions with mocked dependencies that happen within lambdas. To test the method void method(int foo)from your example we'd do this:

现在我们可以使用自定义操作来测试与 lambdas 中发生的模拟依赖项的交互。要测试void method(int foo)您示例中的方法,我们将执行以下操作:

Mockery context = new Mockery();
int foo = 1234;
Bar bar = context.mock(Bar.class);
Baz baz = context.mock(Baz.class);

context.checking(new Expectations() {{
    oneOf(bar).useLambda(with(any(BazRunnable.class)));
    will(new BazRunnableAction(baz));
    oneOf(baz).setFoo(foo);
}});

UnitBeingTested unit = new UnitBeingTested(bar);
unit.method(foo);

context.assertIsSatisfied();

We can save some boilerplate by adding convenience methods to the Expectations class:

我们可以通过向 Expectations 类添加便捷方法来节省一些样板:

class BazExpectations extends Expectations {

    protected BazRunnable withBazRunnable(Baz baz) {
        addParameterMatcher(any(BazRunnable.class));
        currentBuilder().setAction(new BazRunnableAction(baz));
        return null;
    }

    protected <T> BazCallable<T> withBazCallable(Baz baz) {
        addParameterMatcher(any(BazCallable.class));
        currentBuilder().setAction(new BazCallableAction(baz));
        return null;
    }
}

This makes the test expectations a little clearer:

这使得测试期望更清晰一点:

context.checking(new BazExpectations() {{
    oneOf(bar).useLambda(withBazRunnable(baz));
    oneOf(baz).setFoo(foo);
}});

回答by Microkernel

My usual approach is to use an ArgumentCaptor. This way you could capture reference to actual lambda function that was passed and could validate its behavior separately.

我通常的方法是使用 ArgumentCaptor。通过这种方式,您可以捕获对传递的实际 lambda 函数的引用,并可以单独验证其行为。

Assuming your Lambda is reference to MyFunctionalInterface, I would do something like.

假设您的 Lambda 是对 的引用MyFunctionalInterface,我会做类似的事情。

ArgumentCaptor<MyFunctionalInterface> lambdaCaptor = ArgumentCaptor.forClass(MyFunctionalInterface.class);

verify(bar).useLambda(lambdaCaptor.capture());

// Not retrieve captured arg (which is reference to lamdba).
MyFuntionalRef usedLambda = lambdaCaptor.getValue();

// Now you have reference to actual lambda that was passed, validate its behavior.
verifyMyLambdaBehavior(usedLambda);