很多时候我在设计/实现 API 时都面临着这种困境。
我是information hiding的坚定支持者并为此尝试使用各种技术,包括但不限于内部类、私有(private)方法、包私有(private)限定符等。
这些技术的问题在于它们往往会妨碍良好的可测试性。虽然其中一些技术可以解决(例如,通过将一个类放入同一个包中来实现包隐私),但其他技术是 not so easy to tackle两者都需要反射魔法或其他技巧。
让我们看一个具体的例子:
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
- 没有其他类、库应该可以访问它,因为它没有定义任何公共(public)契约并且作者故意不希望它可以访问。然而,要使其 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 个参数!在上面添加 getter,您已经有 100 行完全无用的代码(还需要 getter 来获取这些属性以进行测试)。是的,我可以通过提供构建器模式使情况好转一些,但我认为这不仅是过度工程,而且违背了 TDD 本身的目的!
此问题的另一个解决方案是为类 Foo
公开一个 protected 方法,在 FooTest
中扩展它并获取所需的数据。同样,我认为这也是一种糟糕的方法,因为 protected
方法确实定义了一个契约,并且通过公开它,我现在已经隐式地签署了它。
不要误会我的意思。 我喜欢编写可测试的代码。 我喜欢简洁、干净的 API、短代码块、可读性等。但我不喜欢的是在信息隐藏方面做出任何牺牲只是因为它更容易进行单元测试。
任何人都可以对此提供任何想法(一般而言,特别是)?对于给定的示例,还有其他更好的解决方案吗?
最佳答案
对于这类事情,我的首选答案是“测试代理”。在您的测试包中,从被测系统派生一个子类,其中包含 protected 数据的“直通”访问器。
优点:
- 您可以直接测试或模拟您不想公开的方法。
- 由于测试代理存在于测试包中,您可以确保它永远不会在生产代码中使用。
- 与直接测试类相比,测试代理需要对代码进行更少的更改以使其可测试。
缺点:
- 类必须是可继承的(没有
final
) - 您需要访问的任何隐藏成员都不能是私有(private)的; protected 是你能做的最好的事情。
- 这不是严格意义上的 TDD; TDD 适用于一开始就不需要测试代理的模式。
- 这甚至不是严格意义上的单元测试,因为在某种程度上,您依赖于代理和实际 SUT 之间的“集成”。
简而言之,这在正常情况下应该是很少见的。我倾向于只将它用于 UI 元素,其中最佳实践(以及许多 IDE 的默认行为)是将嵌套的 UI 控件声明为不可从类外部访问。绝对是个好主意,这样您就可以控制调用方如何从 UI 获取数据,但这也使得很难为控件提供一些已知值来测试该逻辑。
关于java - 编写/实现 API : testability vs information hiding,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5031912/