Re: Unit testing - Breaking bad habits
- From: Harold Yarmouth <hyarmouth991476@xxxxxxxxxxx>
- Date: Fri, 07 Nov 2008 02:02:11 -0500
Tom Anderson wrote:
On Mon, 3 Nov 2008, james.bartow@xxxxxxxxx wrote:
I'm interested in practicing at least some level of Test Driven Design. The problem is that I'm so set in my ways that it's hard to see the "right" way to do this. Particularly as I do a lot of CRUD- style Web work, my code doesn't resemble the simple examples very much, and Mock objects isn't where I'd prefer to start.
I think this is actually a hard place to be in for unit testing. In the post i've just made in this thread, i talk about how we did testing on a web app, where we only wrote end-to-end tests.
Those end-to-end tests can sometimes be exceptionally simple.
In particular, today I performed an end-to-end test of a function of a system by:
* Right click main class in class browser in NetBeans
* Pick "Run".
* Examine left half of resulting on-screen JFrame.
* Close the JFrame.
* Answer "Yes" to "Are you sure?" prompt.
Possibly the shortest test ever, short of "java -jar HelloWorld.jar", and testing a whole lot more.
I doubt I'll have such a short-and-sweet test again in my lifetime, though. This particular app, on startup, queries a DB with the last-used query and uses it to populate a JList control with objects for the user to manipulate. I'd coded the actual querying functionality (~1500LOC in 15 classes, most nested/inner in a Query class, with quite a bit of delegation, inversion of control, and strategy pattern, plus conversion of Query objects to a string representation and parsing of same) over a period of 2 days and then changed the application code that populated the JList to go through the Query class instead of using a hardcoded particular query string sent to the backend. Essentially, Query was written and dropped in place of a former mock object.
The testing exposed a grand total of three bugs, all of which had been fixed roughly five minutes after test/debug cycles began.
One was a typo -- child1 and child1 instead of child1 and child2 (did you just guess that maybe there are trees involved? :-)), one was a subtler bug with the same consequences (presence of item X in collection Y was used where presence of as many occurrences of X in Y as in Z was what was needed -- fix was to add one line of code), and one was a devilish bug in the output-sorting routine (every comparator derived from a base class designed to fall back on a modification-date comparator on otherwise-equal items -- including the modification-date comparator, which therefore blew up with StackOverflowError. Fix was to make the modification-date comparator a special case by putting a bit of code in the base class; about four lines of code, as I wrapped the problem line in if (!(this instanceof DateComp)) { and three lines } else {, doSomethingElse() (not the real name, but DateComp is), and }.)
The first two bugs caused a devilish result -- nothing. The JList came up empty, no exception trace, no other signs of incorrect behavior. Silent failure is always the hardest thing to track down (except maybe for inconsistent, intermittent, random failure, which usually boils down to concurrency bugs in the case of a Java system, and is much more common and has even more devilish causes in C/C++ systems). My first resort was to suspect the code that optimized a tree somewhere -- an unoptimized tree would work but be slow. I disabled the call to the optimizer and it worked. Checked the part of the optimizer code relevant to the query tested, starting with the prime suspect code, a bit that decides if something is redundant and throws it out. There I immediately spotted the typo, and saw by direct inspection that it would indeed cause an empty JList at the frontend.
Reran the system, with the optimizer reenabled -- empty JList. Arrrgh! Back to the same block of code, and found that the code actually removing redundant items was removing every occurrence instead of one per call. So when two parts of the query parse tree evaluate identical and are logical-or'd together (think "return all widgets that are green OR blue" -- so the output should be everything matching the first and everything matching the second -- and then think the default initial query if no saved file somehow wound up "return all widgets that are green OR green") it would go to remove exactly one of them, but the remove code would remove both (think "return all widgets that are <null>", which query obviously matches no widgets).
Fixed it to remove only one item per remove and reran the app -- right click main class, "Run", one glance, everything is working.
Now THAT is an end-to-end test. :-)
(Next up: a user interface for querying. Right now, on shutdown it saves the last-used query, on startup it retrieves it and uses it, and if it's not found it uses a default query, though it still goes through Query instead of directly invoking the backend with a hardcoded String constant. The save/restore testing exposed a fourth bug in all that code, which was a particularly stupid one: if (cached != null) sb.append(cached); sb.append(cached = getFoo()); -- can you tell this was refactored from an implementation that had a "return" inside the if block? Only showed up as a parse error at startup after a sequence of delete saved session state, successful startup, successful shutdown. Fortunately the saved session state is a flat-ASCII file and it was easy to see by directly inspecting it in a text editor that part of the query was being repeated with no boolean operator between the repetitions, causing a syntax error. But this bug was provoked only by a slightly more involved test procedure, involving two successive startups from a clean slate instead of one. On the other hand, on the second startup, the error appeared before the JList or any of the rest of it did.)
.
- References:
- Unit testing - Breaking bad habits
- From: james.bartow@xxxxxxxxx
- Re: Unit testing - Breaking bad habits
- From: Tom Anderson
- Unit testing - Breaking bad habits
- Prev by Date: Re: Calendar Issue
- Next by Date: Code generation in Java
- Previous by thread: Re: Unit testing - Breaking bad habits
- Next by thread: Re: Unit testing - Breaking bad habits
- Index(es):
Relevant Pages
|