How refreshing

One of the things that I’ve always been a bit unsure of is the claim by dynamic languages crowd that static typing buys us nothing as the unit tests solve the same problem. It’s a nice idea but I’m a bit scared that the unit tests required would have to be quite a bit better than my unit tests usually are… Anyway, it’s nice to see Brian Marick posting about how his usual retort of ’the unit tests will catch the bugs that the static type checker would’ was wrong

Brian’s posting explains how the tests passed but the code failed in production because the object used in the test wasn’t the same as the object used in production and the dynamic conversions and coercions that went on in production were different to those that happened in the test. Moreover the test was passed a piece of data that the code under test could just use, yet in production the code was passed some data that was the wrong type and that needed to be converted and the result after conversion wasn’t quite right.

I like the fact that Brian posted about the failure of a key argument for dynamic typing vs static typing argument. I find it refreshing, in a strange kind of way. In fact I’m sitting here trying hard to come up with a fix that validates his initial position even though I should be happy that he’s provided people who wish to argue against that view with a nice, self contained, example of how static typing does add some value, sometimes. Admitting a problem like this is much more useful than simply hyping your way around it, or dismissing it. Personally I like to make choices based on negative as well as positive information and often, in our business, it’s hard to see the negatives for all the hyped up positives…

Anyway, not knowing Ruby (but having the “pickaxe book” on order…) I’ll jump in and attempt to fix the problem … The code in question is this:

def svn_log(subsystem, start_date)
  credentials = "--username notme --password not-the-real-one"
  timespan = "--revision 'HEAD:{#{start_date}}'"
  root = ""

  `svn log #{credentials} #{timespan} #{root}/#{subsystem}`

To me the first thing wrong with this code is that it’s doing more than one thing; it’s violating the SRP by being responsible for formatting a command line and executing it. But unfortunately fixing that problem doesn’t help us here. Even if we split the actual execution out of this function so that we can mock up the command executer and validate the command line the test will still pass because start_date will be correct in the test and bad in the production system… It seems the only way to test this is to test from a little higher in the call stack. This is something that I do pretty regularly; If I have object A that’s used by object B when object C does X then I’ll have unit tests for object A and unit tests for objects B and C and, assuming A and B don’t access some external or expensive resource I’ll use a real A and B, rather than mocks, when I’m testing C. These compound tests may be in addition to regular unit tests that test C in isolation, using mocks, or they may be instead of them (Sometimes you can mock too much.). Assuming Brian had a test for the code that executed this line of code:

start_date = month_before(

and then called the function above, his testing would catch the conversion problem and he wouldn’t have needed to write the article ;) I don’t think it’s quite as clear cut as simply using business facing tests though, I think it depends on the granularity of your “unit”.

For me, a unit test tests a unit, but a unit can be, and often must be, more than just a single class. Sure you need unit tests at the individual class level but you also need them at almost every level of composition above. Like all things agile you should pick the levels that you actually test at so that you get the best bang for your buck, but, in my opinion, ideally you’d have tests that test each level of composition to ensure that object interactions really are correct. With static typed languages you can probably skip more layers of composition testing than with dynamic typed languages as the compiler is validating some things for you. Perhaps that’s the value of static typing?