Monday, December 20, 2004

PyFIT Tutorial Part 2

In the conclusion of part 1 of the PyFIT tutorial, I said I will experiment with RowFixture tables. It turned out that they're really easy to use. I'll show here a simple example that will extend the FitNesse acceptance test suite for the Blog Management application.

In the FitNesse tests I put together in part 1 I used only ColumnFixture tables. One example is BloggerFixtures.GetEntryTitleContent, which takes entry_index as an argument and returns the title and the content of that entry. To use a database analogy, that particular ColumnFixture behaves like a SQL query of the form:

SELECT title, content FROM entries WHERE entry_index=N

In the FitNesse tests I wrote, I used GetEntryTitleContent in a table like this:

!|BloggerFixtures.GetEntryTitleContent|
|entry_index|title?|content?|
|1|Entry #3 Title|Entry #3 Content|
|2|Entry #2 Title|Entry #2 Content|
|3|Entry #1 Title|Entry #1 Content|

We can look at each row in this table as being the result of running the SQL query above, with entry_index set to 1, 2, and 3.

However, we really are interested in verifying ALL entries in the blog at the same time. It would be useful to have a fixture similar to a SQL query such as:

SELECT * FROM entries

This is exactly what a RowFixture achieves: it can be thought of as returning all the "rows" in the "database". In our case, we need to return all the entries in the blog, specifically their index, title, and content, so we need something like:

SELECT entry_index, title, content FROM entries

To achieve this, we need to write a clas derived from RowFixture. Our class needs to define
two methods:
  • getTargetClass
  • query
In PyFIT, the query method needs to return a list of objects, each object being an instance of the class returned by getTargetClass. Each object is a representation of a row in the RowFixture table. In our example, the target class needs to expose 3 class variables: entry_index, title and content.

Here is the ListAllEntries fixture I wrote:

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

class BlogEntry:
_typeDict={
"entry_index": "Int",
"title": "String",
"content": "String",
}
entry_index = 0
title = ""
content = ""

class ListAllEntries(RowFixture):

def getTargetClass(self):
return BlogEntry

def query(self):
blogger = Blogger.get_blog()
num_entries = blogger.get_num_entries()
entry_list = []
for i in range(num_entries):
blog_entry = BlogEntry()
blog_entry.entry_index = i+1
blog_entry.title = blogger.get_nth_entry_title(i+1)
blog_entry.content = blogger.get_nth_entry_content_strip_html(i+1)
entry_list.append(blog_entry)
return entry_list

The class derived from RowFixture is ListAllEntries, which defines the 2 methods I mentioned:
  • getTargetClass() returns the class BlogEntry
  • query() builds a list of BlogEntry objects by retrieving entry_index, title and content for each entry in the blog and assigning their values to the corresponding variables of BlogEntry
The BlogEntry class will be inspected by the FitNesse framework, so it needs to define the _typeDict TypeAdapter with the 3 variables listed in the FitNesse table that uses the ListAllEntries fixture. Speaking of this table, here is an example of how I used it:

!|BloggerFixtures.ListAllEntries|
|entry_index|title|content|
|1|Entry #4 Title|Entry #4 Content|
|2|Entry #3 Title|Entry #3 Content|
|3|Entry #2 Title|Entry #2 Content|
|4|Entry #1 Title|Entry #1 Content|

The above fragment is from a new test page I created in the BlogMgmtSuite acceptance test suite. You can see the full contents of this page here: PostDelete4EntriesRowFixture.

Using a RowFixture has another big advantage over using a ColumnFixture: a RowFixture will retrieve all the entries in the table and will let you know if you have any extra rows or if you are missing any rows. Here is an example:

Assume the blog has 4 entries. Assume we use a ColumnFixture like this:

!|BloggerFixtures.GetEntryTitleContent|
|entry_index|title?|content?|
|1|Entry #4 Title|Entry #4 Content|
|2|Entry #3 Title|Entry #3 Content|
|3|Entry #2 Title|Entry #2 Content|

In this case, all the rows will be colored green and the test will pass, since the ColumnFixture will retrieve one by one the three entries we specified.

Now assume we use a RowFixture like this:

!|BloggerFixtures.ListAllEntries|
|entry_index|title|content|
|1|Entry #4 Title|Entry #4 Content|
|2|Entry #3 Title|Entry #3 Content|
|3|Entry #2 Title|Entry #2 Content|

In this case, the test will fail, since the RowFixture will retrieve the fourth entry too and will let us know that we missed it in our table. FitNesse will show a fourth row colored in red, with the following text:

|4 surplus|Entry #1 Title|Entry #1 Content|

Conclusions
  • a RowFixture is the ideal vehicle to use when you need to verify that all the data you entered into the system under test so far is there -- no more and no less
  • using RowFixtures proved to be easy, once I visualized their similarity to SQL "select * from" queries.

2 comments:

Dominic_Cooper_2002 said...

wow! very good.

Anonymous said...

Hand Carved Dog Gifts Hi, found a great gift site for you, all hand carved and great quality.