FitNesse.Tutorials.1

toc <--Back -or- Next Tutorial--> =Introduction= This tutorial assumes some basic [|FitNesse] knowledge. If you need help installing or running [|FitNesse], please go here first. In this tutorial, you will use a Decision table to send data into a system and verify results returned. You will:
 * Create Decision tables in FitNesse
 * Make the tables execute by writing Fixtures
 * Get the tests to pass by updating both the Fixture code and by creating production code
 * Learn how to use variables in tables
 * Learn the difference between setter columns and method call columns
 * Call a constructor in a decision table
 * Discover the relationship between tables, fixtures and instances of fixture classes
 * Learn how to coordinate between different fixtures
 * Learn how to review output from your fixture code

This tutorial is primarily about getting you over the hurtle of the mechanics of getting tests to execute using [|FitNesse]. Even so, you will see some basic design considerations play out as well.

Note, this tutorial assumes you are running [|FitNesse] on localhost at port 8080 []. If you are not sure how to do that, try this tutorial. =Background= [|FitNesse.Slim Decision Tables] are a common way to get test data into a System Under Test. A Decision table has three parts (only the first of which is actually required):
 * 1) One Title Row - Names the fixture to execute, optionally includes constructor parameters
 * 2) One Heading Row - Names of columns, which map to either setter methods or method calls (if they end in ?)
 * 3) Zero or more Data Rows - rows of data used to either provide data into a system or data used to compare to values returned from the fixture

 Here is an example [|FitNesse] decision table: code code
 * Add Programs To Schedule                                                    |
 * name     |episode                      |channel|date     |start time|minutes|
 * House M.D.|House Makes Wilson Mad      |7      |5/12/2008|7:00      |60     |
 * Doctor Who|The One where He Saves the UK|12    |5/17/2008|8:00      |60     |

The first row names the fixture. In this case, [|FitNesse] will look for a class called AddProgramsToSchedule. The second row lists the column names. [|FitNesse] will look for the following methods in AddProgramsToSchedule: These methods can all take Strings or some, where there's a conversion available, other types as well. For example, **"setChannel"** could take an int. It is also possible to define your own translations, however this tutorial does not cover that feature.
 * setName(...)
 * setEpisode(...)
 * setChannel(...)
 * setDate(...)
 * setStartTime(...)
 * setMinutes(...)

Finally, there are two data rows. Given the name of the fixture, this table's goal is to apparently add two programs to the schedule.

Creating this table
Here are some preliminary steps to get this table created (there will be more later, this table is the skeleton of a test): code >DecisionTableExample code
 * Browse to [].
 * Edit this page (click the **Edit** button - or, if not available, go to the following URL: []), add the following before the Release date line at the bottom (the location is arbitrary):
 * Save your changes (click the **Save** button)
 * Click on the linked question mark, which will take you to: []
 * Copy the table contents into the page replacing the !contents ... that is already there.
 * Save the page (click **Save**).
 * Edit the page's properties (click **Properties**).
 * Set the page type to a test page (depending on the version of fitness, this is either a check-box or a radio button). Note, if a page name starts with or ends with the word test, the page type will be set to test by default.
 * Save the property change (click the **Save Properties** button).

Now you can execute the page. Click on the **Test** button. The tests will fail dues to a missing fixutre. [|FitNesse] will color the first row yellow and add the message "//Could not find fixture: AddProgramsToSchedule.//". Now you must create a Fixture class and add it to the test page.

Creating the Fixture
If you are planning on using Eclipse and working in Java, then you can get a repository from github: [|fitnesse-tutorials]. Review the instructions here.

Creating a fixture involves:
 * Creating class.
 * Making it executable:
 * On the JVM, you need a .class file
 * On the CLR, you need a DLL with the compiled class embedded
 * Updating the classpath on your page (or hierarchically above it) to point to your executable code
 * Using an import table to name the package/namespace of the class (or fully qualifying the fixture name in the table)

For full details on these steps, you can review the material here if you're planning on working in Java or here if you're planning on working in C#.

Here is one such fixture (in Java) that will get this test to "pass". Since there are no assertions, this really isn't a very good test yet, but it does make it easier to get it all green. code format="java5" package com.om.example.dvr.fixtures;

