Cucumber be damned. I’ve figured out how to do what I needed to do in Fitnesse to test Excel sheets. This is pretty hacky, but it works (kind of – more on this later), and better yet it’s pretty simple, which to my mind is a good combination of attributes.
First let me review the problem I had. I wanted to use a CombinationFixture to check the contents of an Excel spreadsheet output by the serializer for my business class. This is a natural way to use a CombinationFixture, but it comes with a caveat: The output for the method doing the heavy lifting, combine, is defined as Object. What that means is that you effectively cannot register a ParseDelegate, as that mechanism assumes that you’ll be doing something relatively type-specific. You also can’t simply return a custom type and let it do the hard work, because that’s not how the parser precedence works.
I wrote Rick Mugridge, who seems to have misunderstood my requirements and suggested I use a finder method, which would be fine if I was trying to “fix” the inbound data, but I was not trying to do that. Rather, I was trying to fix something about the way the serialization itself behaves: The spreadsheet treatment of the double is very slightly different from FitLibrary’s internal usage, and so, in the way common to many decimal-representation floating-point issues, you get a very small difference between the value as FitLibrary interprets your test table and the value as read from the serialization.
At this point I resigned myself to hacking the problem. The obvious seam was PlugBoard’s two favourite lookups:
public class SpreadsheetCombinationFixture extends CombinationFixture { public SpreadsheetCombinationFixture() { super(); PlugBoard.lookupClosure = new LookupClosureCustom(PlugBoard.lookupClosure); PlugBoard.lookupTarget = new LookupTargetCustom(PlugBoard.lookupTarget); this.registerParseDelegate(Price.class, Price.class); }
Wrapping these two in Decorators like this gives me a nice mechanism for discovering a significant amount of information, which is my first and best tool in figuring out how to inject the functionality I want into the system.
public class LookupClosureCustom extends LookupClosureStandard { private LookupClosure innerClosure; ublic LookupClosureCustom(LookupClosure fallbackClosure){ innerClosure = fallbackClosure; } public Closure findMethodClosure(TypedObject typedObject, String methodName, int argCount) { System.out.println("Finding method closure for method "+ methodName + " on type " + typedObject.getSubject().getClass().toString()); Closure closure = innerClosure.findMethodClosure(typedObject, methodName,argCount); return closure; } } public class LookupTargetCustom extends LookupMethodTargetStandard implements LookupMethodTarget { LookupMethodTarget innerLookup; public LookupTargetCustom(LookupMethodTarget lookupTarget) { this.innerLookup = lookupTarget; } @Override public ICalledMethodTarget findTheMethodMapped(String name, int argCount, Evaluator evaluator) throws Exception { CalledMethodWrapper target =new CalledMethodWrapper(innerLookup.findTheMethodMapped(name, argCount, evaluator)); System.out.println("Result parser type: "+ target.getResultParser().getClass().toString()); return target; } }
Two key things I’ll point out here.
First, the tool I used to figure out what the system was doing was old-school printf debugging. An aside: I recently read Coders at Work, which is a lovely book that I do recommend you check out at some point, and not just because I put a give-me-money link to it there. Not enough books really explore the people in our industry. Anyway, the topic of print statements comes up again and again in that book. Nearly every person interviewed comes across as allergic to debuggers, and I think the book was written before unit and acceptance testing had really come into their own. It reinforced something I dimly remember learning back in my CGI-in-C university programming – sometimes you need to be willing to do ugly things to get a good result.
The second thing of note there is the wrapper around CalledMethodWrapper. Since I’m looking to preserve most of the system’s behavioural, decoration is again a tool of choice. In the case of ICalledMethodTarget, there are a LOT more methods that need to be implemented, so for the sake of brevity I’m omitting all of the pure-wrapper code. Just hit me up in the comments if you need some guidance on that part of the solution.
public class CalledMethodWrapper implements ICalledMethodTarget { // Omitting wrapped pass-through methods @Override public boolean checkResult(Cell arg0, Object arg1, boolean arg2, boolean arg3, TestResults arg4){ System.out.println("Checking cell " + arg0.text() + " (" + arg0.getClass().toString() + ") against result " + arg1.toString() + " (" + arg1.getClass().toString() + ")"); Price price = tryToParseAsPrice(arg0.text(), arg1); if(price != null) { System.out.println("Trying to match prices"); return price.matches(Double.valueOf(arg1.toString())); } return innerTarget.checkResult(arg0, arg1, arg2, arg3, arg4); } private Price tryToParseAsPrice(String text, Object arg1) { if(!text.contains(".")) return null; try { Price price = new Price(Double.parseDouble(text)); return price; } catch (Exception ex) { return null; } } }
A little poking around, a few blind alleys (like getResultParser), and extensive use of println statements lead me to understand that the meat of the system was in that checkResult method. It’s certainly far from elegant to look at, but that little piece of code accomplishes precisely what I set out to do: It parses a value that looks like a price as a price, and it returns true if that price looks the same as my cell-specified value.
For reference, my Price.matches method looks like this:
public boolean matches(double value) { System.out.println("Matching values: " + Double.toString(value) + "," + Double.toString(this.value)); return Math.abs(this.value - value) < 0.00005f; }
From here, additional configuration would be relatively simple. I could easily extend this system to use built-in functionality to allow each test – even each cell – to specify its own tolerances. I could implement multiple overridden matching behaviours. It’s unfortunate that the seam is more of a combination sheathe + seam, requiring a layer of sludgy wrapper code to inject my customization. But it’s not hard to understand once you get the basic idea of how this code is being called.
Unfortunately, it doesn’t work. My cells are not reporting errors, but they’re not going green either. More work awaits!