简体   繁体   English

用Java维护对象方法契约的自动单元测试?

[英]Automagic unit tests for upholding Object method contracts in Java?

When developing Java applications, I often override Object methods (usually equals and hashCode). 在开发Java应用程序时,我经常重写Object方法(通常是equals和hashCode)。 I would like some way to systematically check that I'm adhering to the contract for Object methods for every one of my classes. 我想通过某种方式系统地检查我是否遵守每个类的Object方法的合同。 For example, I want tests that assert that for equal objects, the hash code is also equal. 例如,我希望测试断言对于相等的对象,哈希码也是相等的。 I'm using the JUnit test framework, so preferably I'd like some JUnit solution where I can automatically generate these tests, or some test case that can somehow visit all of my classes and make sure that the contract is upheld. 我正在使用JUnit测试框架,所以最好我想要一些JUnit解决方案,我可以自动生成这些测试,或者一些测试用例可以以某种方式访问​​我的所有类,并确保合同得到维护。

I'm using JDK6 and JUnit 4.4. 我正在使用JDK6和JUnit 4.4。

public static void checkObjectIdentity(Object a1, Object a2, Object b1) {
        assertEquals(a1, a2);
        assertEquals(a2, a1);
        assertNotSame(a1, a2);
        assertEquals(a1.hashCode(), a2.hashCode());
        assertFalse(a1.equals(b1));
        assertFalse(a2.equals(b1));
        assertFalse(b1.equals(a1));
        assertFalse(b1.equals(a2));
    }

Usage: 用法:

checkObjectIdentity(new Integer(3), new Integer(3), new Integer(4));

Can't think of anything better. 想不出更好的事情。 Add new calls to checkObjectIdentity when you find a bug. 发现错误时,添加对checkObjectIdentity的新调用。

Just a some initial thoughts on that question (which may explain why there are still no answer after a full hour!? ;) 只是对这个问题的一些初步想法(这可以解释为什么整整一个小时后仍然没有答案!?;)

There seems to be two parts when it comes to implement a solution to the question: 在实现问题的解决方案时,似乎有两个部分:

1/ retrieve every classes of my own. 1 /检索我自己的每个类。 Easy, you give a jar name, the Junit test initialization method would: 很简单,你给一个jar名称,Junit测试初始化​​方法会:

  • check if that jar is in the JUnit execution classpath 检查该jar是否在JUnit执行类路径中
  • read and load every classes in it 读取并加载其中的每个类
  • memorizes only those for which equals() and hash() has been declared and redefined (through Reflection) 仅记忆已声明并重新定义equals()和hash()的那些(通过Reflection)

2/ test every objects 2 /测试每个物体
... and therein lies the catch: you have to instantiate those objects, that is create two instances, and use them for equals() tests. ...其中有一个问题:你必须实例化那些对象,即创建两个实例,并将它们用于equals()测试。

That means if your constructors are taken arguments, you have to consider, 这意味着如果你的构造函数被认为是参数,你必须考虑,

  • for primitive types arguments (int, boolean, float, ...) or String, every combinations of limit values (for a String, "xxx", "", null; fonr int, 0, -x, +x, -Integer.MIN, +Integer.MAX, ... and so on) 原始类型参数(int,boolean,float,...)或String,限制值的每个组合(对于String,“xxx”,“”,null; fonr int,0,-x,+ x,-Integer .MIN,+ Integer.MAX,...等等)
  • for non-primitive types, build an instance of those to be passed to the constructor of the object to test (meaning you recursively have to consider the constructor parameters of that parameter: primitive types or not) 对于非基本类型,构建要传递给要测试的对象的构造函数的实例(意味着你递归地必须考虑该参数的构造函数参数:原始类型与否)

Finally, not every parameters automatically created for those constructor would make sense in a functional way, meaning some of those values will fail to build the instance because of an Assert: that must be detected. 最后,不是每个为这些构造函数自动创建的参数都会以函数的方式有意义,这意味着由于Assert必须检测到其中一些值将无法构建实例。

Yet it seems to be possible (you can make it a code-challenge if you want), but I want first let other StackOverflow readers respond to this issue, as they may see a far simpler solution that I am. 然而,它似乎是可能的(如果你愿意,你可以让它成为代码挑战 ),但我想首先让其他StackOverflow读者回应这个问题,因为他们可能会看到一个更简单的解决方案。


To avoid combinations problem and to keep test relevant testing values close to the actual code itself, I would recommend the definition of an dedicated annotation, with a String representing valid values for constructors. 为了避免组合问题并使测试相关测试值保持接近实际代码本身,我建议定义专用注释,其中String表示构造函数的有效值。 There would be located right above the equals() overridden method of one of your object. 将位于您的一个对象的equals()重写方法的正上方。

Those annotation values would then be read, and the instances created from those would be combined for testing equals(). 然后将读取这些注释值,并将组合这些注释值组合起来以测试equals()。 That would keep the number of combinations down enough 这将使组合数量保持足够

Side-node: a generic JUnit test case would of course check that, for each equals() to tests, there is: Side-node:一个通用的JUnit测试用例当然要检查,对于每个equals()测试,有:

  • some annotations as described above (unless there is only default constructor available) 如上所述的一些注释(除非只有默认构造函数可用)
  • a corresponding hash() method also overridden (if not, if would throw an assert exception and fail on that class) 一个相应的hash()方法也被覆盖(如果没有,如果会抛出一个断言异常并在该类上失败)

New answer for an old question, but in May of 2011 Guava (formerly Google Collections) released a class that removes a lot of the boilerplate, called EqualsTester . 一个旧问题的新答案,但是在2011年5月, Guava (以前的Google Collections)发布了一个类,它删除了许多名为EqualsTester的样板。 You still have to create your own instances but it takes care of comparing each object to itself, to null, to every object in the equality group, to every object in every other equality group, and to a secret instance that should match nothing. 您仍然需要创建自己的实例,但它需要将每个对象与自身进行比较,将null,与相等组中的每个对象,每个其他相等组中的每个对象以及应该不匹配的秘密实例进行比较。 It also checks that a.equals(b) implies a.hashCode() == b.hashCode() across all those combinations. 它还检查a.equals(b) a.hashCode() == b.hashCode()涵盖所有这些组合中的a.hashCode() == b.hashCode()

Example from Javadoc: Javadoc的示例:

new EqualsTester()
   .addEqualityGroup("hello", "h" + "ello")
   .addEqualityGroup("world", "wor" + "ld")
   .addEqualityGroup(2, 1 + 1)
   .testEquals();

I think VonC's on the right track, but I would even settle for something less sophisticated, such as a parameterized test that takes in the .class object (for which the Object methods are being tested), followed by a variable number of constructor args. 我认为VonC在正确的轨道上,但我甚至会选择一些不太复杂的东西,例如参数化测试,它接受.class对象(正在测试Object方法),然后是可变数量的构造函数args。 Then, you'd have to use reflection to find the constructor that matches the types for the passed-in arguments, and call the constructor. 然后,您必须使用反射来查找与传入参数的类型匹配的构造函数,并调用构造函数。 This test would assume that the parameters being passed into it would create a valid instance of the object. 此测试将假定传递给它的参数将创建对象的有效实例。

The downside to this solution is that you have to "register" each class you want to test with this test class, and you have to make sure that valid input is given to the constructor, which would not always be easy. 这个解决方案的缺点是你必须“注册”你想用这个测试类测试的每个类,你必须确保给构造函数提供有效的输入,这并不总是那么容易。 In that light, I'm on the fence as to whether or not this would be more or less work than manually writing all the tests for each class anyway. 从这个角度来看,无论如何,我都会考虑这是否比为每个班级手动编写所有测试更多或更少。

Vote up if you think this could work...leave a comment if you want me to flush it out more (if it turns out to be a feasible solution, I may just do this anyway) 如果您认为这可以起作用,请投票...如果您希望我更多地将其冲洗掉(如果事实证明这是一个可行的解决方案,我可能会这样做)

This problem doesn't have "easy" solution unless you're putting strong constraints on your classes. 除非您对类强加约束,否则此问题没有“简单”的解决方案。

For example, if you're using several constructors for a given class, how can you ensure that all your parameters are well taken into account in your equals/hash methods? 例如,如果您为给定的类使用多个构造函数,那么如何确保在equals / hash方法中充分考虑所有参数? How about defaults values? 默认值怎么样? These are things that, unfortunately, cannot be automated blindly. 不幸的是,这些是无法盲目实现的。

