Refactoring the Test
At this point, the test for equality is complete. It exists in a single method, with a handful of guiding comments as to what each segment of the test means. The comments in this case are an anti-pattern. Guiding comments suggest that you should decompose the testEquality method (see Listing 12).
Listing 12 Decomposing the testEquality method.
public void testEquality() { Card card1 = new Card(Rank.king, Suit.hearts); Card card1copy = new Card(Rank.king, Suit.hearts); Card card1copy2 = new Card(Rank.king, Suit.hearts); Card card2 = new Card(Rank.queen, Suit.hearts); Card card3 = new Card(Rank.king, Suit.diamonds); assertTrue(card1.equals(card1copy)); assertFalse(card1.equals(card2)); assertFalse(card1.equals(card3)); Card card1subtype = new Card(Rank.king, Suit.hearts) {}; assertFalse(card1.equals(card1subtype)); assertReflexivity(card1); assertSymmetry(card1, card1copy); assertTransitivity(card1, card1copy, card1copy2); assertConsistency(card1, card1copy, card2); assertNullComparison(card1); } private void assertNullComparison(Card card1) { assertFalse(card1.equals(null)); } private void assertConsistency(Card card1, Card card1copy, Card card2) { assertTrue(card1.equals(card1copy)); assertFalse(card1.equals(card2)); } private void assertTransitivity(Card card1, Card card1copy, Card card1copy2) { assertTrue(card1copy.equals(card1copy2)); assertTrue(card1.equals(card1copy2)); } private void assertSymmetry(Card card1, Card card1copy) { assertTrue(card1.equals(card1copy)); assertTrue(card1copy.equals(card1)); } private void assertReflexivity(Card card1) { assertTrue(card1.equals(card1)); }
Now that you’ve gone through all the effort of building an equality test, I’ll let you know that at least one tool out there will save you this effort going forward. The open source product GSBase provides a class called EqualsTester. EqualsTester requires you to provide four discrete objects: two equal objects, one unequal object, and one object of a subtype. EqualsTester then does all the work of verifying the contract for you.
You could easily develop such a tool yourself by eliminating duplication in the equality tests you write. Once you have to build a second class that requires an equals method, look for the opportunity to construct your own EqualsTester class. If you look at the various assert methods that I extracted, you’ll notice that nothing about them is really specific to the Card class. You could easily substitute Object as the argument types, making these methods readily reusable.
The primary benefit of using GSBase is adherence to a more widely used technique. Also, you’ll save about a half hour in the long run by not building your own tool. Personally, I think you’ll learn more by building your own tool. You’ll also get more robust transitivity testing, which might just save you in the rare esoteric case where you need it.