【问题标题】:How to mock, or otherwise test readPassword?如何模拟或以其他方式测试 readPassword?
【发布时间】:2016-03-06 21:56:06
【问题描述】:

我正在开发一个框架来简化控制台应用程序的创建。我的框架是用 Scala 编写的,我使用 ScalaTest 和 Mockito 进行单元测试。

我需要能够模拟java.io.Console,但它已被宣布为最终版本。我正在尝试实现 100% 的单元测试覆盖率,这是目前唯一阻碍我的事情 - 在功能测试和单元测试中。

到目前为止,我无法通过任何解决方案走得很远,我只是想不出一种方法来做到这一点。它没有实现我可以模拟的接口,该方法在其他任何地方都不可用,显然我无法扩展它。我在想也许有一个解决方案可能涉及某种动态方法来调用诸如readLinereadPassword 之类的方法,但我也没有足够的经验来获得这种思路!

【问题讨论】:

  • 100% 覆盖率是 CS 中最大的幻想,亚伯拉罕·林肯 :)
  • 您是否考虑过查看 SystemRules (stefanbirkner.github.io/system-rules/index.html),它是一组用于处理 System 类的 junit 规则。它可能会给你一些启发。或者,ScalaCheck 可能会在这种情况下提供一些帮助,它是一种不同的测试方法。
  • @SleimanJneidi 我毫不怀疑你是对的! :) 知道是否有解决方案仍然很有趣,这似乎在 Java 的 API 设计中有点疏忽,我还没有遇到过这样的事情。
  • 这可能是一个更普遍的问题 - 我建议将问题重命名为“如何模拟或测试最终和静态方法”以吸引更多答案。

标签: java scala unit-testing mocking mockito


【解决方案1】:

您应该创建自己的界面来包装与java.io.Console 的所有交互,例如

public interface ConsoleService {
    ...
}

只要您只通过ConsoleService 的实例与控制台交互,那么您就可以像往常一样模拟ConsoleService 并测试99% 的代码。 ConsoleService 接口成为您的应用程序的边界,用于对整个应用程序进行功能测试以及直接与其交互的类的单元测试。

现在我们已经将问题的范围缩小到“我如何测试ConsoleService 实现”,我们需要有点创意。例如,您可以将控制台输出重定向到文件并检查文件的内容。您甚至可能不想在 Scala 中测试 ConsoleService;您可以使用ConsoleService 编写一个框架应用程序,然后使用您选择的脚本语言在您最喜欢的操作系统上启动一个真正的控制台,与您的框架应用程序交互并以这种方式测试ConsoleService。您可以在这里尽情发挥创意(和 hacky),因为:

  1. 它只影响少数测试;和
  2. 您的应用程序可能会成熟到不需要对 ConsoleService 实现进行太多更改的程度,即您古怪的测试解决方案不会成为未来开发人员的沉重负担。

由于这些原因,很明显保持ConsoleService 包装器非常薄 是一个好主意,因为其中的任何逻辑都将通过奇怪的ConsoleService 测试进行测试,这不好友好的 Scala 测试。通常直接委托给java.io.Console 方法就足够了,但是您应该允许应用程序的功能测试驱动ConsoleService 接口,而不是做出任何假设(您的功能测试断言可能依赖于与模拟ConsoleService 的特定交互,或者在存根的状态下,您可以在测试中控制ConsoleService 的测试实现。

最后,您可能会认为ConsoleServicewrapper 太薄了,以至于它的实现确实需要任何单元/功能测试。 ConsoleService 的实现可能对您的应用程序至关重要,以至于任何缺陷都将通过 UAT 中的集成测试或手动检查应用程序暴露出来。

你可能会得到这样的结果(抱歉,我不会说 Scala,所以它是 Java):

public class RealConsoleService implements ConsoleService {
    private final java.io.Console delegate;
    public RealConsoleService(java.io.Console delegate) {
        this.delegate = delegate;
    }

    @Override
    public String readLine() throws IOError {
        return delegate.readLine();
    }
}

两个有趣的点:

  • 这是一个很好的例子,说明了为什么测试驱动开发有助于编写灵活的代码。如果您想使用另一种输入和输出方法重写您的框架,您只需将 ConsoleService 重命名为更抽象的 ApplicationInputOutputService 并插入不同的实现。
  • 同样的概念可用于测试使用其他难以测试的 API 的应用程序。 Java 的许多有用的文件 IO 方法都是静态方法,因此在测试中难以控制。通过像上面那样封装在一个接口中,您的应用程序功能变得易于测试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-03
    • 1970-01-01
    • 2023-03-26
    • 2016-04-13
    • 2018-11-15
    • 1970-01-01
    相关资源
    最近更新 更多