[community post here, no karma involved ;) ] [社区帖子在这里,没有涉及业力;)]

Here is another code-challenge for you: 这是您的另一个代码挑战

One java class, implementing a JUnit test case, with a main method able to launch JUnit on itself! 一个 java类,实现一个JUnit测试用例,主要方法能够自己启动JUnit!

This class will also: 这堂课还将:

  • override hash() and equals() override hash()和equals()
  • define a few attributes (with primitive types) 定义一些属性(使用基本类型)
  • define a default constructor but also some constructors with various combinations of parameters 定义默认构造函数,但也定义一些具有各种参数组合的构造函数
  • define an annotation able to enumerate "interesting" values to pass to those constructor 定义一个能够枚举“有趣”值以传递给那些构造函数的注释
  • annotate equals() with those "interesting" values 使用那些“有趣”值注释equals()

The test method takes a class name parameter (here: it will be itself), check if the class with that name has an equals() overridden method with "interesting values" annotations. 测试方法采用类名参数(这里:它将是自身),检查具有该名称的类是否具有带有“有趣值”注释的equals()重写方法。
If it does, it will builds the appropriate instances (of itself) based on the annotations, and test equals() 如果是,它将根据注释构建适当的实例(自身),并测试equals()

This is a self-contained test class, which defines a mechanism able to be generalized to any class with an annotated overridden equals() function. 这是一个独立的测试类,它定义了一个机制,可以推广到任何具有带注释的重写equals()函数的类。

Please Use JDK6 and JUnit4.4 请使用JDK6和JUnit4.4

That class should be copied-paste in the appropriate package of an empty java project... and just run ;) 应该将这个类复制粘贴到一个空的java项目的相应包中...然后运行;)


To add some more thought, in response to Nicolas (see comments): 为了回应尼古拉斯(见评论),增加一些想法:

  • yes the data needed for test are within the class candidate to be tested (that is, the one overriding equals and helping any 'automatic tester' to build appropriate instances) 是的,测试所需的数据在要测试的候选类别内(即,一个覆盖等于并帮助任何'自动测试器'构建适当的实例)
  • I do not see that exactly as "testing logic", but as useful comments on what is supposed to do the equals (and incidentally as data to be exploited by the aforementioned tester ;) ) 我不认为这与“测试逻辑” 完全相同 ,而是作为对应该做什么等于的有用的评论(顺便说一句,作为上述测试人员要利用的数据;))

Should annotations representing potential testing data never ever be in the class itself ?... Hey that could be a great question to ask :) 表示潜在测试数据的注释是否永远不会出现在类中?...嘿,这可能是一个很好的问题要问:)

Maybe I'm misunderstanding the question (and being too CS), but it doesn't sound like the problem you're describing is decidable in the general case. 也许我误解了这个问题(而且太过于CS),但听起来你所描述的问题在一般情况下是不可判定的。

In other words, the only way a unit test can assure you that an overriding method works the same on all inputs as the overridden method would be to try it on all the inputs; 换句话说,单元测试的唯一方法可以确保覆盖方法在所有输入上的工作方式相同,因为重写的方法是在所有输入上尝试它; in the case of equals, that would mean all object states. 在equals的情况下,这将意味着所有对象状态。

I am not sure if any current test framework will automatically trim down and abstract the possibilities for you. 我不确定任何当前的测试框架是否会自动减少并抽象出你的可能性。

I have a first rough implementation, for equals testing with Constructor using only primitive parameters here. 我有一个第一个粗略的实现,在这里只使用原始参数与Constructor进行equals测试。 Just copy-paste it in test.MyClass.java file and run it. 只需将其复制粘贴到test.MyClass.java文件中并运行即可。

Warning: 1720 lines of code (0 errors in findbugs, 0 in "modified" checkstyle, cyclomatic complexity under 10 for all functions). 警告:1720行代码(在findbugs中为0个错误,在“已修改”的checkstyle中为0,对于所有函数,圈数复杂度为10以下)。

See all the code at: Auto-test for equals function in java classes through annotations 查看以下所有代码: 通过注释在java类中自动测试equals函数

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM