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 http://localhost: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):
- One Title Row - Names the fixture to execute, optionally includes constructor parameters
- One Heading Row - Names of columns, which map to either setter methods or method calls (if they end in ?)
- 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:
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:
- setName(…)
- setEpisode(…)
- setChannel(…)
- setDate(…)
- setStartTime(…)
- setMinutes(…)
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.
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):
- Browse to http://localhost:8080.
- Edit this page (click the Edit button - or, if not available, go to the following URL: http://localhost:8080/FrontPage?edit), 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: http://localhost:8080/FrontPage.DecisionTableExample?edit&nonExistent=true
- 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 “.”. 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.
There are still a few things you need to do to make the page use this class:
- 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:
Here’s the updated page put all together(again, update the directory in the !path statement accordingly):
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:
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:
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::
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
Create new Class: TimeSlot.java
Notice, this class is in a different package (com.om.example.dvr.domain).
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
To fix this, we can introduce a new class and perform some basic re-factoring:
Schedule.java
ConflictingProgramException.java
Updated: AddProgramsToSchedule.java
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.
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:
This introduces another column, lastId?. The implementation, which is below, simply returns the last id stored in the method created(). The definition is simply: (
- (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:
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
Update: Schedule.java
Update: AddProgramsToSchedule.java
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:
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:
Just add this to the bottom of your page. You’ll have to create a new fixture. Here is that 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:
Example: Added to AddProgramsToSchedule fixture
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::
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).
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:
Now that we have a single Schedule and access to it, we can simply update the constructor in RemoveProgramById to call the code:
Of course, this requires we add a new method to Schedule:
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:
Then you’ll need to update your RemoveProgramByIdFixture as follows:
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 (http://localhost:8080/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:
- row names the fixture.
- row names columns.
- 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
- column with just text in its name will map to a public method called setX, where X is the name of the column.
- 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 *, 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 Next Tutorial–>
Comments