您如何测试通用API的类型安全性?

时间:2011-07-07 04:09:04

标签: java api generics testing type-safety

您可以使用例如JUnit用于测试库的功能,但是如何测试其在泛型和通配符方面的类型安全性?

只针对编译的代码进行测试是一种“快乐路径”测试;您是否也应该针对非类型安全使用测试您的API并确认这些代码不编译?

   // how do you write and verify these kinds of "tests"?

    List<Number> numbers = new ArrayList<Number>();
    List<Object> objects = new ArrayList<Object>();

    objects.addAll(numbers); // expect: this compiles

    numbers.addAll(objects); // expect: this does not compile

那么如何验证您的通用API在编译时引发了正确的错误?您是否只是构建一个非编译代码来测试您的库,并将编译错误视为测试成功,反之亦然? (当然你必须确认这些错误与泛型相关)。

是否有促进此类测试的框架?

7 个答案:

答案 0 :(得分:6)

由于这不是传统意义上的测试(也就是说 - 你不能“运行”测试),我不认为这样的工具存在,这就是我的建议:

  1. 进行常规单元测试
  2. 在其中生成代码 - 正确的代码和错误的代码
  3. 使用Java compiler API尝试编译并检查结果
  4. 您可以为该功能制作一个易于使用的包装,并为符合您要求的任何人提供。

答案 1 :(得分:5)

听起来您正在尝试测试Java编译器,以确保在分配错误类型时(而不是测试您自己的api)会引发正确的编译错误。

如果是这种情况,为什么你不关心编译器在将Integers分配给String字段时没有失败,以及当你在尚未初始化的对象上调用方法时,以及其他数百万个编译器被认为是错误的检查他们何时编译代码?!

答案 2 :(得分:3)

我猜你的问题不仅限于泛型。我们可以向非通用代码提出相同的问题。如果您描述的工具存在,我会感到害怕。有很多人非常乐意测试他们的getter和setter(并尝试在其他人身上强制执行)。现在,他们更乐意编写新的测试,以确保访问其私有字段不会编译!噢,人性!

但是我觉得仿制药更复杂,所以你的问题没有实际意义。对于大多数程序员来说,如果他们能够最终编译他们该死的泛型代码,他们会很高兴。如果一个泛型代码没有编译,这是开发期间的常态,他们并不确定应该责怪谁。

答案 3 :(得分:1)

“您如何测试通用API的类型安全性?”恕我直言,你的问题的简短答案应该是:

  • 请勿使用任何@SuppressWarnings
  • 确保编译时没有警告(或错误)

更长的答案是“类型安全”不是API的属性,它是编程语言及其类型系统的属性。 Java 5泛型在某种意义上是类型安全的,它可以保证您在运行时不会出现类型错误(ClassCastException),除非它源自用户级转换操作(如果您使用泛型编程,你很少需要这样的演员阵容了。唯一的后门是使用原始类型与Java 5之前的代码进行互操作,但对于这些情况,编译器将发出警告,例如臭名昭​​着的“未经检查的强制转换”警告,以指示类型安全可能会受到损害。但是,如果没有这样的警告,Java将保证您的类型安全。

因此,除非您是编译器编写者(或者您不信任编译器),否则想要测试“类型安全性”似乎很奇怪。在您提供的代码示例中,如果您是ArrayList<T>的实现者,您应该只关心addAll 最灵活的类型签名,以便您编写功能正确的实现。例如,您可以将参数键入Collection<T>,也可以键入Collection<? extends T>,后者是首选,因为它更灵活。虽然您可以过度约束类型,但编程语言和编译器将确保您不能编写非类型安全的东西:例如,您根本无法为参数具有的addAll编写正确的实现输入Collection<?>Collection<? super T>

我能想到的唯一例外是,你正在为系统中一些不安全的部分编写一个外观,并希望使用泛型来强制通过外观使用这个部分的某些保证。例如,虽然Java的反射不是由类型系统控制,但是在Class<T>之类的东西中使用泛型来允许某些反射操作,例如clazz.newInstance() ,与类型系统集成。

答案 4 :(得分:0)

也许您可以在单元测试中使用Collections.checkedList()。以下示例将编译,但将抛出ClassCassException。以下示例是从@Simon G。

复制的
List<String> stringList = new ArrayList<String>();
List<Number> numberList = Collections.checkedList(new ArrayList<Number>(), Number.class);
stringList.add("a string");
List list = stringList;
numberList.addAll(list);
System.out.println("Number list is " + numberList);

答案 5 :(得分:0)

测试编译失败听起来像吠叫错误的树,然后用螺丝刀再次剥离树皮。使用正确的工具来完成正确的工作。

我认为你想要一个或多个:

  • 代码评论(可能由JRT等代码审查工具支持)。
  • 静态分析工具(FindBugs / CheckStyle)
  • 将语言切换为C ++,其实现支持concepts(可能还需要将Universe切换为存在此类实现的Universe)。

如果你真的需要将它作为'测试',你可以使用反射来强制执行任何所需的规则,比如'任何以 add 开头的函数必须有一个通用的参数'。这与custom Checkstyle rule没有什么不同,只是笨拙且不太可重用。

答案 6 :(得分:0)

好吧,在C ++中,他们试图用概念做到这一点但是从标准启动了。

使用Eclipse当Java中的某些东西无法编译时,我可以快速转换,并且错误消息非常简单。例如,如果您希望某个类型具有某个方法调用而它没有,那么您的编译器会告诉您需要知道的内容。与类型不匹配相同。

祝你好运将编译时概念构建到java:P