依赖于常数值的单元测试代码



考虑以下(完全人为的)示例:

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);

这似乎增加了很多复杂性,但没有太多价值(假设没有其他理由注入依赖项)。

在这个阶段,我倾向于使用第二种方法来公开常量,而不是在测试中对值进行硬编码。但这对我来说似乎并不理想。有更好的选择吗?或者,这些案例缺乏可测试性是否说明了设计缺陷?

正如Tim在评论中提到的,您的目标是确保您的软件按照规范运行。一个这样的规范可能是最大长度总是10,在这一点上,没有必要测试长度为5或15的世界。

这是一个要问自己的问题:您希望使用具有不同"常量"值的类的可能性有多大我在这里引用了"常量",因为如果你通过编程改变值,它根本不是一个常量,是吗?:)

  • 如果你的值永远不会改变,你就根本不能使用符号常量,只需直接与10进行比较,并基于(比如)0、3、10和11进行测试。这可能会让你的代码和测试有点难以理解("10来自哪里?11来自哪里?"),如果你有理由改变数字,肯定会让你很难改变。不推荐。

  • 如果您的值可能永远不会改变,您可以使用一个私有的命名常量(即静态的最终字段),正如您所做的那样。然后,您的代码将很容易更改,尽管您的测试将无法自动调整代码的方式。

    • 您也可以放松,打包私有可见性,在同一个包中进行测试。Javadoc(例如/** Package-private for testing. */)或文档注释(例如@VisibleForTesting)可能有助于明确您的意图。如果您的常量值是不透明的,并且在类之外不可用,例如URL模板或身份验证令牌,那么这是一个不错的选项。

    • 您甚至可以将其设置为公共常量,这样您的类的消费者也可以使用它对于您的常量长度示例,公共静态最终字段可能是最好的,假设您的系统的其他部分可能想知道这一点(例如,UI验证提示或错误消息)。

  • 如果您的值可能发生更改,您可以按实例接受它,如new Length(10)new Length().setMaxLength(10)。(我认为前者是一种依赖注入形式,将常数整数作为依赖项计数。)如果你想在测试中使用不同的值,比如在生产中使用2048的最大长度,但为了实用性起见,使用10进行测试,这也是一个好主意要制作一个灵活的长度验证器,这个选项可能是从静态final字段升级而来的一个好方法

  • 只有当您的值在实例的生命周期内可能发生更改时,我才会使用DI样式的值提供程序。在这一点上,您可以交互式地查询该值,这样它的行为就不会像常量一样。对于"长度",这显然有些过头了,但对于"允许的最大内存"、"同时连接的最大数量"或其他类似的伪常数,这可能不是。

简而言之,你必须决定你需要多少控制,然后你可以从中选择最直接的选择;作为"默认值",您可能希望将其设置为可见字段或构造函数参数,因为它们往往在简单性和灵活性之间取得良好的平衡。

相关内容

  • 没有找到相关文章

最新更新