编写/实现API:可测试性与信息隐藏
很多时候,我参与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本身的目的!
这个问题的另一个解决方案是为类Foo
公开一个受保护的方法,在FooTest
中扩展它并获得所需的数据。同样,我认为这也是一种糟糕的方法,因为protected
方法确实定义了一个契约,并且通过暴露它我现在已经隐式签署了它。
别误会我的意思。我喜欢编写可测试的代码。我喜欢简洁,干净的API,短代码块,可读性等等。但是我不喜欢的是在信息隐藏方面做出任何牺牲只是因为它更容易进行单元测试。
任何人都可以对此提出任何想法(一般而言,特别是)?对于给定的例子,还有其他更好的解决方案吗?
没有找到相关结果
已邀请:
6 个回复
览幕堤分
骚瓤
) 您需要访问的任何隐藏成员都不能是私有的;受保护是你能做的最好的事情。 这不是严格的TDD; TDD将适用于首先不需要测试代理的模式。 这不是严格的单元测试,因为在某种程度上,您依赖于代理和实际SUT之间的“集成”。 简而言之,这应该是罕见的。我倾向于仅将它用于UI元素,其中最佳实践(以及许多IDE的默认行为)是将嵌套的UI控件声明为从类外部无法访问。绝对是一个好主意,因此您可以控制调用者如何从UI获取数据,但这也使得难以为控件提供一些已知值来测试该逻辑。
妊辽剁茧
而不是直接构造它:
然后在单元测试中,你可以用mock9ѭ的模拟版本注入这个类,并轻松断言当你用不同的输入调用
时会发生什么 -
接收某些值的参数。 我认为你可能过度简化了这个例子,因为如果
接收到其类型的参数,InnerFoo就不能成为私有类。 “信息隐藏”并不一定意味着您在类之间传递的对象需要是秘密 - 只是您不需要使用此类的外部代码来了解
的详细信息或其他详细信息班级与他人沟通。
款去芳尾脊
可以在
以外测试,对吗?您可以使用自己的实现
的测试类调用其
方法。所以这个单位得到了照顾。现在你正在用经过充分测试的单元和未经测试的内部课程测试
。这并不理想 - 但也不算太糟糕。当你试驾
时,你隐含地试驾了内部阶级。根据一些严格的标准,我知道这不是纯粹的TDD,但我认为这已经足够了。除了满足失败的测试之外,你不是在编写内部类的一行;在测试和测试代码之间存在单一级别的间接不会构成大问题。
惭法搽
糖固傻染
不是特别有帮助。它使类的扩展变得困难,并且使测试变得困难。 考虑重新思考你对“隐藏”的立场。