考虑以下(完全做作的)示例:
public class Length {
private static final int MAX_LENGTH = 10;
private final int length;
public Length(int length) {
if (length > MAX_LENGTH)
throw new IllegalArgumentException("Length too long");
this.length = length;
}
}
我想测试一下,当调用长度大于MAX_LENGTH
时抛出异常。有许多方法可以测试,都有缺点:
@Test(expected = IllegalArgumentException.class)
public void testMaxLength() {
new Length(11);
}
这复制了测试用例中的常量。如果MAX_LENGTH
变得更小,这将无声地不再是边缘情况(尽管显然它应该与单独的情况配对以测试边缘的另一侧)。如果它变大,这将失败并需要手动更改(这可能不是一件坏事)。
通过为MAX_LENGTH
添加getter,然后将测试更改为:
new Length(Length.getMaxLength());
这似乎要好得多,因为如果常量变化则不需要更改测试。另一方面,它暴露了一个常量,否则它将是私有的,它具有同时测试两种方法的重大缺陷 - 如果两种方法都被破坏,测试可能会给出误报。
另一种方法是根本不使用常量,而是注入依赖:
interface MaxLength {
int getMaxLength();
}
public class Length {
public static void setMaxLength(MaxLength maxLength);
}
那么'常数'可以作为测试的一部分进行模拟(例如使用Mockito):
MaxLength mockedLength = mock(MaxLength.class);
when(mokedLength.getMaxLength()).thenReturn(17);
Length.setMaxLength(mockedLength);
new Length(18);
这似乎为很多价值增加了很多复杂性(假设没有其他原因可以注入依赖)。
在这个阶段,我倾向于使用暴露常量的第二种方法,而不是硬编码测试中的值。但这对我来说似乎并不理想。还有更好的选择吗?或者这些案例缺乏可测试性,证明存在设计缺陷?
答案 0 :(得分:7)
正如Tim在评论中提到的那样,您的目标是确保您的软件符合规范。一个这样的规范可能是最大长度总是10,此时不必测试长度为5或15的世界。
这是问自己的问题:你有多大可能想要使用不同的“常数”值来使用你的课程?我在这里引用了“常数”,因为如果你以编程方式改变价值,它根本不是一个常数,是吗? :)
如果您的值永远不会更改,则根本不能使用符号常量,只需直接比较10并根据(例如)0,3,10和11.这可能会使你的代码和测试有点难以理解(“10来自哪里?11来自哪里?”),如果你有理由改变它,肯定会很难改变数。不推荐。
如果您的值可能永远不会改变,您可以使用私有命名常量(即静态最终字段)。然后,您的代码将很容易更改,但您的测试将无法自动调整代码的方式。
您还可以放松一下打包私有可见性,这可以在同一个包中进行测试。 Javadoc(例如/** Package-private for testing. */
)或文档注释(例如@VisibleForTesting
)可能有助于明确您的意图。如果您的常量值不透明且在类之外不可用,例如URL模板或身份验证令牌,那么这是一个不错的选择。
您甚至可以将其设为公共常量,也可以为您的班级消费者提供。 对于常量Length的示例,公共静态final字段可能是最好的,假设系统的其他部分可能想知道(例如,用于UI验证提示或错误消息)。
如果您的值可能会更改,您可以按实例接受它,例如new Length(10)
或new Length().setMaxLength(10)
。 (我认为前者是依赖注入的一种形式,将常量整数计为依赖。)如果你想在测试中使用不同的值,这也是一个好主意,例如在生产中使用最大长度2048但是为实用性而对10进行测试。 要制作灵活的长度验证器,此选项可能是静态最终字段的良好升级。
只有在您的实例生命周期内值可能会发生变化时,我才会厌烦使用DI风格的值提供程序。此时,您可以以交互方式查询值,因此它根本不像常量那样表现。对于“长度”,这显然是过度杀伤,但可能不是“最大允许内存”,“最大同时连接数”或其他一些伪常数。
简而言之,你必须决定你需要多少控制权,然后你可以从那里选择最直接的选择;作为“默认”,您可能希望将其设置为可见字段或构造函数参数,因为这些参数往往具有简单性和灵活性的良好平衡。