【问题标题】:Test lambda expressions called by dependencies测试依赖项调用的 lambda 表达式
【发布时间】:2019-01-22 12:12:08
【问题描述】:

我正在尝试测试 lambda 表达式中的一些代码,这是另一个类的回调。

class EmailSender {
    private EmailBuilder emailBuilder;

    public void send() {
        String testEmail = emailBuilder.buildEmail("Test Email", bodyContentAppender());
        //send testEmail
    }

    private Consumer<Email> bodyContentAppender() {
        //how to test this through JUnit?
        return email -> email.appendBody("Body Content");
    }
}

interface EmailBuilder {

    String buildEmail(String templateName, Consumer<Email> contentAppender);
}

getBodyContent 方法中的 lambda 表达式从 EmailBuilder 调用,这是 EmailSender 的 JUnit 测试中的模拟依赖项。因为我在嘲笑EmailBuilder 的行为,所以getBodyContent 中的代码不会从测试中调用。如何测试这样的一块?

编辑: 在这种情况下,通过 Argument Captors 捕获 lambda 表达式不是解决方案,因为 EmailBuilder 的行为被模拟并且不调用实际方法。其次,email.appendBody 对通过外部 API 传递的对象进行一些转换,并且创建起来并不简单。

【问题讨论】:

  • 直接调用getBodyContent(),测试body是否正确附加
  • 能否提供模拟“EmailBuilder”的代码?
  • @Hulk:我知道了,伙计 :) 遗憾的是,这份文件是大多数人的论点,我没有找到任何不同的论点。
  • @Hulk 离题讨论提醒!

标签: java unit-testing lambda junit java-8


【解决方案1】:

您在这里尝试做的基本上是验证工厂方法确实确实返回了正确的对象。有this related question,其中的共识是不测试工厂方法的结果,除非验证它确实返回了正确类型的对象。该对象的行为应在该类型的单元测试中进行测试。

在对this related question on unit testing lambdasStuart Marks 的回答中认为

如果 lambda 中的代码足够复杂以至于需要测试,那么可能应该从 lambda 中重构该代码,以便可以使用通常的技术对其进行测试。

现在,真正的问题是:如果这不是一个 lambda,而是一个具体的类 MyBodyContentAppender,它实现了功能接口 Consumer&lt;Email&gt;,你将如何对其进行单元测试?你会为这门课写什么样的测试?

您可能会编写测试来验证,给定Email,调用accept() 确实使用适当的参数调用appendBody(),也许使用null 参数调用它会引发NullPointerException 等。您可能验证email.appendBody() 是否按预期工作,因为Email 的测试涵盖了这一点。如果难以创建这些测试,您可能不得不模拟 Email

嗯,所有这些测试也可以针对 lambda 执行。您的问题是创建对象的工厂和类型都是私有的,因此从您的测试的角度来看,访问该对象的唯一方法是通过传递给(模拟)emailBuilder.buildEmail() 的参数。

如果您使用 Mockito 来模拟 emailBuilder,您可以通过 ArgumentCaptors 捕获此方法的参数(请参阅 15. Capturing arguments for further assertions (Since 1.8.0)),我相信其他模拟库也提供类似的功能。

【讨论】:

  • 正是@GhostCat 建议的方式。根据您引用的论点考虑拿走东西。感谢您对其他答案的引用。
【解决方案2】:

大多数模拟框架允许您检查在模拟对象上调用方法时使用的参数。您可以分别捕获它们。

所以:

  • 获取传递的参数
  • 只需调用它所代表的“代码”,并检查这是否会对您提供的Email 对象进行预期更新。

【讨论】:

    【解决方案3】:

    如果您确实将正文内容作为参数提供,并确保该方法是公开的,那么测试会更容易。如果您打算将该方法保持为私有,那么您应该测试调用它的公共方法。

    public Consumer<Email> getBodyContent(String body) {
        //how to test this through JUnit?
        return email -> email.appendBody(body);
    }
    

    然后你可以测试它为

    @Test
    public void testGetBodyContent(){
        .... //send different arguments and assert
        ....
    }
    

    【讨论】:

    • 公共方法调用它 - 不可能。这是由一个将 this 作为参数的 API 方法调用的。
    【解决方案4】:

    因此,当您想要对代码进行单元测试时,您必须确保它一次只完成一项工作。您的方法被命名为getBodyContent,但似乎除了附加到电子邮件正文之外应该做的工作不多。因此,您可以将此方法拉出来公开。

    现在您可以传递正文并获取内容。

    @Test
    public void testGetBodyContent(){
    
    consumer<Email> consumer = EmailSender.getBodyContent();
    
    assertEquals("Email Content", consumer.accept(<mocked Email object>).getBody())
    }
    

    【讨论】:

    • 我认为我们不应该公开这种方法。对任何外部客户来说都是没有意义的。
    • 那么测试就没有意义了。因为您应该测试业务而不是从业务角度来看毫无意义的代码。由于这个原因,我们通常会避免测试私有方法。如果有一种强烈的感觉值得测试,那么它不应该是私有的。我认为这就是为什么类似 Junit 的框架不允许测试私有方法的原因。
    • 不,业务也可以在私有方法中。它只是测试的方式不同。私有方法中的业务被相关公共方法的调用调用。
    猜你喜欢
    • 1970-01-01
    • 2016-02-06
    • 2014-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-24
    • 2013-06-18
    相关资源
    最近更新 更多