Thinking about the model
One of the first things I do (having an understanding of the problem domain) is sketch out a very quick draft class model. I use a couple of very simple principles to help me here
- Examine the requirements and look for nouns
- Examine the requirements and look for verbs (they are the glue between the nouns)
- Examine the requirements and look for adjectives (they help e identify the attributes of the nouns)
The requirements state that the data will be extracted from the old system and this won't be our responsible, but we will be responsible for the correcting and formatting of the data.
There is also the following line in the requirements "The COTS product expects the data to be correct and in the correct format – time entries must be in 24hr format, hourly rate must be decimal format of two decimal points, hours worked and pay must be in a decimal format of two decimal points."
So our application is all about taking an input stream of times, ensuring they are of the correct format, if they are not correcting them, then using the corrected values to calculate the hours worked and pay and displaying it in the correct format.
Using the three techniques above I need something to validate the times, something to format the times and something to calculate the hours worked and pay. As far as the problem description goes, it's a simple data in, process it, then spit it out. We don't need to hold tables of the input values and their associated outputted values. So I see two classes, Something to format the time (I'm going to call this TimeFormatter) and something to calculate the pay (I'm going to call this PayCalculator). What do you think?
My next concern is identifying whether there is any relationship between the two classes that I've specified (TimeFormatter and PayCalulator). My thinking here is pretty simple, calculating the pay is not a formatting a time, and just because I format I may not want to automatically calculate the pay, there's no cause and effect here, they are two very different concerns! So far now I'm going to stick with separate classes that are not related to each except that the time produced by TimeFormatter can used in PayCalculator.
Separation of concerns
One of the crucial lessons of working with FtiNesse is that you should not place all the logic in the fixture. In actual fact it’s bad practice to place your business logic in the fixture. The fixture should be used as a gateway to your business classes. What we actually want to do is to create a separation between your business logic and the fitnesse harness as shown here
This model makes it possible to view the contents of the business objects through any channel, a GUI, Web page or even a fixture. If we didn't provide the Business Session Object above, each of the channels shown would have to provide the same logic to coordinate the behaviour of the Business Objects. So what you would actually see is that the channels are polluted with Business Object information and the Business Objects are polluted with channel information. This would make changing either side difficult and after a while the whole code base would become maintainable.
View the above model through the prism of a sequence diagram
In this contrived model the channel object is interacting directly with the business objects (entity objects), sequencing their actions, linking and unlinking them to follow the flow of a use case or story. This is a crazy approach because the next channel that wants to interact with the business objects would more than likely repeat the same pattern of behaviour, the only difference being the actual channel or means of communicating with the outside world. On examination you would find that the pattern of behaviour doesn't actually change.
A better way to understand this model is to think of it in terms of UML use cases
A use case represents the sequence of interactions between the actor and the system to achieve a business goal. NEED MORE INFO ON WHAT THIS MEANS
When we translate UML models to code, it is good practice to create a class for each use case. We call these <<controllers>>. They are essentially objects that coordinate business objects to achieve the business goal. You might be wondering why is there a Calculate Pay but no Format Time. Well, the whole purpose of the application is to calculate the correct pay (read the initial requirements), the problem was that the time entries were in the wrong format, so formatting the time is not a business goal.
Now we have this use case model, our above model would be better modelled as follows
Notice that each use case has its own controller.
Wiring the model
A fitnesse fixture is active for the lifetime of a fitnesse test page. So each row of data is pumped into the single instance of an instantiated fixture. So for the above model to work one approach is make your controllers singletons, each being accessed by several fixtures. Each of these fixtures are called from a single fitnesse test page, as the model below shows. By wiring your model like this, it is possible to pass data between fixture tests on the same fitnesse page.