很多时候,我参与了 API 的设计/实现,我面临着这个困境。
我是信息隐藏的坚定支持者,并尝试为此使用各种技术,包括但不限于内部类、私有方法、包私有限定符等。
这些技术的问题在于它们往往会妨碍良好的可测试性。虽然其中一些技术可以解决(例如,通过将一个类放入同一个包中的包私有性),但其他技术并不那么容易解决,并且需要反射魔法或其他技巧。
我们来看一个具体的例子:
public class Foo {
SomeType attr1;
SomeType attr2;
SomeType attr3;
public void someMethod() {
// calculate x, y and z
SomethingThatExpectsMyInterface something = ...;
something.submit(new InnerFoo(x, y, z));
}
private class InnerFoo implements MyInterface {
private final SomeType arg1;
private final SomeType arg2;
private final SomeType arg3;
InnerFoo(SomeType arg1, SomeType arg2, SomeType arg3) {
this.arg1 = arg1;
this.arg2 = arg2;
this.arg3 = arg3;
}
@Override
private void methodOfMyInterface() {
//has access to attr1, attr2, attr3, arg1, arg2, arg3
}
}
}
有充分的理由不公开InnerFoo
- 没有其他类,库应该可以访问它,因为它没有定义任何公共合同,并且作者故意不希望它可以访问。然而,为了使其 100% TDD-kosher 并且无需任何反射技巧即可访问,InnerFoo
应该像这样重构:
private class OuterFoo implements MyInterface {
private final SomeType arg1;
private final SomeType arg2;
private final SomeType arg3;
private final SomeType attr1;
private final SomeType attr2;
private final SomeType attr3;
OuterFoo(SomeType arg1, SomeType arg2, SomeType arg3, SomeType attr1, SomeType attr2, SomeType attr3) {
this.arg1 = arg1;
this.arg2 = arg2;
this.arg3 = arg3;
this.attr1 = attr1;
this.attr2 = attr2;
this.attr3 = attr3;
}
@Override
private void methodOfMyInterface() {
//can be unit tested without reflection magic
}
}
这个例子只涉及 3 个 attrs,但有 5-6 个是相当合理的,OuterFoo
然后构造函数必须接受 8-10 个参数!在顶部添加 getter,您已经有 100 行完全无用的代码(还需要 getter 来获取这些 attrs 进行测试)。是的,我可以通过提供构建器模式使情况变得更好,但我认为这不仅是过度设计,而且违背了 TDD 本身的目的!
这个问题的另一个解决方案是为 class 公开一个受保护的方法Foo
,将其扩展FooTest
并获取所需的数据。同样,我认为这也是一种不好的方法,因为protected
方法确实定义了一个合同,并且通过公开它,我现在已经隐式地签署了它。
不要误会我的意思。我喜欢编写可测试的代码。我喜欢简洁、干净的 API、短代码块、可读性等。但我不喜欢在信息隐藏方面做出任何牺牲,因为它更容易进行单元测试。
任何人都可以对此提供任何想法(总的来说,特别是)?对于给定的示例,还有其他更好的解决方案吗?