public class AddProgramsToSchedule { public void setName(String name) { }

public void setEpisode(String name) { }

public void setChannel(int channel) { }

public void setDate(String date) { }

public void setStartTime(String startTime) { }

public void setMinutes(int minutes) { } } code

There are still a few things you need to do to make the page use this class: code !define TEST_SYSTEM {slim} code code !path /Users/schuchert/src/fitnesse-tutorials/DVR/bin code code code
 * Inform [|FitNesse] you want to use Slim versus fit:
 * Inform [|FitNesse] where to look for your class files (update this directory as appropriate):
 * Inform [|FitNesse] the package/namespace in which to look:
 * import|
 * com.om.example.dvr.fixtures|

Here's the updated page put all together(again, update the directory in the !path statement accordingly): code !define TEST_SYSTEM {slim}

!path /Users/schuchert/src/fitnesse-tutorials/DVR/bin


 * import|
 * com.om.example.dvr.fixtures|

code
 * Add Programs To Schedule                                                    |
 * name     |episode                      |channel|date     |start time|minutes|
 * House M.D.|House Makes Wilson Mad      |7      |5/12/2008|7:00      |60     |
 * Doctor Who|The One where He Saves the UK|12    |5/17/2008|8:00      |60     |

code !path fitnesse.jar code
 * //Note//**: You might need to add the following line as well (e.g., if you built from source):

Run the test and verify that the page passes successfully.

While you are at it, you have your original test page from the first tutorial. You can verify it still passes as well.

=Add Assertions= Right now, this table does not assert any results, which means the underlying fixture can do the same, which is not much. Let's extend this just a bit to have the table actually perform validation: code code
 * Add Programs To Schedule                                                             |
 * name     |episode                      |channel|date     |start time|minutes|created?|
 * House M.D.|House Makes Wilson Mad      |7      |5/12/2008|7:00      |60     |true    |
 * Doctor Who|The One where He Saves the UK|12    |5/17/2008|8:00      |60     |true    |

Try running this page and FitNesse will complain that it cannot find the **created[0]** method. The name is followed by the number of expected parameters, which is 0 in our case. Here is just such a method you can add to your "AddProgramsToSchedule" fixture: code format="java5" public boolean created { return true; } code

Update your table and add the missing method. Verify that the test still passes. You'll notice there are three successful assertions.

What is this doing?
Adding a column with a ? at the end of its name requires that the fixture have a method with a matching name (remove spaces, use camel casing) with some return value. [|FitNesse] will execute that method and compare its return value to the value in the cell, marking it green or red for matching/not matching. If you happen to have a cell with no value, the return value will be displayed in the cell with a gray coloring.

=Make the Assertion have some Value= There's nothing in the flow of this table that would cause a problem. However, what if we want to make sure adding a program on top of another is not possible? We can do that by adding one more row to the bottom of the table:: code code
 * Conflicts |Should not be added         |7      |5/12/2008|7:00      |30     |false   |

This demonstrates a conflict because the third program is on the same channel, date, time as the first.

This is a non-typical use of the Decision table, but it certainly is legitimate. Assuming the slot is already occupied (even partially), this item should not be added to the schedule.

Run it, you should have one failed assertion. Your code will need some way to know that one slot is already used. Here's one way to accomplish that:

Update AddProgramsToSchedule.java
code format="java5" package com.om.example.dvr.fixtures;

import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.LinkedList; import java.util.List;

import com.om.example.domain.TimeSlot;

public class AddProgramsToSchedule { private static SimpleDateFormat dateFormat = new SimpleDateFormat("M/d/yyyy|h:mm"); private List scheduledTimeSlots = new LinkedList; private int channel; private String date; private String startTime; private int minutes;

public void setName(String name) { }

public void setEpisode(String name) { }

public void setChannel(int channel) { this.channel = channel; }

public void setDate(String date) { this.date = date; }

public void setStartTime(String startTime) { this.startTime = startTime; }

public void setMinutes(int minutes) { this.minutes = minutes; }

public boolean created { TimeSlot timeSlot = new TimeSlot(channel, buildStartDateTime, minutes);

if (conflictsWithOtherTimeSlots(timeSlot)) return false;

scheduledTimeSlots.add(timeSlot); return true; }

private boolean conflictsWithOtherTimeSlots(TimeSlot timeSlot) { for (TimeSlot current : scheduledTimeSlots) if (current.conflictsWith(timeSlot)) return true;

return false; }

private Date buildStartDateTime { try { String dateTime = String.format("%s|%s", date, startTime); return dateFormat.parse(dateTime); } catch (ParseException e) { throw new RuntimeException("Unable to parse date/time", e); }  } } code

