Re: Need help designing some JUnit tests
Eric Sosman <esosman@ieee-dot-org.invalid> wrote in
news:ht49d0$i91$1@news.eternal-september.org:
On 5/20/2010 4:47 PM, Rhino wrote:
Eric Sosman<esosman@ieee-dot-org.invalid> wrote:
[...]
For constructors, what you want to test is that they throw
exceptions when they're supposed to (e.g.,
InvalidArgumentException), and that the newly-constructed object
satisfies all the invariants it's supposed to. In your no-argument
Foo() constructor exceptions seem unlikely (possible, though: think
HeadlessException), but you might check `myFoo.getHeight() *
myFoo.getWidth() == myFoo.getArea()' or whatever.
Could you expand on this last paragraph a bit. The part about
verifying that exceptions get thrown at the appropriate time are fine
but I'm not clear on what you mean by the invariants that it is
supposed to satisfy. The last sentence is almost not quite clear. Are
you saying to find some aspect of the work that is done in the
constructor and verify that it took place, so that if it is drawing a
GUI component, that the component exists and has dimensions greater
that 0 x 0? What if the constructor does very very little - maybe
just a super() - or even nothing at all? In those cases, is it
sufficient to just do
Read the Javadoc for your Foo class, and consider what
properties a newly-constructed Foo instance should satisfy.
Test that they are satisfied -- that is, test that a newly-
constructed Foo complies with its "contract."
Okay, you've given me a broad principle and that is all well and good but
I do a lot better with at least one concrete example. It doesn't have to
be _my_ example either, just something that resembles something I'm
doing.
if (Foo == null) fail("Constructor failed to instantiate the
class");
Rhino, if you keep on spewing this sort of codecrap I'm going
to shove that horn of yours firmly up the orifice that spews.
I'm really not sure what you mean by "codecrap" here. I assume you're
saying that it won't compile but, in fact, this is a line from an actual
test case with the class name changed to Foo. Here is the full unit test,
WITHOUT the name change, copied and pasted directly from the test case
and I assure you that this does compile:
public void testGetInstance() {
StringUtils stringUtils = StringUtils.getInstance();
if (stringUtils == null) fail("testGetInstance() failed");
}
You should test that the factory method behaves as advertised.
If it can return null under some circumstances, you should check
that it does so when it's supposed to and does not when it isn't.
If it returns non-null, the thing returned will necessarily be of
the type declared for the factory method -- but not necessarily of
that "exact" type, as it might be a subclass or any arbitrary
implementation of an interface type. That may make a difference in
what you test.
Can you elaborate on this a bit? Can you show me a simple example of
a constructor returning a subclass or implementation of an interface?
No, because a constructor cannot do such a thing. But you seemed
to be talking about a factory method (although it's hard to be sure
from reading your codecrap),
Yes, I do in fact mean a factory method. And here are my constructors and
getInstance() methods, copied and pasted directly from the source:
private StringUtils() {
locale = Locale.getDefault();
localizationUtils = LocalizationUtils.getInstance(locale);
locMsg = localizationUtils.getResources(locale, MSG_PREFIX);
msgFmt.setLocale(locale);
}
private StringUtils(Locale myLocale) {
locale = myLocale;
localizationUtils = LocalizationUtils.getInstance(locale);
locMsg = localizationUtils.getResources(locale, MSG_PREFIX);
msgFmt.setLocale(locale);
}
public static StringUtils getInstance() {
return new StringUtils();
}
public static StringUtils getInstance(Locale myLocale) {
return new StringUtils(myLocale);
}
and a factory method -- any method, in
fact -- can return anything compatible with its declared type. Wasn't
it you who had the problem with SpinnerNumberModel recently, where the
getNumber() method sometimes returned a Short, sometimes a Long,
sometimes something else?
Yes, that's right. Oh, so _that's_ what you meant in your previous reply!
Again, when someone answers a question only with a generality, it's never
as clear for me as when I also hear a concrete "for instance'. I'm really
not being obtuse although I imagine it must seem like it....
I'd say it's unnecessary to test wait() and notify() and other
final methods of Object. More generally, it's probably unnecessary
to test final methods of any superclass.
But equals() is not final, and if the class being tested has
its
own equals() you should test it. (Note that it's extremely rare to
inherit equals() from a superclass unless you're inheriting it all
the way from Object undisturbed.) If you were writing tests for
Integer, you might test `new Integer(42).equals(new Integer("42"))',
for example, and `! new Integer(42).equals(new Integer("-42"))'.
So, in a nutshell, only test the methods of the parent classes if I
overrode them; otherwise, don't worry about them. That makes sense to
me!
There's a subtle point there, a conflict between "black box"
and "clear box" testing. The only way you can *know* that a subclass
inherits a non-final method rather than overriding it is to peek
into the subclass' implementation (either by looking at the source
or by using reflection). But what if somebody comes along next week
and decides to override a method you decided not to test, on the
grounds that it was not overridden?
My theory is rather weak so I'm not really up on the meanings of "black
box" and "clear box" testing, let alone the subtle differences between
them. Also, I'm not writing the code for anyone but myself right now,
except that I _would_ like to get one project's code looking as
professional as possible so that I could present it to a prospective
client or employer as a portfolio of what I can do. (And then imitate
that in future projects as well as gradually retrofit other existing
projects with what I've learned). With that in mind, would a reasonable
employer/client likely find it acceptable that I just tested the methods
I wrote and overrode myself in my classes or are they going to see me as
the biggest idiot since the development of COBOL if I fail to observe
these subtleties?
One thing you might do is run some of Super's unit tests on Sub
instances. Another might be to include a "sanity check" test in your
Sub, something that reflects on Sub and verifies that the methods
you've chosen not to test are in fact inherited.
Finally, you've got to realize that unit testing, important as it
is, is not the be-all and end-all of verifying correctness.
I have no problem with that at all. If there are certain things I don't
need to cover in unit testing, that's perfectly fine. If you or anyone
else reading this could point me a good summary of what is and is not a
concern in unit testing, that would be very helpful. Again, I have
effectively NO formal training and what much of the on-the-job stuff I
have was learned many years ago. That means that my memory of the theory
is very incomplete at this point. In short, I don't know what the
prevailing theory is on exactly what should be covered by unit testing,
acceptance testing, regression testing, et. al. I'm not going to worry
about anything beyond unit testing for the moment but if anyone can point
me to a general - and hopefully fairly brief and example-laden -
discussion of the prevailing theories of testing, that would be very
helpful.
--
Rhino