Saturday, January 08, 2005

PyFIT Tutorial Part 3

I will expand here on Part 1 and Part 2 of the PyFIT tutorial and I'll show how to use SetUp pages in FitNesse. I'll also clean up the code in some of the fixtures I wrote for the Blog Management application that I used as the SUT (Software Under Test).

First, some code cleanup. I had a lot of hard-coded paths in my fixtures. All fixtures used to start with:

from fit.ColumnFixture import ColumnFixture
import sys
blogger_path = "C:\\eclipse\\workspace\\blogger"
sys.path.append(blogger_path)
import Blogger

This is clearly sub-optimal and requires a lot of copy-and-paste among modules. To simplify it, I turned the blogger directory into a package by simply adding to that directory an empty file called __init__.py. I also moved Blogger.py (the main functionality module) and its unit test module, testBlogger.py, to a subdirectory of blogger called src. I made that subdirectory a package too by adding to it another empty __init__.py file. Now each fixture module can do:

from fit.ColumnFixture import ColumnFixture
from blogger.src.Blogger import get_blog

There's one caveat here: the parent directory of the blogger directory -- in my case C:\eclipse\workspace -- needs to be somewhere on the Python module search path. In FitNesse it's easy to solve this issue by adding C:\eclipse\workspace to the classpath via this variable definition which will go on the main suite page:

!path C:\eclipse\workspace

When invoking the Python interpreter from a command line, one way of making sure that C:\eclipse\workspace is in the module search path is to add it to the PYTHONPATH environment variable, for example in a .bash_profile file. For our example though, the fixture modules are always invoked within the FitNesse/PyFIT framework, so we don't need to worry about PYTHONPATH.

Now to the SetUp page functionality. If you create this page as a sub-page of a suite, then every test page in that suite will automatically have SetUp prepended to it by FitNesse. I created a SetUp page (its URL is http://localhost/FitNesse.BlogMgmtSuite.SetUp) with the following content:

!|BloggerFixtures.Setup|
|setup?|
|true|

I also created the corresponding Setup.py fixture, with the following code:

from fit.ColumnFixture import ColumnFixture
from blogger.src.Blogger import get_blog

blog_manager = None

class Setup(ColumnFixture):
_typeDict={
"setup": "Boolean"
}

def setup(self):
global blog_manager
blog_manager = get_blog()
return (blog_manager != None)

def get_blog_manager():
global blog_manager
return blog_manager

The setup method invokes the get_blog function from the Blogger module in order to retrieve the common Blogger instance used by all the fixtures in our test suite. I did this because I wanted to encapsulate the common object creation in one fixture class (Setup), then have all the other fixture classes cal the get_blog_manager function from the Setup module. Another benefit is that only the Setup module needs to know about the physical location of the Blogger module, via the line:

from blogger.src.Blogger import get_blog

All other fixture classes need only do:

from Setup import get_blog_manager

since they are in the same directory with Setup.py.

A SetUp page can also be used to pass values to the application via methods in the Setup class. Assume we need to pass the path to a configuration file. One way of accomplishing this is to define a FitNesse variable in the SetUp page, like this:

!define CONFIG_PATH {C:\config}

The FitNesse syntax for referencing a variable is ${variable}, so we can pass it as an argument to our Setup fixture like this:

!|BloggerFixtures.Setup|${CONFIG_PATH}|
|setup?|
|true|

When the page is rendered by FitNesse, ${CONFIG_PATH} is automatically replaced with its value, so on the rendered page we'll see:

variable defined: CONFIG_PATH=C:\config

BloggerFixtures.Setup C:\config
setup?
true

In the Setup fixture class, we can get the value of the argument like this:

config_path = self.getArgs()[0]

One other thing we could do in the SetUp page is to include the DeleteAllEntries fixture, so that we can be sure that each test page will start with a clean slate in terms of the blog entries.

I moved the old code from parts 1 and 2 of the tutorial here. You can see the new code here.