很多时候,我在参与 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 个属性,但是有 5-6 个属性是相当合理的,OuterFoo
构造函数必须接受 8-10 个参数!在顶部添加 getters,你已经有 100 行完全无用的代码(还需要 getters 来获取这些属性进行测试)。是的,我可以通过提供构建器模式让情况好一点,但我认为这不仅是过度设计,而且违背了 TDD 本身的目的!
此问题的另一个解决方案是公开类的受保护方法Foo
,将其扩展为FooTest
并获取所需数据。再说一次,我认为这也是一个不好的方法,因为protected
method 是否定义了合同通过公开它,我现在已经隐含地签署了它。
别误会我的意思。我喜欢编写可测试的代码. 我喜欢简洁、干净的 API、短代码块、可读性等等。但我不喜欢的是在信息隐藏方面做出任何牺牲只是因为单元测试更容易.
任何人都可以对此提供任何想法(一般而言,特别是)?对于给定的示例,还有其他更好的解决方案吗?