Designing your Specification Language using FitNesse August 21, 2010
One of the great benefits of FitNesse is that it allows creation of human readable tests, hiding the implementation details, so that the test experts can focus on designing tests rather than struggle with code. This does not mean that any code you write and call it a fixture would automatically follow this philosophy. You actually have to design your fixture methods/ functions so that the users of this solution can get this benefit from FitNesse.
Here I will explain some of the design considerations to keep in mind while designing your fixture methods. If I had to choose my favorite fixture that comes out of the box in FitNesse/ FitLibrary, it would be DoFixture. This is because in my experience in QA, this fixture best represents the steps in a test case. This is the reason why the examples here are using DoFixture in java, but these concepts can be applied to any language or any Fixture you are using.
How does DoFixture work
The easiest way to use the capabilities of a DoFixture is to create a public class that extends DoFixture. All you have to do to expose the functionality of this class is to create public methods in this class. To see an example look at the Getting Started With FitNesse post.
Methods with no arguments:
Let us assume you have a fixture method
public void doSomething() the way you could call it in your test case (wiki usage) by doing:
and it would work … however that is not the FitNesse recommended approach. The fitnesse way would be:
Notice that the upper-case S is replaced with a space and lower-case s. This is what helps the methods look more like sentences. You can have as many words as you like to describe the action. An example in browser testing could be
clearAllCookies(). Here the wiki usage would be:
|clear all cookies|
Methods with Arguments
DoFixtures support methods with arguments differently from other fixtures. If you have a method
clickLink(String linkName) in your fixture, you can use it like this:
Here the first cell is the name of the method and the second cell is the argument. By the way, you can also use this method like this:
The reason why this works too is that DoFixtures actually concatenate the text from the odd numbered cells to form the method name and uses all the even numbered cells as arguments to the method. This design seemed bazaar the first time I saw it but it actually gives you a lot of flexibility in describing your actions as human readable sentences. To me, the second usage of clickLink is a lot more readable.
Ok, let us now look the above information for an example with multiple arguments … here is a usage of a method
logInWithUsernameAndPassword(String username, String password):
|log in with username|rahul|and password|foobar|
There are several special actions in DoFixtures, the most commonly used are
reject. Special actions are used for handling the return type of your fixture methods. Lets say you have a method in your web fixture
getPageTitle() with the return type of string. If you want to see what the method returns you can use the
show special action like this:
|show|get page title|
Here the first cell describes the special actions and the following cells correspond to the method and the arguments. If you want to verify the title with an expected value of “Home Page” you can use the
check special actions like this:
|check|get page title|Home Page|
In this case the first cell is the special action and the last cell is the argument to the special action. The cells in between correspond to method name and arguments of the fixture. So what happens when you run this? If the method does end up returning the expected value of “Home Page”, the like turns green like this:
If the method instead returns “My Home Page”, then the line turns red like this:
This works if the return type is any of the primitive types like int or any object that has a toString() method that returns a value that can be used for validations. Lets assume your
logInWithUsernameAndPassword(String username, String password) method returns boolean –
true if the credentials were correct and
false if it wasn’t (logging in failed). You can validate it using:
|check|log in with username|rahul|and password|foobar|true|
but booleans are a little special. DoFixture reflects on the method return type and if its boolean, it automatically turns green if it returns true and red if its false, i.e., if you just used
|log in with username|rahul|and password|foobar|
as before, it would turn green without explicitly using
Get and Set methods
Again to make the methods more readable, the get and set portion of the getter and setter methods can be eliminated. So, our example of
getPageTitle() can also be called using:
Designing your DoFixture methods
With this understanding of DoFixture let us talk about the best practices around designing your fixture methods:
- We are all used to creating methods like
login(String username, String password). We don’t usually have really long method name, like
logInWithUsernameAndPassword. In FitNesse fixtures however the later is the recommended so that the tests look human readable.
- When using 3rd party tools I have often seen implementations where the method names from the tools are named the same in the fixtures. For instance in Selenium 1.x there is a method
public void click(String locator)
If you created the same method in your web fixture, your usage would end up being something like this:
do you think this is readable, or would
look more readable? To make this transformation all you have to do is your fixture code will have to generate the XPath string, that’s all.
- The fixtures often end up as a facadé to one or more 3rd party tools. The challenge with this is to not have a balance between too many methods doing almost the same thing (and so a higher code maintenance cost, confusion between which methods to use etc) and too few do-it-all methods that make reading the tests unreadable.
- One of the drawbacks of having long method names, others might not quickly get what the usage of the method should really be. Documentation in the code, like providing the usage in the java docs, will resolve this problem.