Re: Basic JUnit questions
On 3/19/2010 1:40 AM, Daniel Pitts wrote:
On 3/18/2010 5:44 PM, Eric Sosman wrote:
[...]
Sorry if I wasn't clear: By referring to the "pair," I meant
trying to test that x.equals(y) -> x.hashCode() == y.hashCode(),
or that x.hashCode() != y.hashCode() -> !x.equals(y). Testing
equals() alone may make sense. Testing hashCode() alone almost
certainly doesn't. The implications must hold, but I'm not
convinced testing is the right way to verify that they do.
if your objects behavior depends on correct functioning of that method
pair, then you should definitely unit test them.
Usually it's not the object's own behavior, but the
behavior of a HashMap or similar that might use your object
as a key, possibly in some circumstance you didn't anticipate.
At the very least, testing the first is important (x.equals(y) ->
x.hashCode() == y.hashCode()).
Yeah, but how do you test it and have confidence? You can
create a bunch of equal objects and compare hashCodes, but how
do you know you've covered the space of all glitches? For
example, you might generate the String "Hello" as a literal
and again by assembling it in a StringBuilder, and find that
the two Strings' hashCodes are equal; that's fine. But what if
they had been six characters long instead of five, or if they'd
had some other genesis?
"How do you know when you've tested enough?" is, of course,
a question that besets all testing. But with respect to the
equals/hashCode pair, I suggest that code inspection is a
superior approach, because it can lead to complete confidence
instead of to "Well, I've never seen it misbehave." If you
can show that
1) The result of the equals() method depends only on the
instance variables of the two objects, and not on any
"external" datum (once class identity is satisfied), and
2) The result of the hashCode() method depends only on the
instance variables of one instance, and
3) The variables used by hashCode() are a subset of those
used by equals()
.... then you've *proved* the methods consistent. I submit that
a solid proof is preferable to anything testing can yield, and
that these methods are nearly always simple enough to make
proof not merely feasible but straightforward.
Actually, if you test that for all edge/general cases,
Knowing where the edge cases are requires knowledge of the
way the methods are implemented, or at the very least an informed
guess about the implementation. If you're willing to examine the
code to guide the writing of the test, it seems silly not to go
ahead and do the proof.
Ah, well: Different strokes for different folks.
then the second
one is probably irrelevant (or redundant)
The two implications are logically equivalent. There's an
old story about two friends who set out to test "All crows are
black." The first spent a lot of time and money, traveled the
wide world over looking for crows, and recorded that every crow
he saw was black. "Testing can't *prove* crowblackness," he said,
"but by examining a large sample of crows and finding all of them
black I have increased my level of confidence in the proposition."
The second guy was a logician, and observed that the statement
"All non-black things are non-crows" is logically equivalent to
the original: Prove either, and you have proved the other. So he
just sat back in his easy chair and let his eyes wander to non-black
things in his field of view, observing that every non-black thing he
saw was a non-crow. "Testing can't *prove* nonblacknoncrowness,"
he said, "but by examining a large sample of non-black things and
finding all of them to be non-crows I have increased my level of
confidence in both propositions. Plus, I've saved a lot of time
and money and have been lavishing it on my absent friend's main
squeeze, to our considerable enjoyment."
More prosaically, one of the implications might be easier to
test than the other.
--
Eric Sosman
esosman@ieee-dot-org.invalid