Create new Class: TimeSlot.java
Notice, this class is in a different package (com.om.example.dvr.domain).

code format="java5" package com.om.example.dvr.domain;

import java.util.Date;

public class TimeSlot {

public final int channel; public final Date startDateTime; public final int durationInMinutes;

public TimeSlot(int channel, Date startDateTime, int durationInMinutes) { this.channel = channel; this.startDateTime = startDateTime; this.durationInMinutes = durationInMinutes; }

public boolean conflictsWith(TimeSlot other) { if (channel == other.channel && startDateTime.equals(other.startDateTime)) return true; return false; } } code

Make these changes to your code and see that your tests now pass. Now your fixture is recording the time slots in use. The implementation of "TimeSlot.conflictsWith" may seem inadequate, but it is complete for what we are testing, so in fact is it fine.

Another issue is that the "AddProgramsToSchedule" class is starting to get somewhat big. Fixtures are enabling technology and as such should primarily handle data translation and then delegate to production code.

Along those lines, "buildStartDateTime" also exhibits feature envy. The "Schedule" is currently just a "List", but it might warrant its own class. While this tutorial's focus is [|FitNesse], this fixture contains business logic. You do not want any business logic in your fixture code, so that's the next thing to fix.

To fix this, we can introduce a new class and perform some basic re-factoring: code format="java5" package com.om.example.dvr.domain;
 * Schedule.java**

import java.util.Date; import java.util.LinkedList; import java.util.List;

public class Schedule { private List scheduledTimeSlots = new LinkedList;

public void addProgram(String programName, String episodeName, int channel,        Date startDateTime, int lengthInMinutes) {

TimeSlot timeSlot = new TimeSlot(channel, startDateTime, lengthInMinutes);

if (conflictsWithOtherTimeSlots(timeSlot)) throw new ConflictingProgramException;

scheduledTimeSlots.add(timeSlot); }

private boolean conflictsWithOtherTimeSlots(TimeSlot timeSlot) { for (TimeSlot current : scheduledTimeSlots) if (current.conflictsWith(timeSlot)) return true;

return false; } } code

code format="java5" package com.om.example.dvr.domain;
 * ConflictingProgramException.java**

public class ConflictingProgramException extends RuntimeException { private static final long serialVersionUID = 1L; } code

code format="java5" package com.om.example.dvr.fixtures;
 * Updated: AddProgramsToSchedule.java**

import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date;

import com.om.example.dvr.domain.ConflictingProgramException; import com.om.example.dvr.domain.Schedule;

public class AddProgramsToSchedule { static SimpleDateFormat dateFormat = new SimpleDateFormat("M/d/yyyy|h:mm"); private Schedule schedule = new Schedule; private int channel; private String date; private String startTime; private int minutes; private String programName; private String episodeName;

public void setName(String name) { this.programName = name; }

public void setEpisode(String name) { this.episodeName = name; }

public void setChannel(int channel) { this.channel = channel; }

public void setDate(String date) { this.date = date; }

public void setStartTime(String startTime) { this.startTime = startTime; }

public void setMinutes(int minutes) { this.minutes = minutes; }

public boolean created { try { schedule.addProgram(programName, episodeName, channel, buildStartDateTime,              minutes); } catch (ConflictingProgramException e) { return false; }     return true; }

private Date buildStartDateTime { try { String dateTime = String.format("%s|%s", date, startTime); return dateFormat.parse(dateTime); } catch (ParseException e) { throw new RuntimeException("Unable to parse date/time", e); }  } } code

This split makes more sense:
 * The determination of whether there is or is not a conflict is now in a class that is part of the production code.
 * The handling of parsing input strings is in the fixture.

