Decoupling the FX GUI


The rates engine was now easy to test but the interaction between the engine and the user wasn't. This was unfortunate as the interaction is reasonably complex. We hadn't built and tests for any of the GUI code yet, last week we fixed that...

The rates engine interacts with the user via two grid controls, a master and optional detail display and a cluster of buttons. As the live data changes the grids update. The user can freeze the display by pressing a button and optionally see live updates of the delta between the frozen values and the live data. The user can customise the terms displayed and can change the currencies involved. Different types of rate are displayed in the detail window in different ways; an outright has a smaller display than a forward forward which in turn is less complex than a cross currency forward forward. With cross currency forwards the user can enter their own far point value in the detail view and this overrides the live value and the rest of the display adjusts accordingly. Depending on the state of the data the colours of the cells change... There's a lot going on and in keeping with the style of the surrounding code it's a mess.

Whilst I was refactoring the rates engine to allow us to test it I had to take out all of the 'freeze the data and display deltas' stuff and also the user editable far points. In the original design this code was buried deep within the live data provider which, in turn, knew all about display formats and other nonsense. I didn't like the idea of deliberately breaking some functionality whilst I was refactoring but keeping it working during the manipulations required to get the FX code into a library and decouple it from the display was just too complicated.

I've not written tests for GUIs before and expected it to be hard work, however, I stumbled on a paper called "The Humble Dialog Box" by Michael Feathers of Object Mentor Inc. at just the right time. As Michael points out, all you need is another level of indirection and testing GUIs becomes much easier...

Step one was to create an interface onto our custom grid controls so that it would be easy to create a mock grid control for testing and validation purposes. Once I had the IWritableGrid interface I began to move all of the interaction between the live data and the master and detail grids out of the dialog itself and into a helper class. The helper class lives in the display library, so it's testable. Inputs from the dialog's controls are routed, via simple forwarding functions in the dialog to functions on the display helper. Output to the grids goes via the interfaces. The code can be tested by creating a couple of mock grid controls and calling methods to simulate the user pressing buttons. The live data can be plugged in, complete with our mock live data provider and we can drive the GUI display with a predetermined set of live data ticks and user actions.

Once that was working I started to put the freeze data concept back in. The original design parsed the data currently displayed in the grid and worked out the delta between the new data tick and the display. It then redisplayed the new values... Nasty. It did this from deep within the code that was dealing with the live data. Nastier. We added a 'previous data value cache' (or map, if you prefer) to the display helper. This keeps track of the tick before last and allows us to freeze the data without caring about the current display in the grid. After unpicking the display code some more it became obvious that this previous value cache was required for non frozen ticks too - the colour of the cell changes depending on whether this new tick was up or down compared to the previous value.

With the freeze display functionality back in action we had one final piece to go. The original code allows the user to override some of the live values with values of their own, it then recalculates all dependant values based on the user values and the new live data. This code was horribly complex and, again, buried deep within the live data provider. We have created types for each of the FX rates results that we deal with; outright, fwd fwd and cross ccy fwd fwd. These types don't yet hold the calculation code - they will once we start refactoring our newly testable FX library... If they did then freezing the points would be easy. I added the first calc to the rate result and allowed the user to plug values in and recalculate. Job done.

After a long hard slog we have a testable rates engine and the interaction with the user is also testable. We have test coverage for all of the special cases that originally caused us problems and we can write tests to check that the display works as expected. Now we just have to integrate the changes and release to the user. Once that's done we can get on with refactoring the rates engine and display and fixing the bugs.

Leave a comment