Sunday, November 3, 2013

Customizing fitSharp with plug-in behavior

When I started working on FitNesse.NET several years ago, it already included a plug-in mechanism, known as 'cell handlers'.  The purpose of cell handlers was to customize the way the cell contents were handled.  For example, the standard behavior for checking a cell with an expected value is to parse the cell contents based on the actual value type, compare the actual and expected values, and mark the cell as right or wrong.  But expected value cells with keywords, like 'null', or symbol operators, like '>>mysymbol', require different behavior.

FitNesse.NET contained a list of classes that implemented a cell handler interface.  A cell handler could tell if it was able to handle a cell, usually based on the cell content.  If so, it could perform the appropriate behavior for basic cell operations like parsing and checking.  The list of cell handlers could be modified by a 'cell handler loader' fixture.  So individual tests or suites could have customized behavior by loading new cell handlers.

When I re-designed and re-branded FitNesse.NET as fitSharp, I kept the cell handler concept, expanding its use and making it into a more generic plug-in mechanism.  Rather than a single cell handler interface, fitSharp uses a separate interface for each operation that can be customized.  For example, there is a ParseOperator interface for parsing cell contents and a CheckOperator interface for checking an expected value cell. A class can implement one or more of these interfaces to provide customized behavior.

Every operator interface has two methods: one to indicate if the implementing class can provide behavior for a cell, and one to implement the behavior for the cell.  So the CheckOperator interface has methods CanCheck and Check.

fitSharp contains a list of operator classes, and whenever it needs to perform an operation, it scans the list looking for the first class that returns true from CanXxxx.  The list includes a priority number so, if needed, one operator class can always override another operator class.  Operator classes can be added to the list in a configuration file, in fixture code or in a test.

fitSharp currently contains these operator interfaces:
  • CheckOperator - checks an expected value cell
  • CompareOperator - compares a cell value to an actual value
  • ComposeOperator - constructs a cell with a given value
  • CreateOperator - creates an instance of a type
  • ExecuteOperator - executes a method described by member name and parameter cells
  • FindMemberOperator - finds a member of a type
  • InvokeOperator - invokes a method on the system under test
  • InvokeSpecialOperator - invokes a keyword method
  • ParseOperator - parses a cell value
  • RunTestOperator - runs a story test
  • WrapOperator - wraps a value in a fixture that can evaluate it
As you can see from the list, this plug-in mechanism is now used to customize a variety of operations, beyond basic cell behavior.

Every standard fitSharp fixture, and any other fixtures that need to perform any of these operations, must use the operator list to find the appropriate class to perform the operation.  Every fixture is passed a Processor interface that provides a method to do this, e.g:
processor.Operate(...);
 There is also a number of helper methods for common operations, like processor.Parse(), processor.Invoke() and others.