BDD with Scenario tables in Fitnesse Slim

I think writing functional tests in a given-when-then style manner is a great idea. Tests tend to be clearer and more precise when written this way. There are quite a few tools too choose from. Cucumber by Aslak Hellesøy seems to have a lot of momentum these days and is an acceptance testing framework definitely worth considering.

However, the customer I work with has been using Fitnesse for a number of years now, which means people are familiar with this framework. So going for the new Fitnesse Slim framework seemed like a step in the right direction.

I quickly had some tests up and running, and with Fitnesse running inside Eclipse my cycles were really short. Having learned the basics I felt an urge to learn more and therefore decided to watch some of Uncle Bob´s tutorials on the Fitnesse site. After watching the Scenario table BDD tutorial I decided to try it out on my current project.

What kind of tests are we talking about?

I would characterize the tests I’m going to talk about here as Component tests. So they are not integrations tests, they rather test that the component we’re writing is doing what we expect it to do. As such, they are an excellent addition to our unit tests which tests our classes in isolation.

Who are the tests for?

The primary purpose of a component test is to aid developers to make the right code. But should the developer create these kind of tests for themselves? I think not. These kind of tests need to be carved out by resources with in-depth knowledge of the domain. Ultimately, I think that a joint effort between customer (possibly represented by a proxy), testers and developers gives the best result. A workshop before starting working on a feature is a good way to get this started. In addition, if we can express the tests in a natural language, we are increasing our chances of success. Given-when-then is a perfect fit for bridging the communication gap between business and developers.

Given-when-then

Given-when-then is a three step sentence where given is describing the pre-conditions, when is describing the action of the user and then the result of the action. The structure and the natural language makes it a good fit for bridging the gap between customers and developers. Cucumber has a great introduction to this construct. This page also links to another great post by Uncle Bob where he describes given-when-then as a finite state machine.

BDD with Fitnesse Slim

given a purchase in initialized state
and payment succeeds
and delivery succeeds
when the client completes the purchase
then a purchase receipt should be shown

I would argue that this is quite easy to read and it certainly communicates the intent of the user quite well. It is in a natural language and makes it therefore quite easy for customers to understand. In the following we will dive closer into how we can build tests in this manner using Fitnesse. We will first see how we can use Slim tables to do this. We then move on to see how we can improve things using the open source framework GivWenZen. But first, a very short description of the system under test.

The system under test

The component under test is that of a simple purchase system. From the web, customers can refill prepaid mobiles and pay it using a credit card (by redirection to third party solution). So conceptually we have a purchase that consists of a delivery of a product to a customer, and payment of the product. The purchase goes through a series of states, initialized and completed to name but a few.

BDD with Scenario and script tables

The key element for writing given-when-then style tests in Slim is Scenario tables. Scenario tables enable you to hide the technical details of the tests (like calling fixtures etc), allowing us to create tests revealing the true intent of the users.

The following shows an example of a Scenario table

scenario_table

The first word is scenario, the rest of the first line is the signature where every second cell is a parameter, we’ll see how to invoke the scenario shortly. The second line is the body. This particular scenario will call method initializeRefillWithProduct and pass two arguments (telephoneNumber and product) to the method. Notice that the parmeters must be prefixed with the @ sign.

A script table contains a series of actions and checks, it is similar to DoFixtures in Fit. This makes it a good fit for setting up our 3 step given-when-then scenarios, especially if we combine script tables with scenario tables, remember we can call scenarios from script tables. This way, we can hide the details of calling the fixtures from the script table and instead place it in our scenario table.

Order of precedence when executing script tables

When the Slim engine executes a line of a script table it will first look to see if any scenarios match the script definition. If none is found it will look for methods in the imported fixture(s) with that name. Note that scenarios must always be defined before the script tables that uses them

Problems with this approarch

After a while we had quite a few given-when-then tests. One problem we quickly identified was that organization of the scenarios was quite messy. As a consequence tests were hard to change, an intolerable situation. I was about to turn my attention to Cucumber when Uncle Bob pointed me in the direction of GivWenZen. I had a quick peak at this about a month ago but not yet had time to try it. I decided to give it a try…

BDD with GivWenZen

Setting up GivWenZen to my already existing fitnesse configuration was easy, I simply made sure that all the libraries present in the zip download was on the classpath. It took a little while however before I realized that my Step classes needed to be in a package bdd.steps in order for the framework to pick it up correctly (according to the documentation one can override this location but I have not yet looked into this matter).

I soon discovered that using the framework was surprisingly easy. The framework is built around Step classes which contains Step methods. It uses regular expression matching to determine which step method to execute. To cut the story short: Instead of hiding complexity in scenario tables we hide the complexity in Java classes using annotations.
Let´s look at the example presented earlier in this post:

Our fitnesse page look like this:

The SetUp page at the top contains the necessary import and script table that GivWenZen needs in order to function correctly. Notice that the given-then-when keywords come first in a row, followed by a step definition. These are the step definitions we need to set up in the Step class. Here is the step class for our example (Note: Some imports are left out for brevity):

package bdd.steps;

import org.givwenzen.annotations.*;
import org.mockito.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;

@DomainSteps
public class CompletePurchase extends RefillFixture {
    
   @Mock
    private WebServiceOperations billingAdapter;
    @Mock 
    private RefillDAO refillDAO;

    private DeliveryConnector refillDeliveryConnector;
    private RefillEndpoint refillEndpoint;
    private String telephoneNumber;

    public CompletePurchase() {
        this.telephoneNumber = telephoneNumber;
        MockitoAnnotations.initMocks(this);
    }

    
    @DomainStep ( "the client completes the purchase")
    public void completePurchase() throws Exception{
        CompletePurchaseRequest request = null;
        try{
           request = 
               RequestMother.makeCompletePurchaseRequest(purchaseId);
        } catch (PurchaseException pe){
           errorCode = pe.getErrorCode();
        }
    }

    @DomainStep ("a purchase is in (.*) state")
    public void setUpPurchase(String state){
        PurchaseTestConfigurator configurator = 
           getPurchaseTestConfigurator();
    
        purchaseId = configurator.generatePurchaseId(telephoneNumber);
        Purchase purchase = PurchaseMother.createNewPurchase(purchaseId, state);
        configurator.storePurchase(purchase);
    }

    @DomainStep  ("delivery succeeds" )
    public void mockRefillDelivery() {
        when(refillDAO.performRefill(any(Delivery.class), any(String.class), 
              any(PaymentType.class), any(String.class))).
            thenReturn(RefillResponsesMother.makeOKRefill();
    }

    @DomainStep ("payment succeeds" )
    public void mockPayment() {
        when(paymentConnector.complete(any(InitializePaymentTransaction.class), any(Product.class))).
        thenReturn(PaymentTransactionMother.makeSuccessFullVisaPaymentTransaction());
    }

   
    @DomainStep ("payment fails")
    public void mockPaymentFails(){
        when(paymentConnector.complete(any(PaymentTransaction.class), any(Product.class))).
            thenReturn(PaymentTransactionMother.makeFailedPaymentTransaction());
    }

    @DomainStep ("a purchase receipt should be shown" )
    public boolean checkResponse() {
        return validResponse(); 
    }

    @DomainStep ("purchase should fail with error (.*)")
    public boolean checkErrorType(String error){
        return error.equals(errorMessage());
    }

    @DomainStep("what the error is")
    public String showError(){
        return errorMessage();
    }
}

(Note: The class above is not complete, some methods were left out to make it easier to read)

The class is annotated with @DomainSteps annotation while the methods that are to be invoked when the tests run is annotated with @DomainStep. Lets walk through the test:

In the given clause we set up the conditions for the test, in our case the following sentences defines the pre-conditions:

given a customer has ordered a product
and the payment succeeds
and the delivery succeeds

Notice how the sentences in italic maps to steps in our step class. The first inserts a row in our in-memory database. The two next are a bit special. They defines behavior we expect from the system test in this specific case. The reason is simply that the payment and delivery system are external systems where we don´t control the data. Relying on data in these system can make our tests fragile.

when approving the purchase

In the when clause we do the actual test. In this case we call the method completePurchase() on the endpoint class which is the entry point to the application.

Finally we check that the result is as expected:

then a purchase receipt for should be displayed

If we run the tests our result looks like this:

The “then row” is marked with a green color signaling that the test was successful.
That´s it. One thing to note though: Since the domain steps are determined using regular expression matching you cannot have to identical steps. Something to watch out for.

The way forward

Having tried GivWenZen for a few days I´m really starting to like the library. It makes my tests compact and easy to use. The complexity is shifted from the fitnesse page to the java class, allowing us to express tests in a natural fluent language. It is a great starting point for communication and collaboration between customers and developers.
Uncle Bob has started working in given-when-then table for Fitnesse Slim, the release date is yet not known but I´m sure it will be worth looking at once it is released.

About these ads

4 responses to “BDD with Scenario tables in Fitnesse Slim

  1. Pingback: Algumas observações sobre BDD | marcuscavalcanti.net

  2. hi Ketil, I have been playing with combining GivWenZen with scenario table and parameterized scenarios. I think I like the scenario table better than a parameterized but I still want to create some more examples. Take a look at the latest fitnesse example in the GivWenZen src. Thanks for using and writing about the tool. If you have any feedback or ideas please let me know.

  3. Pingback: 2010 in review | Walk the walk

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s