Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

The Case for “PhraseFixture” -- A Better DoFixture() for Use with FIT and FitNesse Acceptance Testing Framework

5.00/5 (2 votes)
13 Aug 2011CPOL3 min read 17.7K  
The powerful DoFixture() in FitLibrary simulates English like specification. The PhraseFixture proposal takes this to the next level.

The Case for “PhraseFixture”

Background

This articles assumes knowledge of FIT (Framework for Integrated Test) and the FitLibrary.

Overview

For those who’ve used DoFixture from FitLibrary, you are probably familiar with the ChatStart example (FitLibraryUserGuide.DoFixture.WritingFixtures). Shown here:

  • The fixture of the first table is a DoFixture, so the created flow fixture object handles the rest of the tables:

ChatStart
  • The second table contains an action, which is mapped into the method connectUser() of the (initial) flow fixture object, as shown below:
connect user sarah
  • The third table contains two actions which are also applied to the flow fixture object.
user sarah creates fitRoom
user sarah enters fit Room

What DoFixture attempts to do is to make the table data be more readable. For instance, in the above table, the last row is attempting to be as close as possible to what one would say in English: “User Sarah enters fit room”. There are two problems here:

  1. The phrase is split across multiple columns of a table, which seems odd. When most people see a table, they typically think of a grid, where the rows are “records”, each column represents some kind of a field. For a TableFixture in FIT, a table is a good method. But DoFixture pushes the table metaphor over its limit. Things get worse when people create tables where each row has different number of columns, based on the number of “parameters”.
  2. The other problem with this is that, even ignoring the fact that the phrase is in a table, reading “User Sarah enters fit room” sounds weird. Shouldn’t “fit” and “room” be swapped? Well, it would sound better, but it wouldn’t work with DoFixture. This is because DoFixture relies upon commands and parameters alternating from one column to the next. In the above five columns containing the words “sarah” and “fit” constitute the parameters, and the words “user”, “enters” and “room” are concatenated to correspond to the fixture method userEntersRoom();. See the class below:
C#
public class ChatStart : DoFixture
{
    private ChatRoom chat = new ChatRoom();

    public ChatStart()
    {
        setSystemUnderTest(chat);
    }
    
    public bool ConnectUser(string userName)
    {
        return chat.connectUser(userName);
    }

    public bool UserCreatesRoom(string userName, string roomName)
    {
        return chat.userCreatesRoom(userName, roomName);
    }
    
    public bool UserEntersRoom(string userName, string roomName)
    {
        return chat.userEntersRoom(userName, roomName);
    }
}

Proposal: PhraseFixture()

It’s possible to have a fixture be smart enough to locate the right method even if the words in the phrase did not follow the column alternating sequence. Furthermore, if spaces were used as the delimiter, then the rows would look a lot better. Example:

Chat Start
Connect user sarah
User sarah creates room fit
User sarah enters room fit

Which certainly looks better than:

ChatStart
connect user sarah
user sarah creates fit room
user sarah enters fit room

… and is probably easier for the business analyst to type into a table.

The way this can be made to work is simply by trying the combinations of words until a matching method is found, and the unused words become the parameters. So for the fourth row, the words “User”, “Enters” and “Room” get matched to the method UserCreatesRoom(), and the unused words, “Sarah” and “fit” end as the parameters to the method.

Downsides

Every solution has its set of problems. Although this reads better than DoFixture, since spaces are used as word delimiters, string parameters that contain spaces will have to be dealt with another way. The proposal is to require to place strings in quotes. So, for instance, if the user name above was “sarah connor” instead of “sarah”, then the author would have to have entered:

User “sarah connor” enters room fit

which isn’t so bad.

Algorithm

The algorithm is quite simple, and perhaps can be improved. But what it does is like this. As a simpler example, let’s say that the words in the phrase are “Connect user sarah”. It will look for the following method names, and the order ensures that the longest possible name is looked for first:

  • ConnectUserSarah()
  • UserSarah()
  • ConnectSarah()
  • Sarah()
  • ConnectUser()
  • User()
  • Connect()

(The one in bold is obviously the one that will be found since that method exists in the fixture. And since the word “Sarah” is not used in that particular match, that word becomes the parameter to the ConnectUser() function.)

I’m sure it’s possible to come up with a smarter algorithm, but the one included here serves the purpose of demonstrating what is possible.

Supporting Code

C#
const int cBitsInInt = 32;

static uint ReverseBits(uint x)
{
    uint h = 0;
    uint i = 0;
    
    for (h = i = 0; i < cBitsInInt; i++)
    {
        h = (h << 1) + (x & 1); 
        x >>= 1; 
    }
    
    return (uint)h;
}

static uint ReverseAndShift(uint x, int length)
{
    return ReverseBits(x) >> (cBitsInInt - length);
}

static bool FindMethod(MethodInfo[] methods, string[] columns, 
	out MethodInfo foundMethod, out List<string> parameters)
{
    uint bits = (uint)(1 << columns.Length);
    for (uint i = bits - 1; i >= 0 && i <= cBitsInInt; i--)
    {
        string s = "";
        parameters = new List<string>();
        
        uint x = ReverseAndShift(i, columns.Length);
        for (int j = 0; j < columns.Length; j++)
        {
            if ((1 & x) > 0)
                s += columns[j].ToLowerInvariant();
            else
                parameters.Add(columns[j]);
                
            x >>= 1;
        }
        
        foreach (MethodInfo method in methods)
        {
            if (method.Name.ToLowerInvariant() == s)
            {
                foundMethod = method;
                return true;
            }
        }
    }
    
    foundMethod = null;
    parameters = null;
    return false;
}

Kenneth Kasajian (ken@kasajian.com)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)