如何测试枚举类型?

31

我目前正在尝试为一个小型库构建更完整的单元测试集。由于我们希望允许存在不同的实现,因此我们希望这个测试集是(a)通用的,以便我们可以重复使用它来测试不同的实现,以及(b)尽可能完整。对于(b)部分,我想知道是否有任何最佳实践来测试枚举类型。例如,我有一个如下所示的枚举:

public enum Month {
    January,
    February,
    ...
    December;
}

我想确保所有的枚举类型都真实存在。这样做真的必要吗?目前我正在使用Hamcrests的 assertThat,就像下面的例子一样:

assertThat(Month.January, is(notNullValue()));
缺少"January"枚举会导致编译时错误,可以通过创建所缺少的枚举类型来解决。我这里使用的是Java,但如果你的答案是针对其他语言也没关系。
编辑:正如mkato和Mark Heath都指出的那样,测试枚举可能是不必要的,因为编译器在使用不存在的枚举类型时不会编译。但我仍然想测试这些枚举,因为我们希望构建一个类似于TCK的独立测试.jar文件,可以在不同的实现上运行相同的测试。因此,我的问题更像是:测试枚举类型的最佳方法是什么?
经过再次思考后,我将上面的Hamcrest语句改为:
assertThat(Month.valueOf("January"), is(notNullValue()));

如果一月份还没出现,这个语句现在会抛出一个NPE异常。这种方法有什么问题吗?

6个回答

39
对于枚举类型,我只在它们实际上有方法时测试它们。如果是像你的例子一样纯粹的值枚举,我建议不要测。
但既然你想测试它,那么选择第二个选项比第一个好得多。第一个的问题是如果你使用 IDE,任何对枚举的重命名也会重命名测试类中的枚举。

我有一个枚举类型,其中有一个方法需要进行测试。我是单元测试的新手,无法想出一种编写该方法测试用例的方法。你能否提供一个示例或帮助我完成它? - dirtydexter
如果OP问题中的"assertThat(Month.January, is(notNullValue()));"不足以为您提供足够的示例,我认为您最好将其作为单独的问题进行提问,并提供您尝试测试的示例。听起来您可能需要关于JUnit本身的帮助。 - aberrant80

20

我同意aberrant80的看法。

对于枚举类型,只有当它们实际上包含方法时我才进行测试。 如果它是一个纯值枚举,就像你的例子一样,我会建议不要费力气去测试。

但既然你想测试它,选择第二种选项比第一种好多了。第一种的问题在于,如果你使用 IDE,任何更改枚举名称的行为都会重命名测试类中的枚举。

我还想补充一点,枚举的单元测试非常有用。如果你在一个庞大的代码库中工作,构建时间会变长,而单元测试可以更快地验证功能(测试只会构建它们所依赖的内容)。另一个非常大的优势是其他开发人员不会无意中更改您代码的功能(这是非常庞大的团队面临的一个巨大问题)。

在所有测试驱动开发中,围绕枚举方法的测试减少了代码库中的错误数量。

简单示例:

public enum Multiplier {
    DOUBLE(2.0),
    TRIPLE(3.0);

    private final double multiplier;

    Multiplier(double multiplier) {
        this.multiplier = multiplier;
    }

    Double applyMultiplier(Double value) {
        return multiplier * value;
    }

}

public class MultiplierTest {

    @Test
    public void should() {
        assertThat(Multiplier.DOUBLE.applyMultiplier(1.0), is(2.0));
        assertThat(Multiplier.TRIPLE.applyMultiplier(1.0), is(3.0));
    }
}

我有一个枚举类型,其中有一个方法需要进行测试。我是单元测试的新手,无法想出一种编写该方法测试用例的方法。你能否提供一个示例或帮助我完成它? - dirtydexter
感谢您提供示例。我也卡住了,因为我不知道如何测试枚举类中的方法。 - Erik Medina

7
通常我会说这是过度设计,但有时编写枚举的单元测试是必要的。有时枚举成员分配的值必须永远不变,否则将无法加载遗留持久化数据。同样,未使用的成员也不能删除。单元测试可用于防止开发人员在没有意识到影响的情况下进行更改。

6

你可以通过以下示例测试是否确切有某些值:

for(MyBoolean b : MyBoolean.values()) {
    switch(b) {
    case TRUE:
        break;
    case FALSE:
        break;
    default:
        throw new IllegalArgumentException(b.toString());
}

for(String s : new String[]{"TRUE", "FALSE" }) {
    MyBoolean.valueOf(s);
}

如果有人删除或添加一个值,一些测试将失败。


4
如果您在代码中使用了所有月份,IDE将不允许您编译,所以我认为您不需要单元测试。
但是,如果您正在使用反射与它们一起使用,即使删除一个月份,它也会编译,因此放置单元测试是有效的。

1
单元测试不知道你的月份将如何使用,甚至更不知道它们将来可能如何使用。这意味着即使应用程序当前无法编译所有月份都不存在,也应该为它们包括单元测试。注意:在担心没有方法的枚举等简单对象之前,请确保正确覆盖复杂对象的单元测试。 - Vince O'Sullivan

2

这是我们项目中的一个示例。

public enum Role {

    ROLE_STUDENT("LEARNER"),
    ROLE_INSTRUCTOR("INSTRUCTOR"),
    ROLE_ADMINISTRATOR("ADMINISTRATOR"),
    ROLE_TEACHER("TEACHER"),
    ROLE_TRUSTED_API("TRUSTEDAPI");

    private final String textValue;

    Role(String textValue) {
        this.textValue = textValue;
    }

    public String getTextValue() {
        return textValue;
    }
}

class RoleTest {

    @Test
    void testGetTextValue() {

        assertAll(
                () -> assertEquals("LEARNER", Role.ROLE_STUDENT.getTextValue()),
                () -> assertEquals("INSTRUCTOR", Role.ROLE_INSTRUCTOR.getTextValue()),
                () -> assertEquals("ADMINISTRATOR", Role.ROLE_ADMINISTRATOR.getTextValue()),
                () -> assertEquals("TEACHER", Role.ROLE_TEACHER.getTextValue()),
                () -> assertEquals("TRUSTEDAPI", Role.ROLE_TRUSTED_API.getTextValue())
        );
    }
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接