Automagic unit tests to support Object method contracts in Java?

When developing Java applications, I often override Object methods (usually equal and hashCode). I would like to somehow systematically verify that I adhere to the contract for the Object methods for each of my classes. For example, I want tests to claim that for equal objects, the hash code is also equal. I use the JUnit test environment, so it is advisable that I have some kind of JUnit solution where I can automatically generate these tests, or some test case that can somehow visit all my classes and make sure the contract is supported.

I am using JDK6 and JUnit 4.4.

+10
java unit-testing junit testing
Oct 10 '08 at 3:22
source share
8 answers
     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));
     }

Using:

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

I can’t think of anything better. Add new calls to checkObjectIdentity when you find the error.

+2
Oct 10 '08 at 12:08
source share

Just some initial thoughts on this question (which may explain why there is still no answer after a full hour!?;)

There seem to be two parts when it comes to resolving the issue:

1 / return all classes. Easy, you name jar, the Junit test initialization method:

  • check that this jar is in the junit runtime class path
  • read and load all classes in it
  • remembers only those for which equals () and hash () are declared and redefined (via Reflection)

2 / check all objects
... and this is the catch: you must create instances of these objects that create two instances, and use them for equals () tests.

This means that if your constructors accept arguments, you must consider

  • for primitive argument types (int, boolean, float, ...) or String, each combination of limit values ​​(for String, "xxx", "", null; fonr int, 0, -x, + x, -Integer.MIN , + Integer.MAX, ... etc.)
  • for non-primitive types, create an instance of those that will be passed to the constructor of the test object (which means that you must recursively take into account the constructor parameters of this parameter: primitive types or not)

Finally, not all parameters automatically created for this constructor will make sense in a functional sense, that is, some of these values ​​will not be able to create an instance due to Assert: this must be detected.

However, it seems like it is possible (you can do this code-challenge if you want), but I want to allow others first. StackOverflow readers respond to this problem as they can see a much simpler solution that I have.




To avoid the problem with combinations and to ensure that the relevant test values ​​are close to the actual code itself, I would recommend defining a highlighted annotation with a string representing the valid values ​​for the constructors. It would be located above the overridden equals () method of one of your objects.

These annotation values ​​will then be read, and instances created from them will be merged to test equals (). This would allow the number of combinations down enough

Side-node: JUnit's general test case, of course, will check that for each equals () there are for tests:

  • some annotations as described above (unless only the default constructor is available)
  • the corresponding hash () method is also overridden (if not, if he would throw an assert exception and fail in this class)
+1
Oct 10 '08 at 4:18
source share

I think VonC is on the right track, but I would even agree to something less complicated, like a parameterized test that takes a .class object (for which Object methods are tested), followed by a variable number constructor args. Then you will need to use reflection to find the constructor that matches the argument types passed in and call the constructor. This test assumes that the parameters passed to it will create a valid instance of the object.

The disadvantage of this solution is that you need to “register” each class that you want to test with this test class, and you must make sure that the correct input is specified for the constructor, which will not always be easy. In this light, I am on the fence as to whether this will more or less work than writing all the tests for each class anyway.

Vote if you think this might work ... leave a comment if you want me to send it more (if that turns out to be an acceptable solution, I can just do it anyway)

0
Oct 10 '08 at 4:27
source share

This problem does not have an “easy” solution unless you place strong restrictions on your classes.

For example, if you use several constructors for a given class, how can you ensure that all your parameters are well considered in your equals / hash methods? What about the defaults? These are things that, unfortunately, cannot be automated blindly.

0
Oct 10 '08 at 6:02
source share

[community message here, no karma is involved;)]

Here is another code-challenge :

One java class that implements a JUnit test case, with a basic method that allows you to run JUnit yourself!

This class will also be:

  • override hash () and equals ()
  • define multiple attributes (with primitive types)
  • define a default constructor, but also some constructors with various combinations of parameters
  • Define an annotation that can list “interesting” values ​​to jump to those constructors
  • annotate equals () with these "interesting" values

The test method takes a parameter for the class name (here: it will be itself), check if the class with this name has the overals method with equalities () with annotations of "interesting values".
If so, it will build the corresponding instances (by itself) based on annotations and check equals ()

This is a self-contained test class that defines a mechanism that can be generalized to any class with annotated overridden equals () function.

Please use JDK6 and JUnit4.4

This class should be copied-pasted into the appropriate package of an empty java project ... and just start;)




To add a few more considerations in response to Nicholas (see comments):

  • yes, the data needed for testing is in the candidate for the class to be tested (ie, one overrides equal values ​​and helps any “automatic tester” create the corresponding instances).
  • I don’t see exactly the same as the “testing logic”, but as useful comments on what should be done equal (and, by the way, as data that will be used by the aforementioned tester;))

Should annotations representing potential test data never be in the class itself? ... Hey, that might be a great question :)

0
Oct 10 '08 at 14:55
source share

I may not understand the question (and too often CS), but it does not seem that the problem you are describing is solvable in the general case.

In other words, the only way that unit test can assure you that the overriding method works the same way on all inputs, since the overridden method would have to try it on all inputs; in case of equal, this will mean all the states of the object.

I'm not sure if any current testing environment will automatically crop and abstract the features for you.

0
Oct 10 '08 at 15:41
source share

I have the first rough implementation, for testing peers with a constructor, using only primitive parameters here. Just copy it to the test.MyClass.java file and run it.

Warning: 1720 lines of code (0 errors in findbugs, 0 in the “modified” control style, cyclomatic complexity up to 10 for all functions).

See all the code at: Autotest for equivalent function in Java classes via annotations

0
Oct 19 '08 at 15:08
source share

A new answer to an old question, but in May 2011, Guava (formerly Google Collections) released a class that removes a lot of the template called EqualsTester . You still have to create your own instances, but he takes care of comparing each object with itself, zero, every object in the equality group, every object in every other equality group, and a secret instance that does not have to match anything. It also checks that a.equals(b) implies a.hashCode() == b.hashCode() for all of these combinations.

Example from Javadoc:

 new EqualsTester() .addEqualityGroup("hello", "h" + "ello") .addEqualityGroup("world", "wor" + "ld") .addEqualityGroup(2, 1 + 1) .testEquals(); 
0
Oct. 31 '12 at 5:59
source share



All Articles