Programming Tools: FitNesse

by Reg. Charney

Seldom does a tool or product span the worlds of end users, QA and development. It is rarer still for such a tool to do so simply and elegantly. FitNesse, however, does all of these things superbly.

FitNesse is a collaborative tool based on a wiki that allows users, developers and testers to define, modify and delete tests. These tests are more like usage scenarios. You define what you expect to put into the system and what you expect to get out. The FitNesse framework runs your test and displays the results on a wiki page. To make it all work, FitNesse comes with its own simple server, so no other software is required.

Basics

FitNesse defines tests using one or more tables. Each row defines a set of inputs and an expected outputs. Each table has a title and a column heading describing input parameter names and the names of the expected output results. To indicate an output column, the column name ends with a question mark (?).

As an example, assume I am writing a routine to capitalize words, defined as strings of letters separated by non-alphabetic characters. A set of tests for this is shown in Table 1.

Table 1. Capitalize Words

InputOutput ?
a short sentence.A Short Sentence.
partial Capitalization testPartial Capitalization Test.
Already Capitalized!Already Capitalized!
woops!woops!

One of the truly brilliant parts of this tool is it is based on a wiki. This offers two important advantages. First, executable code can be associated with widgets, such as buttons, on a page. Second, anyone can edit pages simply. On each FitNesse wiki page there can be a test button. Clicking it processes all the embedded tables in the wiki page and produces one or more tables. Processing Table 1, for example, produces Table 2.

Programming Tools: FitNesse

Table 2. The Processed Table 1 Results

To create this table, you could edit the page and insert the text for Table 1 as:


|eg.CapitalizeWords|
|input|output?|
|a short sentence.|A Short Sentence.|
|partial Capitalization test.|Partial Capitalization Test.|
|Already Capitalized!|Already Capitalized!|
|woops!|woops!|

You also could create this table using any spreadsheet program that generates CVS files. You then can import them into Fitnesse.

Although I showed only one test set here, you also can organize pages and tables into suites of tests.

Under the Covers

Fitnesse is a GUI built on top of the FitFramework, a Java application. The way things work depends on conventions and processing done by the Fit framework. When the "Test" button is pressed, Fit scans the source for tables. The first row in the table is expected to be a class name. For example, Fit would expect to find a CapitalizeWords class with public member variables corresponding to the input columns--input, in this case--and a public member function--output--corresponding to the output column. The Java code looks like this:


import fit.ColumnFixture;

public class CapitalizeWords extends ColumnFixture {
  public String input;
  public string output() {
    return Capitalize(Input);
  }
}

Currently, FitNesse supports Java and C#. Testing code like this is called a fixture. Having executed the fixture, Fit compares the actual result with the expected result. Depending on the comparison, the result is color coded: green means a match, red means a mismatch and yellow means an exception was thrown. For a mismatch, FitNesse shows expected and actual results. For an exception, FitNesse shows a stack trace.

Also, as part of the Fit framework, introspection is used to associate the requirements of the output functions with the input parameters. This greatly simplifies writing fixtures and makes developing test code trivial. Most test code is simple; usually, it is simply a thin wrapper around actual code.

Now consider a slightly more complex example. We are developing a mailing list, and we want to keep it as clean as possible--no invalid addresses and no duplicates. First, we must define the appropriate "classpath" to the fixture code in any FitNesse page that contains test code. It must appear before any tables:


!path /home/reg/MailingList/

FitNesse displays it as:


classpath: /home/reg/MailingList/

Next, define your table by editing a wiki page and writing:


|eg.MailingListAdd|
|address|validAddr?|duplicateAddr?|finalAddr?|
|123 Main Street, #3a|Yes|No|123 MAIN ST APT 3A|
|18 Ash Lane|Yes|No|18 ASH LN|
|43 Ave|No|No||
|18 Ash ln|Yes|Yes|18 ASH LN|
|43 First Ave|Yes|No|43 FIRST AVE|

It should end up looking like what is shown here:

Programming Tools: FitNesse

Table 3. Mailing List

The Java code to implement this fixture is:


import fit.ColumnFixture;

public class MailingListAdd extends ColumnFixture {
  public String address;
  public boolean validAddr() {
    return AddressIsValid(address);
  }
  public boolean duplicateAddr() {
    return FoundAddr(address);
  }
  public String finalAddr() {
    return StandardizeAddr(address);
  }
  public boolean addAddr() {
    // Business logic dictates that we
    // only add an address if the address
    // is valid and not a duplicate.
    // While we could check it here, it
    // really belongs in the code that
    // this test code invokes.
    return AddAddr(address);
  }
}

Notice that the addAddr member function is as thin as possible, because you don't want to put business logic in testing code. It should exist in the actual code. If you have a series of tables in a page, they are evaluated in order, from top to bottom.

To handle the complexity often found in real-world systems, FitNesse uses a hierarchy of wikis, called sub-wikis. Organizing test suites in this way helps with organization and avoids unexpected name collisions. To assist with this type of organization, FitNesse allows inheritance of parent variables, page headings and footings, and other values, including classpaths. It also supports symbolic links, which simplifies complex hierarchies.

FitNesse is a combination of Java and JavaScript. I used the July 31, 2005, version for this article.

Usage Scenario

If you are like me, you build up a limited set of test cases for any code that you produce. One of the main reasons the code fails is the invalid assumptions I made. To overcome this, others need to supply test cases. Thus, I see the process as:

  1. I write the code.

  2. I test fixtures to exercise the code.

  3. I write the initial acceptance tests using FitNesse.

  4. All other stakeholders define additional test cases by editing the FitNesse wiki page and using the simple FitNesse syntax.

  5. Any problems are reported back to me with all the information I need to reproduce the problem.

  6. Repeat steps 4-5 with occasional code fixes.

You can consider the testing code I write to be unit testing. The testing code the other stakeholders write is acceptance testing code. As they say, unit testing ensures that the code is built right and acceptance testing ensures that the right code it written.

Conclusion

Why am I really excited by this technology? It allows all stakeholders to define acceptance tests using the same simple language. In other words, they can talk to one another. Even non-technical people can produce tables of what is expected for what input. And even techies can read those tables and use them to validate their work. Generally speaking, FitNesse is a great concept and a terrific open-source solution to an often difficult problem.

One last note: I often have had problems installing products. In the case of FitNesse, it was almost flawless. Only a minor hiccup occurred, which I solved instantly. It had defaulted to using port 80 for its Web server. That port is the same as the one used by Apache and other Web servers. The error message was clear, and a convenient command-line parameter made the fix trivial. Also, the User's Guide is well-written. Reading it is a must for those who have not had previous experience with using wikis.

Load Disqus comments