Wednesday, June 18, 2014

Unintended Benefits of Unit Testing: Documentation for Nothing and Testing for Free

I'm working on the geochrono[1] project a little bit at the time.  I unexpectedly came across a benefit of unit testing I'd forgotten about, documentation by testcase.  One of the first requirements for geochrono is:

The user will be allowed to add events or person chrono-locations by adding markers to a map at the appropriate location.
First implementation 
The user will be required to enter the year, month, and day of the month in three distinct textboxes before clicking on OK. The date will be checked as valid using code available at stackexchange[2].
Testcase: Send bad and good dates to date checker code.
Something like this:


The requirements and testcsases seemed simple enough.  Form a bad date and pass it into the date checking function.  Something like this should have done the trick:


assert.equal(isValidDate(new Date(1980, 100, 150)), false);

You get the idea, pass in a month that doesn't exist, (100), and a day of the month that doesn't exist either, (150).  The problem that cropped up almost immediately was that the Date object is remarkably resilient in creating a date it thinks makes sense even if you hand it garbage.  Consequently, the testcase checking to make sure a 'nonsensical' date was caught by the checker, failed.



There were a number of other combinations that resulted in valid Date objects being created.  This led to the following series of testcases that demonstrated the 'bad dates' that isValidDate will not catch:

QUnit.test( "hello test", function( assert ) {
  assert.ok( 1 == "1", "Passed!" );
  //The following are counter-examples showing how the Date object will
  //wrangle several 'bad' dates into a valid date anyway
  assert.equal(isValidDate(new Date(1980, 12, 15)), true);
  d = new Date();
  d.setFullYear(1980);
  d.setMonth(1);
  d.setDate(33);
  assert.equal(isValidDate(new Date(1980, 100, 150)), true);
  assert.equal(isValidDate(d), true);
  //If you go to this exterme, then the checker will fail
  assert.equal(isValidDate(new Date("This is junk")), false);
  //This is a valid date string
  assert.equal(isValidDate(new Date("November 17, 1989")), true);
  //but is this?
  //Ha!  It's not.  So, the secret to working with this version of
  //isValidDate is to pass in dates as text strings... Hooboy
  assert.equal(isValidDate(new Date("November 35, 1989")), false);
  //alert(d.toString());
});


So, as you can see, the testcases showed that to reuse isValidDate and get the checking behavior I'd like, I'm going to have to submit my dates in the form of a text string.

Of course, the next thing to figure out is how to guarantee that my code always ships in dates in the intended format, but I'll save that for another time.

References:
1.  http://copaseticflow.blogspot.com/2014/06/mapping-historical-events-in.html

2.  http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript

No comments: