Test Pages: 3
right, 1 wrong, 0 ignored, 0 exceptions Assertions:
163 right, 2 wrong, 0 ignored, 0 exceptions
12
right, 0 wrong, 0 ignored, 0 exceptions GuiTest
8
right, 0 wrong, 0 ignored, 0 exceptions GuiTestWithOtherFixtures
33
right, 2 wrong, 0 ignored, 0 exceptions TestingDatasets
110
right, 0 wrong, 0 ignored, 0 exceptions TestingWithFitNesse
This is only the HTML version of the original WiKi-based tutorial.
You should download it from here to experience the real thing.
Test Output
<
New Requirements | Gui
Test with other Fixtures >
The ActionFixture is based on the following metaphor:
Meaning with this Fixture you can simulate the user interactions. In order to fool around with this Fixture we created a tiny little GUI for our time recording system.

Now we would like to test our system using the ActionFixture by clicking the Test button:
Tried it? Cooool..., but how does this work? Let's have a look at the relevant UI code first. You will find this code in the classes
For us the focus is on the class TimeRecordingUI. Here an excerpt with UI components of interest:
So the table describes the workflow and the expected results. But the real interaction with the UI components happens in our Fixture. One thing that's remarkable is that we are dealing with two Fixtures: The ActionFixture, and the Fixture that actually performs the interaction with the UI components. How does this work? Let's have a closer look to the cycle of an ActionFixture test:
That means in a table row like
|enter|from|9:00|
a method from() is called with the value 9:00 as parameter.
|press|add|
calls the method add()
|check|working time|9.5|
calls the method workingTime() and checks if the returned value is 9.5. If so, the cell is turned green, otherwise red.
This means our Fixture has to provide the appropriate methods. Let's see:
The methods from(), to() and add() do not need any further explanation. workingTime() and sum() are not that hard either, they just retrieve the needed value from the TableModel.
|slowMotion|200|
Well, if we would run the original fit.ActionFixture you could hardly follow the UI interactions, since they are much too fast. That's why we made our own extension de.fitsample.ActionFixture, which provides the additional (optional) command slowMotion. If slow motion is used the Fixture will take a nap after each command. The value after slowMotion is the duration of the nap in milliseconds, which is in our case a nap of 200ms.
< New Requirements | Gui Test with other Fixtures >
UI-based TimeRecording Test
I can hear the FIT envangelists yelling Don't you never ever perform FIT tests based on the UI. Test the business logic for christ's sake. Well, nail me to the cross if you like. Testing the business logic is one thing, but this doesn't help if the graphical user interface is not working correctly. So if you are a first class hardliner, skip to Testing Datasets. You're still reading? So let's go on. FIT provides a special Fixture for testing UI interactions: The ActionFixture.The ActionFixture is based on the following metaphor:
- With enter you enter values into fields.
- The action press means clicking a button.
- With check you check values of fields.
Meaning with this Fixture you can simulate the user interactions. In order to fool around with this Fixture we created a tiny little GUI for our time recording system.

Now we would like to test our system using the ActionFixture by clicking the Test button:
| de.fitsample.ActionFixture | ||
| slowMotion | 200 | |
| start | de.fitsample.timerecording.ui.TimeRecordingUIFixture | |
| enter | day | 1 |
| enter | from | 6:30 |
| enter | to | 12:00 |
| press | add | |
| enter | from | 13:30 |
| enter | to | 16:15 |
| press | add | |
| check | working time | 8.25 |
| check | sum | 8.25 |
| enter | day | 2 |
| enter | from | 6:30 |
| enter | to | 16:15 |
| press | add | |
| check | working time | 9 |
| check | sum | 17.25 |
| enter | day | 3 |
| enter | from | 10:00 |
| enter | to | 12:00 |
| press | add | |
| check | working time | 2 |
| check | sum | 19.25 |
| enter | from | 12:45 |
| enter | to | 17:00 |
| press | add | |
| check | working time | 6.25 |
| check | sum | 23.5 |
| enter | day | 4 |
| enter | from | 8:00 |
| enter | to | 12:00 |
| press | add | |
| check | working time | 4 |
| check | sum | 27.5 |
| enter | from | 13:00 |
| enter | to | 18:30 |
| press | add | |
| check | working time | 9.5 |
| check | sum | 33 |
Tried it? Cooool..., but how does this work? Let's have a look at the relevant UI code first. You will find this code in the classes
- http://files/FitTutorial/FitSample/src/de/fitsample/timerecording/ui/TimeRecordingTable.java
- http://files/FitTutorial/FitSample/src/de/fitsample/timerecording/ui/TimeRecordingTableModel.java
- http://files/FitTutorial/FitSample/src/de/fitsample/timerecording/ui/TimeRecordingUI.java
For us the focus is on the class TimeRecordingUI. Here an excerpt with UI components of interest:
public class TimeRecordingUI extends JFrame {
protected JButton addButton;
protected JTextField dayTextField;
protected JTextField fromTextField;
protected JTextField toTextField;
protected JTable timeRecordTable;
...
So the table describes the workflow and the expected results. But the real interaction with the UI components happens in our Fixture. One thing that's remarkable is that we are dealing with two Fixtures: The ActionFixture, and the Fixture that actually performs the interaction with the UI components. How does this work? Let's have a closer look to the cycle of an ActionFixture test:
- In the first row the ActionFixture is created which then takes control.
- The action start creates our real Fixture.
- Now if the ActionFixture finds an enter, it will search for a method with a name that comes after enter and calls it with the value in the next cell as a parameter.
- On press a method with the name found in the next cell is called.
- On check a method with the name found in the next cell is called. The value returned be the method is compared to the expected value specified in the next cell.
That means in a table row like
|enter|from|9:00|
a method from() is called with the value 9:00 as parameter.
|press|add|
calls the method add()
|check|working time|9.5|
calls the method workingTime() and checks if the returned value is 9.5. If so, the cell is turned green, otherwise red.
This means our Fixture has to provide the appropriate methods. Let's see:
public class TimeRecordingUIFixture extends Fixture {
private TimeRecordingUI timeRecordingUI;
public TimeRecordingUIFixture() {
timeRecordingUI = new TimeRecordingUI();
timeRecordingUI.pack();
timeRecordingUI.setVisible(true);
}
public void day(String day) {
timeRecordingUI.dayTextField.setText(day);
}
public void from(String from) {
timeRecordingUI.fromTextField.setText(from);
}
public void to(String to) {
timeRecordingUI.toTextField.setText(to);
}
public void add() {
timeRecordingUI.addButton.doClick();
}
public double workingTime() {
TableModel model = timeRecordingUI.timeRecordTable.getModel();
Object value = model.getValueAt(model.getRowCount() - 1,
TimeRecordingTableModel.WORKING_TIME_COLUMN);
return ((Double) value).doubleValue();
}
public double sum() {
TableModel model = timeRecordingUI.timeRecordTable.getModel();
Object value = model.getValueAt(model.getRowCount() - 1,
TimeRecordingTableModel.SUM_COLUMN);
return ((Double) value).doubleValue();
}
}
The methods from(), to() and add() do not need any further explanation. workingTime() and sum() are not that hard either, they just retrieve the needed value from the TableModel.
Slow Motion
In the second row there is something we did not explain yet:|slowMotion|200|
Well, if we would run the original fit.ActionFixture you could hardly follow the UI interactions, since they are much too fast. That's why we made our own extension de.fitsample.ActionFixture, which provides the additional (optional) command slowMotion. If slow motion is used the Fixture will take a nap after each command. The value after slowMotion is the duration of the nap in milliseconds, which is in our case a nap of 200ms.
public class ActionFixture extends fit.ActionFixture {
private long slowMotionMs;
public void slowMotion() {
slowMotionMs = Long.parseLong(cells.more.text());
}
public void enter() throws Exception {
super.enter();
sleep();
}
public void check() throws Exception {
super.check();
sleep();
}
public void press() throws Exception {
super.press();
sleep();
}
public void start() throws Exception {
super.start();
sleep();
}
protected void sleep() {
if (slowMotionMs < 1) {
return;
}
try {
Thread.sleep(slowMotionMs);
} catch (InterruptedException excep) {
excep.printStackTrace();
}
}
}
< New Requirements | Gui Test with other Fixtures >
< GUI Test
| Testing Datasets >
Is this the only way to perform UI tests with FIT? Nope. Can do it with any Fixture. For example with our well known ColumnFixture. Let's have a look at our UI again:

Now we write a table that represents the data on the UI as simple as possible:
And here we go, click the Test button...
...cool, the same user interactions we saw with our ActionFixture, but the table is much more compact. So how the heck does this work now? This is done in our new Fixture:
Hmm, not that complicated. The ColumnFixture assigns values to the members day, from and to, and execute() performs the UI interactions. workingTime() and sum() are pretty much the same as in our ActionFixture.
Alright, but what about that sleep() method? This is that slow motion thing again, which we already had in our ActionFixture.
< GUI Test | Testing Datasets >
Alternatives to ActionFixture
So with ActionFixture we directly test the UI by simulating user interactions. This workflow oriented do-this-then-do-that approach is quite nice, but the resulting table is anything but intuitive. Our first attempt with the ColumnFixture was much more descriptive.Is this the only way to perform UI tests with FIT? Nope. Can do it with any Fixture. For example with our well known ColumnFixture. Let's have a look at our UI again:

Now we write a table that represents the data on the UI as simple as possible:
| de.fitsample.timerecording.ui.TimeRecordingUIColumnFixture | ||||
| day | from | to | working time? | sum? |
| 1 | 6:30 | 12:00 | 5.5 | 5.5 |
| 1 | 13:30 | 16:15 | 8.25 | 8.25 |
| 2 | 06:30 | 16:15 | 9 | 17.25 |
| 3 | 10:00 | 12:00 | 2.0 | 19.25 |
| 3 | 12:45 | 17:00 | 6.25 | 23.5 |
| 4 | 8:00 | 12:00 | 4.0 | 27.5 |
| 4 | 13:00 | 18:30 | 9.5 | 33.0 |
And here we go, click the Test button...
...cool, the same user interactions we saw with our ActionFixture, but the table is much more compact. So how the heck does this work now? This is done in our new Fixture:
public class TimeRecordingUIColumnFixture extends ColumnFixture {
public String day;
public String from;
public String to;
private TimeRecordingUI timeRecordingUI;
public TimeRecordingUIColumnFixture() {
timeRecordingUI = new TimeRecordingUI();
timeRecordingUI.pack();
timeRecordingUI.setVisible(true);
timeRecordingUI.toFront();
}
public void execute() {
sleep();
timeRecordingUI.dayTextField.setText(day);
sleep();
timeRecordingUI.fromTextField.setText(from);
sleep();
timeRecordingUI.toTextField.setText(to);
sleep();
timeRecordingUI.addButton.doClick();
sleep();
}
public double workingTime() {
TableModel model = timeRecordingUI.timeRecordTable.getModel();
Object value = model.getValueAt(model.getRowCount() - 1,
TimeRecordingTableModel.WORKING_TIME_COLUMN);
return ((Double) value).doubleValue();
}
public double sum() {
TableModel model = timeRecordingUI.timeRecordTable.getModel();
Object value = model.getValueAt(model.getRowCount() - 1,
TimeRecordingTableModel.SUM_COLUMN);
return ((Double) value).doubleValue();
}
protected void sleep() {
int slowMotionMs = 200;
try {
Thread.sleep(slowMotionMs);
} catch (InterruptedException excep) {
excep.printStackTrace();
}
}
}
Hmm, not that complicated. The ColumnFixture assigns values to the members day, from and to, and execute() performs the UI interactions. workingTime() and sum() are pretty much the same as in our ActionFixture.
Alright, but what about that sleep() method? This is that slow motion thing again, which we already had in our ActionFixture.
< GUI Test | Testing Datasets >
< Gui Test with other
Fixtures | Links and Books >
That's where the RowFixture comes into play. In a row RowFixture table one table row specifies an expected dataset. The RowFixture has the following properties:
Let's check it out by pressing Test:
At first we enter the datasets into the system using our TimeRecordingColumnFixture.
After that, we test the expected datasets with a RowFixture. As you can see the order of the datasets is of no intereset:
That's the way it looks when datasets are missing or surplus:
So how is this implemented in detail? Let's have a look at our good old
Our RowFixture code is found in the classes
So what's happening here? The constructor does not create any new instance of TimeRecording but retrieves it from the TimeRecordingRepository; and that's the instance we created and set up with data using our ColumnFixture.
The main methods of RowFixture are
Method query() returns the datasets under test, and getTargetClass() the type of a single dataset. Wait a minute, I would have expected getTargetClass() to return TimeRecord as the type of a dataset. What the heck do we need this TimeRecordRowFixtureAdapter for? Well, the RowFixture tries to map the row header names to fields, resp. methods of the dataset class. Our TimeRecord does contain a field named day, but none for from and to since they are encapsulated by a TimeFrame. In order to make these values accessible to the RowFixture we just wrap the TimeRecord with an adapter. This also provides a way to use names more convenient to the testing audience:
< Gui Test with other Fixtures | Links and Books >
The RowFixture
The ColumnFixture is good at testing functions; GUIs may be tested with ActionFixture. But sometimes the result of an operation is not a single piece of data but a dataset that must be tested against the expectations.That's where the RowFixture comes into play. In a row RowFixture table one table row specifies an expected dataset. The RowFixture has the following properties:
- The order of the datasets is not taken into account. The RowFixture identifies a dataset by equality of the row cells, starting from left to right.
- Missing and surplus datasets will be rendered as failures.
Let's check it out by pressing Test:
At first we enter the datasets into the system using our TimeRecordingColumnFixture.
| de.fitsample.timerecording.TimeRecordingColumnFixture | ||
| day | from | to |
| 1 | 10:00 | 12:00 |
| 1 | 12:45 | 17:00 |
| 2 | 08:00 | 12:00 |
| 2 | 13:00 | 18:30 |
| 3 | 08:00 | 12:00 |
| 3 | 13:00 | 19:30 |
After that, we test the expected datasets with a RowFixture. As you can see the order of the datasets is of no intereset:
| de.fitsample.timerecording.TimeRecordingRowFixture | ||
| day? | from? | to? |
| 3 | 08:00 | 12:00 |
| 1 | 10:00 | 12:00 |
| 2 | 08:00 | 12:00 |
| 1 | 12:45 | 17:00 |
| 3 | 13:00 | 19:30 |
| 2 | 13:00 | 18:30 |
That's the way it looks when datasets are missing or surplus:
| de.fitsample.timerecording.TimeRecordingRowFixture | ||
| day? | from? | to? |
| 1 | 12:45 | 17:00 |
| 2 | 08:00 | 12:00 |
| 2 | 13:00 | 18:30 |
| 3 | 13:00 | 19:30 |
| 3 | 08:00 | 12:00 |
| 4 missing | 08:30 | 16:00 |
| 1 surplus | 10:00 | 12:00 |
So how is this implemented in detail? Let's have a look at our good old
- http://files/FitTutorial/FitSample/src/de/fitsample/timerecording/TimeRecordingColumnFixture.java
- http://files/FitTutorial/FitSample/src/de/fitsample/timerecording/TimeRecordingRepository.java
public TimeRecordingColumnFixture() {
timeRecording = new TimeRecording();
TimeRecordingRepository.getInstance().store(timeRecording);
}
Our RowFixture code is found in the classes
- http://files/FitTutorial/FitSample/src/de/fitsample/timerecording/TimeRecordingRowFixture.java
- http://files/FitTutorial/FitSample/src/de/fitsample/timerecording/TimeRecordRowFixtureAdapter.java
public class TimeRecordingRowFixture extends RowFixture {
private TimeRecording timeRecording;
public TimeRecordingRowFixture() {
timeRecording = TimeRecordingRepository.getInstance().retrieve();
}
public Object[] query() throws Exception {
Object[] result = new Object[timeRecording.size()];
for (int i = 0; i < result.length; i++) {
result[i] = new TimeRecordRowFixtureAdapter(timeRecording
.getRecord(i));
}
return result;
}
public Class getTargetClass() {
return TimeRecordRowFixtureAdapter.class;
}
public Object parse(String text, Class type) throws Exception {
if (type == Time.class) {
return Time.valueOf(text);
}
return super.parse(text, type);
}
}
So what's happening here? The constructor does not create any new instance of TimeRecording but retrieves it from the TimeRecordingRepository; and that's the instance we created and set up with data using our ColumnFixture.
The main methods of RowFixture are
- Object[] query()
- Class getTargetClass()
Method query() returns the datasets under test, and getTargetClass() the type of a single dataset. Wait a minute, I would have expected getTargetClass() to return TimeRecord as the type of a dataset. What the heck do we need this TimeRecordRowFixtureAdapter for? Well, the RowFixture tries to map the row header names to fields, resp. methods of the dataset class. Our TimeRecord does contain a field named day, but none for from and to since they are encapsulated by a TimeFrame. In order to make these values accessible to the RowFixture we just wrap the TimeRecord with an adapter. This also provides a way to use names more convenient to the testing audience:
public class TimeRecordRowFixtureAdapter {
private TimeRecord timeRecord;
public TimeRecordRowFixtureAdapter(TimeRecord timeRecord) {
this.timeRecord = timeRecord;
}
public int day() {
return timeRecord.getDay();
}
public Time from() {
return timeRecord.getTimeFrame().getStartTime();
}
public Time to() {
return timeRecord.getTimeFrame().getEndTime();
}
< Gui Test with other Fixtures | Links and Books >
<
Under the Hood | New
Requirements >
Here comes FitNesse into play. FitNesse is a Wiki with its own Web-Server and a built-in FITRunner. You are currently using a FitNesse Wiki. The syntax is quite easy, at least a lot easier than HTML (click the Edit button on the left and have a look). And you can run a test very easily, just by clicking the Test button. Not all customers like Wikis (even if it is becoming popular thanks to projects like WikiPedia). But FitNesse also provides the possibility to import tables from Word and Excel using Copy/Paste.
But enough big words here, let's face the real thing and run a test from our example. Click the Test button. Now!
< Under the Hood | New Requirements >
Testing with FitNesse
Works quite well with FIT, what do I need FitNesse for? One thing is that not everybody likes editing a HTML document; and saving a Word document as HTML does not always provide a usable result. And if the customer likes to run FIT tests itself, she has to cope with the FITRunner command line tool.Here comes FitNesse into play. FitNesse is a Wiki with its own Web-Server and a built-in FITRunner. You are currently using a FitNesse Wiki. The syntax is quite easy, at least a lot easier than HTML (click the Edit button on the left and have a look). And you can run a test very easily, just by clicking the Test button. Not all customers like Wikis (even if it is becoming popular thanks to projects like WikiPedia). But FitNesse also provides the possibility to import tables from Word and Excel using Copy/Paste.
But enough big words here, let's face the real thing and run a test from our example. Click the Test button. Now!
Subtraction of mandatory breaks
This test checks if mandatory breaks are subtracted.- More than 6 hours of work: 1/2 hour
- More than 9 hours of work: 3/4 hour
| de.fitsample.timerecording.TimeRecordingColumnFixture | |||
| day | from | to | working time? |
| 1 | 6:00 | 8:00 | 2.0 |
| 2 | 6:00 | 9:00 | 3.0 |
| 3 | 6:00 | 10:00 | 4.0 |
| 4 | 6:00 | 11:00 | 5.0 |
| 5 | 6:00 | 12:00 | 6.0 |
| 6 | 6:00 | 12:01 | 6.0 |
| 7 | 6:00 | 12:30 | 6.0 |
| 8 | 6:00 | 12:31 | 6.02 |
| 9 | 6:00 | 13:00 | 6.5 |
| 10 | 6:00 | 14:00 | 7.5 |
| 11 | 6:00 | 15:00 | 8.5 |
| 12 | 6:00 | 15:30 | 9.0 |
| 13 | 6:00 | 15:31 | 9.0 |
| 14 | 6:00 | 15:45 | 9.0 |
| 15 | 6:00 | 15:46 | 9.02 |
| 16 | 6:00 | 16:00 | 9.25 |
| 17 | 6:00 | 16:45 | 10.0 |
| 18 | 6:00 | 16:46 | 10.02 |
| 19 | 6:00 | 17:00 | 10.25 |
| 20 | 6:00 | 18:00 | 11.25 |
Taken break shorter than mandatory
If the employee has taken a break shorter than mandatory, then the difference is subtracted from the working time.| de.fitsample.timerecording.TimeRecordingColumnFixture | |||||||
| day | from | to | recorded time? | recorded break? | mandatory break? | working time? | sum? |
| 1 | 10:00 | 12:00 | 2.0 | 0 | 0 | 2 | 2 |
| 1 | 12:15 | 17:00 | 6.75 | 0.25 | 0.5 | 6.5 | 6.5 |
| 2 | 08:00 | 12:00 | 4.0 | 0 | 0 | 4 | 10.5 |
| 2 | 12:15 | 18:30 | 10.25 | 0.25 | 0.75 | 9.75 | 16.25 |
| 3 | 08:00 | 12:00 | 4.0 | 0 | 0 | 4 | 20.25 |
| 3 | 12:15 | 19:30 | 11.25 | 0.25 | 0.75 | 10.75 | 27.00 |
Taken break equals mandatory
If the employee has taken a break that equals the mandatory break, nothing is subtracted.| de.fitsample.timerecording.TimeRecordingColumnFixture | |||||||
| day | from | to | recorded time? | recorded break? | mandatory break? | working time? | sum? |
| 1 | 10:00 | 12:00 | 2.0 | 0.0 | 0.0 | 2 | 2 |
| 1 | 12:30 | 17:00 | 6.5 | 0.5 | 0.5 | 6.5 | 6.5 |
| 2 | 08:00 | 12:00 | 4.0 | 0.0 | 0.0 | 4 | 10.5 |
| 2 | 12:45 | 18:30 | 9.75 | 0.75 | 0.75 | 9.75 | 16.25 |
| 3 | 08:00 | 12:00 | 4.0 | 0.0 | 0.0 | 4 | 20.25 |
| 3 | 12:45 | 19:30 | 10.75 | 0.75 | 0.75 | 10.75 | 27.00 |
Taken break longer than mandatory
If the break taken by the employee is longer than mandatory, nothing is subtracted.| de.fitsample.timerecording.TimeRecordingColumnFixture | |||||||
| day | from | to | recorded time? | recorded break? | mandatory break? | working time? | sum? |
| 1 | 10:00 | 12:00 | 2.0 | 0.0 | 0.0 | 2 | 2 |
| 1 | 12:45 | 17:00 | 6.25 | 0.75 | 0.25 | 6.25 | 6.25 |
| 2 | 08:00 | 12:00 | 4.0 | 0.0 | 0.0 | 4 | 10.25 |
| 2 | 13:00 | 18:30 | 9.5 | 1.0 | 0.5 | 9.5 | 15.75 |
| 3 | 08:00 | 12:00 | 4.0 | 0.0 | 0.0 | 4 | 19.75 |
| 3 | 13:00 | 19:30 | 10.5 | 1.0 | 0.75 | 10.5 | 26.25 |
< Under the Hood | New Requirements >
