Component-Based Fixtures for FIT.
I enjoy using FIT. It’s an extremely powerful testing framework and it provides a lot of capability “out of the box.” However, there are times when I wish it was structured a bit differently.
Last week I was doing some work in the Python version of FIT and I noticed that I needed something that behaved like ColumnFixture. In fact, it needed to behave a lot like ColumnFixture but it had to be implemented very differently.
I started to think through the possibilities. Subclass ColumnFixture? It was an option. But it would’ve required a lot of overriding and substantial code duplication. I also thought about modifying ColumnFixture directly, but the resulting code wouldn’t have been very clear. In the end, I decided to build my fixture as a subclass of Fixture, not ColumnFixture. I had to provide many of the same features that ColumnFixture provides, so there was some code duplication, but on balance, I think it was the right choice.
The fact is, I’ve done this sort of thing a couple of times, and it works, but it makes me wonder whether there is another way of approaching fixtures, a way which lets us compose them from smaller pieces.
Over the next few blogs, I’ll walk you through an experiment I’m performing. I’m attempting to come up with a way of building fixtures from smaller pieces. The idea is to make pieces that are a little more elemental than ColumnFixture, ActionFixture, RowFixture, and DoFixture. If everything works out, we’ll be able to knit these pieces together to make those larger fixtures. We’ll also be able to create new pieces that allow us to create new fixture types a bit more easily. But, as I write this, I don’t know what the end of the story will be. It could fail miserably, but it should be a fun journey.
If you're game, read along.
The First Step - Getting out of Fixture
So my goal is to create componentized fixtures in FIT. The first thing that I feel an urge to do is to move outside the Fixture hierarchy.
Fixture is an interesting class. It defines a way of traversing over a set of parses (pieces of a test document) and, interestingly, all of these traveral methods are public. You can just override them when you want to add behavior.
Here’s an example in PyFit. This is the doRows method on the Fixture class:
class Fixture(object):
...
def doRows(self, rows):
while rows:
more = rows.more
self.doRow(rows)
rows = more
...
The ColumnFixture subclass of Fixture overrides doRows so that it can treat the first row of a table specially. It can take the elements of the first row as column headers and bind them to some special objects that it uses later.
class ColumnFixture(Fixture):
...
def doRows(self, rows):
exceptionIfNone(rows, "ColumnHeadsMissing")
self.bind(rows.parts)
super(ColumnFixture, self).doRows(rows.more)
...
This pattern recurs in all of the subclasses of Fixture. We find a traversal method, override it to add some behavior, and then we call the superclass’s version of the method to continue with the traversal.
This works, but it is a shame that we have to mix the new behavior we’re adding (in this case, bind()) along with the traversal code when we override. What would it be like if we created a special kind of fixture delegates out to a listener?
from fit.Fixture import Fixture
class ShimFixture(Fixture):
def __init__(self):
self.shim = None
def setShim(self, shim):
self.shim = shim
def doTable(self, table):
self.shim.onTableStart(table)
Fixture.doTable(self, table)
self.shim.onTableEnd(table)
def doRow(self, row):
self.shim.onRowStart(row)
Fixture.doRow(self, row)
self.shim.onRowEnd(row)
def doCell(self, cell, columnNumber):
self.shim.onCellStart(cell, columnNumber)
Fixture.doCell(self, cell, columnNumber)
self.shim.onCellEnd(cell, columnNumber)
I’m going to call the listener a shim (there is a already a FixtureListener class in the Fitnesse version of FIT and I don’t want to play with it while I’m experimenting).
Now that I have this ShimFixture, I can introduce a Shim class which will receive events before and after we traverse each element of a test document:
class Shim(object):
def onTableStart(self, table):
pass
def onTableEnd(self, table):
pass
def onRowStart(self, row):
pass
def onRowEnd(self, row):
pass
def onCellStart(self, cell, columnNumber):
pass
def onCellEnd(self, cell, columnNumber):
pass
This might not seem like much right now, but it will give us some leverage as we move on. If we want to do something simple, like change the color of the first row in a table, it would be easy. All we’d have to do is create a subclass of Shim and override one method: onRowStart. In fact, we could create a collection of shims which handle different parts of a table.
I’ll get to that in the next blog entry. In the meantime, let me know what you think.
!commentForm
You've piqued my curiosity. I look forward to seeing what possibilities this opens up. I'm puzzled as to who would call the ShimFixture.[?]setShim() method and when they would call it. It would have to be in between the time FIT creates the Fixture and executes it... hmmm. Are you changing the FIT framework?.
- I think the possibilities will be pretty neat. No changes to FIT, and I don't think I'll need to. I've worked a bit in advance of what I've reported here, so I'll forshadow a little.. setShim is going to end up being called from a subclass. ShimFixture is going to be a base. -- mf (off the write the next installment)
- I think the possibilities will be pretty neat. No changes to FIT, and I don't think I'll need to. I've worked a bit in advance of what I've reported here, so I'll forshadow a little.. setShim is going to end up being called from a subclass. ShimFixture is going to be a base. -- mf (off the write the next installment)
I like the presentation - this time I understood it! [grin]. It looks like an interesting approach, and it's nice to see someone adding themselves to the (very small) number of people that are looking at the basic structure of FIT.
Looking forward to seeing your next installment!
Looking forward to seeing your next installment!
I was just thinking about something like this today. The discussion several weeks back on the Fitnesse list had me wanting a way to implement verbose column names, and in working through it I was thinking how great a plugable model for Fitnesse would be.
I like where it's going so far!
I like where it's going so far!
How would you skip the header row for a ColumnFixture[?] (in Shim.onTableStart())?
You would need to modify 'table' or ... to skip over this element during the iteration.
But would it be rendered in the resulting page?!?
Otherwise a good idea (to think about refactoring into parts/components).
I had a similar one when I ported the RowEntryFixture[?] form Java to Python.
The whole concept of dynamically adding rows, cells, etc should be moved into one or more utility classes. It is seldom used, but when not dynamic Parse manipulation, you could easily reuse it.
NOTE (Design Critique):
The ShimFixture.[?]shim attribute is a class attribute in Python.
Therefore, all objects of ShimFixture[?] or derived classes use the same shim attribute value.
You need a Ctor (__init__) to define it as normal attribute (BEST-WAY) or in a method.
;-)
Thanks. I'm a "reference book in hand" Python programmer at this point. If you see anything else like that, by all means, let me know. I'll take care of the skipping issue in the next installment. BTW, I really enjoyed writing RowEntryFixture[?]. It was my first shot at mucking with the internals. - mf
You would need to modify 'table' or ... to skip over this element during the iteration.
But would it be rendered in the resulting page?!?
Otherwise a good idea (to think about refactoring into parts/components).
I had a similar one when I ported the RowEntryFixture[?] form Java to Python.
The whole concept of dynamically adding rows, cells, etc should be moved into one or more utility classes. It is seldom used, but when not dynamic Parse manipulation, you could easily reuse it.
NOTE (Design Critique):
The ShimFixture.[?]shim attribute is a class attribute in Python.
Therefore, all objects of ShimFixture[?] or derived classes use the same shim attribute value.
You need a Ctor (__init__) to define it as normal attribute (BEST-WAY) or in a method.
;-)
Thanks. I'm a "reference book in hand" Python programmer at this point. If you see anything else like that, by all means, let me know. I'll take care of the skipping issue in the next installment. BTW, I really enjoyed writing RowEntryFixture[?]. It was my first shot at mucking with the internals. - mf
Authors of large programs are wise to modularize things like application sequence, data access and input/output conversion. Where these modules exist in an application, it makes a lot of sense for Fit Fixtures to use them (after first testing them, of course). Be careful not to make your variation of Fit a platform for Fit-only modules that discourages modularity where it matters most, in the delivered application.
Nice approach. I've encountered exactly the same forces when writing FIT fixtures. But ShimFixture[?] is a terrible name. FIT suffers from very confusing class names that I've seen act as a hurdle when programmers start learning to use it. E.g. What is the difference bewtween Row and Column Fixtures: all tables have rows and columns. So, please find an intention-revealing name, for the same of all us FIT users.
<a href="http://www.bralawyers.org/mesothellaw.html"> http://www.bralawyers.org/mesothellaw.html </a>, <a href="http://www.bralawyers.org/appallaw.html"> http://www.bralawyers.org/appallaw.html </a>, <a href="http://www.bralawyers.org/lawstatevirgin.html"> http://www.bralawyers.org/lawstatevirgin.html </a>, <a href="http://www.bralawyers.org/commlawcontr.html"> http://www.bralawyers.org/commlawcontr.html </a>, <a href="http://www.bralawyers.org/autoinsurancelaw.html"> http://www.bralawyers.org/autoinsurancelaw.html </a>, <a href="http://www.bralawyers.org/publish.html"> http://www.bralawyers.org/publish.html </a>, <a href="http://www.bralawyers.org/brokintern.html"> http://www.bralawyers.org/brokintern.html </a>,<a href="http://www.bralawyers.org/lawinsan.html"> http://www.bralawyers.org/lawinsan.html </a>,<a href="http://www.bralawyers.org/bookintern.html"> http://www.bralawyers.org/bookintern.html </a>,<a href="http://www.bralawyers.org/florlem.html"> http://www.bralawyers.org/florlem.html </a>,<a href="http://www.bralawyers.org/findabankr.html"> http://www.bralawyers.org/findabankr.html </a>,<a href="http://www.bralawyers.org/indianaephedra.html"> http://www.bralawyers.org/indianaephedra.html </a>,<a href="http://www.bralawyers.org/miamibankr.html"> http://www.bralawyers.org/miamibankr.html </a>,<a href="http://www.bralawyers.org/texchild.html"> http://www.bralawyers.org/texchild.html </a>,<a href="http://www.bralawyers.org/criminlawnew.html"> http://www.bralawyers.org/criminlawnew.html </a>
Add Child Page to ComponentBasedFixturesForFit