您应该创建自己的界面来包装与java.io.Console
的所有交互,例如,
public interface ConsoleService {
...
}
只要你只通过的ConsoleService
实例的控制台交互,那么你就可以嘲笑你的代码的ConsoleService
和测试99%,你通常会。接口将成为应用程序的边界,用于整个应用程序的功能测试以及直接与其交互的类的单元测试。
现在我们已经将问题的范围缩小为“如何测试ConsoleService
实施”,我们需要获得一点创意。例如,您可以将控制台输出重定向到一个文件并检查该文件的内容。您可能甚至不想测试Scala中的ConsoleService
;您可以使用ConsoleService
编写一个框架应用程序,然后使用您选择的脚本语言在您最喜欢的操作系统上启动一个真正的控制台,与您的骨架应用程序进行交互并以此方式测试ConsoleService
。你可以得到像你这样的创意(和哈克),因为:
- 它只影响少量的测试;和
- 你的应用程序很可能会成熟到一个点,其中
ConsoleService
实现不需要改变很多,即你的古怪测试解决方案将不会成为未来开发人员的重大负担。
由于这些原因,应该是显而易见的,这是一个好主意,让ConsoleService
包装非常薄,因为没有任何逻辑将通过奇怪ConsoleService
测试进行测试,而不是漂亮的友好斯卡拉测试。通常直接授权到java.io.Console
方法已经足够好了,但您应该允许应用程序的功能测试来驱动接口而不是做出任何推定(您的功能测试断言可能依赖于与模拟ConsoleService
的特定交互,或者可能取决于状态的一个存根,您可以在测试中控制的ConsoleService
的测试实现)。
最后,你可能会认为ConsoleService
包装是,所以很薄,它的实现确实需要任何单元/功能测试。 ConsoleService
的实现可能对您的应用程序非常关键,以至于UAT中的应用程序的集成测试或手动检查将暴露任何缺陷。
也许最终你会像这样的东西(抱歉,我不说话斯卡拉所以它的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方法都是静态方法,因此很难在测试中进行控制。通过如上所述的界面包装,您的应用程序功能变得易于测试。
100%的覆盖率是在CS,亚伯拉罕林肯最大的幻想:) –
你有没有考虑过查看SystemRules(http://stefanbirkner.github.io/system-rules/index.html),它的一套junit处理System类的规则。它可能会给你一些启发。或者,ScalaCheck可以在这种情况下提供一些帮助,它是一种不同的测试方法。 – Gavin
@SleimanJneidi我毫不怀疑你在那里是正确的! :)知道这是否有解决方案仍然很有趣,它似乎是Java中API设计的一个疏忽,我还没有遇到过这样的事情。 – Seer