This really was just an Extract class refactoring or wrapping a collection. Wrapping collections is generally a good idea. For more details, see the sidebar, Wrapping Collections.

Before moving on, make sure your test passes. Assuming it does, congratulations on a successful refactoring.

include page="sidebar_start" Wrapping Collections When dealing with a language-provided collection, you should wrap it by default and only not wrap it if it makes sense. This might seem controversial, but in my experience the extra overhead of wrapping the collection provides a place for functionality that is otherwise heavily duplicated. For example: include page="sidebar_end" =Deleting Something By Key= We should be able to add a program, remove it and then add another at the same time slot. Here's just such a test and it uses something you might have noticed in the first tutorial: code code
 * Only adding something if it does not conflict in some way with existing members in the collection.
 * Doing some kind of work over the entire collection.
 * Responding in a domain-specific way to empty/full collections.
 * Add Programs To Schedule                                                                     |
 * name     |episode                      |channel|date     |start time|minutes|created?|lastId?|
 * House M.D.|House Makes Wilson Mad      |7      |5/12/2008|7:00      |60     |true    |$p=    |
 * Doctor Who|The One where He Saves the UK|12    |5/17/2008|8:00      |60     |true    |       |
 * Conflicts |Should not be added         |7      |5/12/2008|7:00      |30     |false   |       |

This introduces another column, **lastId?**. The implementation, which is below, simply returns the last id stored in the method **created**. The definition is simply:, e.g., the id's above are:
 * (House M.D.:7)
 * (Doctor Who:12)

Update your table with the new table above and try running this page and FitNesse will complain that it cannot find the **lastId[0]** method. The name is followed by the number of expected parameters, which is 0 in our case. Here is just such a method you can add to your "AddProgramsToSchedule" fixture: code format="java5" public String lastId { return lastId; }

code Add the missing method. Verify that the test still passes. You'll notice there are three unsuccessful assertions for "lastId".

As for the third id, you'll see that in a minute. To get this to run, you'll need to make several changes:

Add: Program.java
code format="java5" package com.om.example.dvr.domain;

public class Program {

public final String programName; public final String episodeName; public final TimeSlot timeSlot;

public Program(String programName, String episodeName, TimeSlot timeSlot) { this.programName = programName; this.episodeName = episodeName; this.timeSlot = timeSlot; }

public String getId { return String.format("(%s:%d)", programName, timeSlot.channel); } } code

Update: Schedule.java
code format="java5" package com.om.example.dvr.domain;

import java.util.Date; import java.util.LinkedList; import java.util.List;

public class Schedule { private List scheduledPrograms = new LinkedList;

public Program addProgram(String programName, String episodeName, int channel,        Date startDateTime, int lengthInMinutes) {

TimeSlot timeSlot = new TimeSlot(channel, startDateTime, lengthInMinutes);

if (conflictsWithOtherTimeSlots(timeSlot)) throw new ConflictingProgramException;

Program program = new Program(programName, episodeName, timeSlot); scheduledPrograms.add(program); return program; }

private boolean conflictsWithOtherTimeSlots(TimeSlot timeSlot) { for (Program current : scheduledPrograms) if (current.timeSlot.conflictsWith(timeSlot)) return true;

return false; } } code

Update: AddProgramsToSchedule.java
code format="java5" package com.om.example.DVR.fixture;

import java.util.Calendar; import java.util.Date;

import com.om.example.dvr.domain.ConflictingProgramException; import com.om.example.dvr.domain.Program; import com.om.example.dvr.domain.Schedule;

