- All Things Tested Equally
- Building Equality by Assertion
- Ensuring Adherence to the Contract for Equality
- Refactoring the Test
- Back to the Deck Test
- Final Notes
Building Equality by Assertion
Listing 3 shows the first bit of test to add to CardTest.
Listing 3 First assertion: equal objects.
public void testEquality() { Card card1 = new Card(Rank.king, Suit.hearts); Card card1copy = new Card(Rank.king, Suit.hearts); assertTrue(card1.equals(card1copy)); }
Notice that I chose not to use assertEquals here. The equals method is what I’m testing; I’d rather make that fact explicit.
Because Object defines equals, the test compiles. But it fails, since the Object definition is memory equivalence. Here’s the simplest route to getting the test to pass:
public boolean equals(Card card) { return true; }
That seems wrong, since you probably know that a proper equals method overrides the Object definition. And the Object equals method takes an Object, not a Card, as an argument. Hang tight, all will be cleared up soon (see Listing 4).
Listing 4 Second assertion: unequal objects.
public void testEquality() { Card card1 = new Card(Rank.king, Suit.hearts); Card card1copy = new Card(Rank.king, Suit.hearts); Card card2 = new Card(Rank.queen, Suit.hearts); assertTrue(card1.equals(card1copy)); assertFalse(card1.equals(card2)); }
A hard-coded return of true will no longer suffice, now that we have a card2 which is unequal from card1:
public boolean equals(Card card) { return this.getRank().equals(card.getRank()); }
We should add another card, to demonstrate how any difference between suit will also fail equality (see Listing 5).
Listing 5 Third assertion: varying by suit.
public void testEquality() { Card card1 = new Card(Rank.king, Suit.hearts); Card card1copy = 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)); }
Adding this distinction allows us to complete the detailed field comparisons between two cards:
public boolean equals(Card card) { return this.getRank().equals(card.getRank()) "" this.getSuit().equals(card.getSuit()); }
We should add an "apples and oranges" test to ensure that comparison of a Card to any other type of object—including an object that’s of a subtype of Card—fails (see Listing 6).
Listing 6 Adding an "apples and oranges" test.
public void testEquality() { Card card1 = new Card(Rank.king, Suit.hearts); ... Card card1subtype = new Card(Rank.king, Suit.hearts) {}; assertFalse(card1.equals(card1subtype)); }
Providing the braces ({}) at the end of the card1subtype instantiation creates an instance that is of an anonymous subtype of Card.
The new assertion should fail, but the test passes. The problem, as I mentioned earlier, is that I didn’t override the Object equals method properly. You’ll need to change the method signature, which will require you to cast the argument before using it:
@Override public boolean equals(Object object) { Card card = (Card)object; return this.getRank().equals(card.getRank()) "" this.getSuit().equals(card.getSuit()); }
Of course, this code throws a ClassCastException in the test: The final assertion passes a String literal to the equals method. Fixing the problem is a matter of checking type upon entry to equals—one of the rare times that checking against object type is acceptable:
@Override public boolean equals(Object object) { if (object.getClass() != this.getClass()) return false; Card card = (Card)object; return this.getRank().equals(card.getRank()) "" this.getSuit().equals(card.getSuit()); }
The implementation for equality would seem to be complete. Or is it?