public class AddProgramsToSchedule { // snip private String lastId;

// snip

public boolean created { try { Program p = schedule.addProgram(programName, episodeName, channel,              buildStartDateTime, minutes); lastId = p.getId; } catch (ConflictingProgramException e) { return false; }     return true; }

public String lastId { return lastId; }  // snip } code

Once you've made these updates, execute the table. You should notice three values in the "lastId?" column:
 * $p<-[(House M.D.:7)] - the variable p was assigned the value (House M.D.:7)
 * The second and third cells contain (Doctor Who:12) in gray.

In all cases:
 * the value returned is displayed,
 * the cells are empty, so [|FitNesse] just displays the results.

In the first case, there is a variable assignment, which [|FitNesse] dutifully assigned.

This variable is available for the rest of the page. However, before we get to that we do have a problem. The lastId? is set upon a successful program add, but it is not reset if the program is not added. Here is a quick fix to improve that: code format="java5" public boolean created { try { Program p = schedule.addProgram(programName, episodeName, channel,              buildStartDateTime, minutes); lastId = p.getId; } catch (ConflictingProgramException e) { lastId = "n/a"; return false; }     return true; } code
 * AddProgramsToScheule.created**

Make the update and then you'll notice the third data row of the lastId? column is now n/a (in gray).

Finally, Delete by Key
Time to add another table and fixture: code
 * Remove Program By Id|$p|

code
 * Add Programs To Schedule                                                |
 * name  |episode            |channel|date     |start time|minutes|created?|
 * Ok now |No longer conflicts|7     |5/12/2008|7:00      |30     |true    |

Just add this to the bottom of your page. You'll have to create a new fixture. Here is that code: code format="java5" package com.om.example.dvr.fixtures;

public class RemoveProgramById { public RemoveProgramById(String id) { } } code

This fixture does not do anything yet, but even so there are several things worthy of note:
 * You can provide parameters after the name of a fixture.
 * A fixture's constructor can take parameters.
 * $p is passed in as the first parameter to the constructor.
 * The parameters are matched by order, which is probably what you are used to.

The second decision table using the AddProgramsToSchedule fixture on the page should verify that we can add a program to that time slot that was previously occupied.

What to do:
 * Update the page to include these two additional tables.
 * Create the RemoveProgramById fixture
 * Run you tests.

When you run your tests, do you notice a problem? The tests pass! Maybe you expected the second attempt to add would fail, but it appears to work. This illustrates something [|FitNesse] does; each table causes a new instance of the fixture to be created, even on the same page. How can you tell this? If you want to verify it, you could simply add a print statement to the constructor and view the output. I've already done that. Here's the print statement: code format="java5" private static int numberCreated = 0;
 * Example: Added to AddProgramsToSchedule fixture**

public AddProgramsToSchedule { System.out.printf("Creating ProgramsToSchedule #%d\n", ++numberCreated); } code

Adding this and then executing the tests, [|FitNesse] will display a yellow triangle with the label "Output Captured". Clicking on that triangle, you'll see the output captured during test execution:: code Standard Output:

Creating ProgramsToSchedule #1 Creating ProgramsToSchedule #2 code

So what is the problem? The fixture holds the schedule. Each fixture has its own schedule. We need the schedule to be a single instance. You have several options:
 * Simply make Schedule static.
 * Make the Schedule a singleton (I mention this, but I'm not a fan of the Singleton pattern).
 * Use some kind of IoC container like Spring and look up the schedule there.

Ultimately, how you should do it depends on your system. If your system will eventually need objects like this configured, wired and passed around, then it might make sense to introduce Spring or maybe even a hand-rolled IoC container (a factory of some kind). For our purposes, simply making the schedule static in AddProgramsToSchedule will work effectively. So do that and then see the test fail (note, I've removed the constructor and static variable **numberCreated** in my version to get rid of output making its way into my test execution).

include page="sidebar_start" Tests Should Not Produce Output Your acceptance tests (and unit tests) should not produce output. Why? Because you've written them to have assertions. Those assertions are the only thing that define success or failure. If you find the need to produce output, are you also going to verify that output? If so, then turn the verification of the output into an assertion. If not, then you're adding noise to the test execution.

This might be OK while you are working on your machine but don't check this cruft in. What I've seen happen, repeatedly, is people add output to verify their work (that's fine in the short term, maybe, but it represents a lack of trust in either your own abilities or the test system), and then other people notice the output and then the output grows. Soon, it becomes the norm.

On one project, I removed the unnecessary output (random print statements that people were too lazy to remove) and it decreased test execution time by 30%. Imagine that, 30%, or 4.5 minutes (the tests were too slow). On average, people were running tests at least 2 times a day (I'd run them 6 - 10 times a day). Even so, at one point we had 20 developers. 20 * 2 * 4.5 = 3 hours lost per day for the team. 30 hours per week lost.

To quote Jerry Weinberg: > Nothing + Nothing + Nothing eventually equals something.

Leaving output in tests, unit or acceptance tests, is lazy. You can do better. include page="sidebar_end"

Now that the test is failing, we need a way to get access to the schedule between fixtures. For now, adding a getSchedule method on the AddProgramsToSchedule fixture is adequate: code format="java5" public class AddProgramsToSchedule { private static Schedule schedule = new Schedule;

public static Schedule getSchedule { return schedule; }

// snip } code

Now that we have a single Schedule and access to it, we can simply update the constructor in RemoveProgramById to call the code: code format="java5" package com.om.example.dvr.fixtures;

public class RemoveProgramById { public RemoveProgramById(String id) { AddProgramsToSchedule.getSchedule.removeProgramById(id); } } code

Of course, this requires we add a new method to Schedule: code format="java5" import java.util.Iterator;

public void removeProgramById(String programIdToRemove) { for (Iterator iter = scheduledPrograms.iterator; iter.hasNext;) if (iter.next.getId.equals(programIdToRemove)) { iter.remove; break; }  } code

Run your tests and you should see all tests green.

Not Doing the Work in the Constructor
If for some reason, you do not like to do the actual work done in the constructor, you can optionally write the table as follows: code code
 * Remove Program By Id|
 * id                 |
 * $p                 |

Then you'll need to update your RemoveProgramByIdFixture as follows: code format="java5" package com.om.example.dvr.fixtures;

public class RemoveProgramById { private String id;

public RemoveProgramById { }

public RemoveProgramById(String id) { this.id = id; execute; }

public void setId(String id) { this.id = id; }

public void execute { AddProgramsToSchedule.getSchedule.removeProgramById(id); } } code Note that this Fixture, as written, supports both styles. The real reason I wanted to include this last example was to demonstrate how you can cause a row of a decision table to be executed without include a column with a ? in its name. You add a method called **execute**. [|FitNesse] will call that method, if it exists, after calling the last setter (the columns without ? in their name).

=Conclusion and Summary= Congratulations, you've completed this tutorial.

This tutorial emphasizes Decision tables. There is still more to you can do with decision tables, but this covers most of what you'll need to know to effectively use decision tables. If you go to your fitness installation and go to FitNesse.SliM.DecisionTable ([]), you can read the [|FitNesse]-provided documentation.

However, you've learned several things in this tutorial:
 * How to tell [|FitNesse] to use Slim instead of fit (its default)
 * How to import packages (works for namespaces as well)
 * How to create a decision table
 * You've learned that a decision table has three parts:
 * First row names the fixture.
 * Second row names columns.
 * Third and subsequent rows provide data.
 * How to make the tables execute by writing Fixtures
 * How to get the tests to pass by updating both the Fixture code and by creating production code
 * How to use variables in Decision tables (both writing and reading)
 * Learned the difference between setter columns and method call columns
 * A column with just text in its name will map to a public method called setX, where X is the name of the column.
 * A column with a ? at the end of its name is a method call, which causes [|FitNesse] to invoke a method and use the returned value for possible verification.
 * Learned how to call a constructor in a decision table
 * Discovered the relationship between tables, fixtures and instances of fixture classes
 * Learn how to coordinate between different fixtures
 * Learn how to review output from your fixture code
 * Learned that you can add an execute method, which [|FitNesse] will call for you after calling the setters.
 * Learned that a fixture is just a plane old class, it does not inherit from anything.
 * Learned that the methods to be called must be public.
 * Learned that fixtures should not have any production logic in them.
 * Learned that if you put output in your code, [|FitNesse] will capture it and you'll be able to see it
 * Note, while you can debug this, your fixtures should be so simple that this is seldom necessary. If you find yourself doing this often, consider simplifying your fixtures. If that's not possible, write unit tests for your complex fixture code.
 * Learned that each of the data rows in your fixture is executed in order, top-to-bottom.

After working with decision tables, the next tutorial which makes sense is this one on query tables. <--back -or- Next Tutorial-->