Click here to monitor SSC
  • Av rating:
  • Total votes: 2
  • Total comments: 4
Michael Sorens

Acceptance Testing with FitNesse: Symbols, Variables and Code-behind Styles

16 January 2014

Although FitNesse can be used as a generic automated testing tool for both applications and databases, it excels at unit testing since it is designed with a Wiki-style interface that makes it very easy to set up tests.  In part 5,  Michael Sorens explains how to  use symbols and variables effectively, and why those terms are themselves rather problematic

Contents

Symbols

Storing and Retrieving Values in Symbols. 2

Avoid Global Scoping of Symbols. 3

Variables

Avoid Duplicating Data (Even if it is Nearby). 4

Avoid Duplicating Data from a Database. 4

Avoid Duplicating Data from Code. 5

Avoid Duplicating Data by Composition. 5

Specify Global Variables in the Right Place. 6

Code Behind Style

Use Proper Types. 7

Prefer Properties over Fields. 7

Discard Superfluous DLLs. 7

More to Come….. 8

FitNesse is a wiki-based framework for writing acceptance tests for software systems. If you are not familiar with FitNesse, Part 1 of this series walks through a complete .NET example from writing the test in your browser to writing the C# code-behind. While FitNesse provides a rather nifty and user-friendly way to write acceptance tests in general, in practice there are plenty of quirks and glitches to watch out for. This and the subsequent parts of this series provide “tips from the trenches”, i.e. an accumulation of tips collected from intensive use of FitNesse on a daily basis to alleviate or avoid many of those pain points.

Here is your roadmap to the series, showing where you are right now:

 

Part 1: FitNesse Introduction  and Walkthrough

 

Part 2: Documentation and Infrastructure

 

Part 3: Naming and Layout

 

Part 4: Debugging, Control Flow and Tracing

Part 5: Symbols, Variables, and Code-Behind Style

 

Part 6: Multiplicities and Comparisons

 

Part 7: Database Fixtures, Project Overview

Most sections in this article have references with actual hyperlinks to the FitNesse, fitSharp, or DbFit reference material. Some also have references to the sample test suite accompanying this series of articles, e.g. CleanCode.ConceptNotes.LayoutShowingEmbeddedNewlines. That path refers to a page on your FitNesse server. Thus if you are running on port 8080 on your local machine, the full URL to visit that page would be:
http://localhost:8080/CleanCode.ConceptNotes.LayoutShowingEmbeddedNewlines

Symbols

Storing and Retrieving Values in Symbols

FitNesse symbols work much like variables in a conventional programming language. In any test table cell you can store its value into a symbol or you can retrieve a previously stored symbol and use it as the value for the cell.

To store a cell’s value into a symbol for later reuse, use the double-greater-than (>>) operator. Notice how at runtime FitNesse displays the value that it is storing in the symbol. In this case, the Result value is not a testable condition, however; it is just storing the value.

!|Echo              |

|Value    |Result?  |

|25       |>>MyValue|

Echo

Value

Result?

25

>>MyValue

Echo

Value

Result?

25

>>MyValue 25

To retrieve a symbol’s value for consumption, use the double-less-than (<<) operator. Here the value in the symbol is retrieved and compared against the expected value of 25. The execution color codes the Result because here we are using a standard test assertion.

!|Echo              |

|Value    |Result?  |

|<<MyValue|25       |

Echo

Value

Result?

<< MyValue

25

Echo

Value

Result?

<< MyValue 25

25

When using DbFit (to retrieve database values), you have further options. The symbol operators just shown cannot be combined with anything else within a cell; i.e. the operator and symbol must be alone in a cell. Thus there would be no way to construct a parameterized query like this:

    SELECT * FROM Products WHERE Price = <<TargetPrice

DbFit provides an alternate access mechanism that does allow you to access a symbol when it is alone in a cell:

    SELECT * FROM Products WHERE Price = @TargetPrice

This @symbolName notation may be used within DbFit’s Query method. (I also instrumented the DbClean method in my fixture library to honor that parameter notation as well, discussed later.)

Furthermore, DbFit also provides an alternate storage mechanism in the form of the SetParameter method, e.g.:

|Set Parameter|ExpectedCountOfRecords|10|

This provides a more concise way to store a value into a symbol than my Echo fixture does.

Reference: Symbols, Working with Parameters

Avoid Global Scoping of Symbols

Symbols are scoped not just within a test but globally across your current execution context. Remember that FitNesse symbols are like variables in a conventional language? The issues described in the classic paper Global Variables Considered Harmful apply equally strongly here.

First, consider how global scope applies. Once a symbol is defined it is available throughout the current execution context, be that one test page or an entire suite. Thus, as the example shows you can define a value in one test and reference it in a later test. (FitNesse runs tests at each depth alphabetically so the order of execution is well defined.) As long as you run a suite that is a common parent of both tests you get the results shown. But if you run the second page by itself or in a suite that does not include the first page, the value will be undefined!

 

On First Test Page

On Second Test Page

!|Echo              |

|Value    |Result?  |

|25       |>>MyValue|

!|Echo              |

|Value    |Result?  |

|<<MyValue|25       |

Echo

Value

Result?

25

>>MyValue

Echo

Value

Result?

<< MyValue

25

Echo

Value

Result?

25

>>MyValue 25

Echo

Value

Result?

<< MyValue 25

25

Variables

Avoid Duplicating Data (Even if it is Nearby)

Assume, for example, you need to create a path, but you also need access to specific parts of that path for later use, so you define each part you need to access. FitNesse lets you easily create nested definitions because you can use any of the three common bracket sets interchangeably: ()    {}    []

 

With  Duplicated Data

Without Duplicated Data

!define BaseName (testfile)

!define FileName (testfile.xls)

!define FilePath (\foo\bar\testfile.xls)

!define BaseName (testfile)

!define FileName (${BaseName}.xls)

!define FilePath (\foo\bar\${FileName})

Defining a variable certainly looks like defining a constant in a conventional language. But do not be misled—not only can you update the value of a variable (with a later !define statement) but when you have compound definitions like those shown above the references are dynamic. That is, if you later change BaseName to be “otherfile” then FileName and FilePath reflect this new value when you reference them! In that sense, variables are more like macro definitions than constants, per se.

Reference: Variables

Avoid Duplicating Data from a Database

The last section showed how to avoid duplication when it was easy to do so—you had everything you needed immediately available. But even if your values are tucked away in a database it is still worth the effort to avoid duplication. Consider the example below. On the left, you define an ID for a particular database record. You then also define the corresponding invoice number, diligently adding a comment that they must be kept in sync—not good enough! That makes the test very fragile. Rather, fetch the value corresponding to the key from the database, as shown on the right, storing it in a symbol.

 

With  Duplicated Data

Without Duplicated Data

!define IdInvoice (12)

# This must match the record for the key value above!

!define InvoiceNumber (inv-2532)

!define IdInvoice (12)

 

!|query|!-SELECT InvoiceNumber

FROM Invoices

-!WHERE IDInvoice = ${IdInvoice}|

|InvoiceNumber?                 |

|>>InvoiceNumber                |

Avoid Duplicating Data from Code

If you need to use values already defined in your code, resist the urge to duplicate the value in your FitNesse test-- even if you provide a disclaimer (example, left side) ! Rather, retrieve the value from your codebase. If it is a constant in your code base create a simple fixture that exposes that constant to FitNesse. If it is in a standard .NET configuration file, you can leverage the AppSettingsFixture in my CleanCode fixture library (but note this only works for AppSettings, not ApplicationSettings!). In the example, right side, you can see how to retrieve a setting named DemoWorkingPath into the WorkingRoot symbol from the CleanCodeFixtures.dll.config file.

 

With  Duplicated Data

Without Duplicated Data

# must match value in app.config!

!define WorkingRoot (\\sys1\files\test\TestA)

!|AppSettings                                       |

|DllName              |PropertyName  |PropertyValue?|

|CleanCodeFixtures.dll|DemoWorkingPath|>>WorkingRoot|

Reference: AppSettings vs ApplicationSettings

Avoid Duplicating Data by Composition

The above several sections have shown how to collect data from specific sources in isolation to avoid duplication. You can combine those techniques to compose values from disparate sources as needed. One convenient way is with the Concat fixture from my fixture library attached to this article. Here is an example using a locally defined variable (IdInvoice) and a value retrieved from a database (InvoiceNumber):

!define IdInvoice (12)

 

!|query|SELECT InvoiceNumber from Invoices WHERE IDInvoice = ${IdInvoice}|

|Invoice Number?                                                         |

|>>InvoiceNumber                                                         |

 

!|Concat                                             |

|Separator|Value1      |Value2         |Result?      |

|_X_      |${IdInvoice}|<<InvoiceNumber|>>Description|

query

SELECT InvoiceNumber from Invoices WHERE IDInvoice = 12

Invoice Number

>> Invoice Number

 

Concat

Separator

Value1

Value2

Result?

_X_

12

<<Invoice Number

>>Description

query

SELECT InvoiceNumber from Invoices WHERE IDInvoice = 12

Invoice Number

>> Invoice Number 25

 

Concat

Separator

Value1

Value2

Result?

_X_

12

<< Invoice Number 25

<< Description 12_X_25

Concat takes up to 9 values; use it to combine variables, symbols, database values, Windows-defined environment variables, and FitNesse-defined environment variables. Environment variables of either type are easily accessible within a test using standard variable notation, e.g. ${COMPUTERNAME} or the FitNesse-defined ${PAGE_NAME}.

Reference: Variables

Specify Global Variables in the Right Place

Earlier you learned that you should avoid globally-scoped symbols because those are like conventional variables and everyone knows global variables are bad. But remember to FitNesse the term variables really just means macros and everyone knows global macros are good. Got it?

So FitNesse global variables, which are OK to use, should be in your SetUp page; even the top-level SetUp page is fine if those values are needed in many tests. But do not put them in your SuiteSetUp page as logically tempting as that may seem! Here is why—with one variable in each (i.e. one in a SetUp page and one in a SuiteSetUp page) I ran this test:

!|Echo                        |

|Value                |Result?|

|${SuiteSetupVariable}|abc    |

|${SetupVariable}     |123    |

Echo

Value

Result?

abc

abc

123

123

Echo

Value

Result?

undefined variable: SuiteSetupVariable

abc expected

---------------

undefined variable: SuiteSetupVariable actual

---------------

Length expected 3 was 38

123

123

 

Notice how the render output looked perfectly correct, but at runtime it blew up. I am rather sure this is a bug so it may well be fixed in the latest FitNesse download.

Reference: CleanCode.ConceptNotes.BuggyVariablesOnSuitePage

Code-Behind Style

Use Proper Types

When you want a yes/no answer from a fixture, it is tempting to use a String instead of a Boolean return type in your fixture code just to allow for flexibility of returning an error message (below, left). While the test table looks reasonable, a failure report is often inappropriate. If you get false when expecting true, it simply does a string comparison, pointing out that the actual result was 5 characters when you were expecting 4—that makes the failure much more opaque; the length of true vs. false is really not the point! The proper way is to return a Boolean and let exceptions happen (or throw an exception if appropriate). You can then use a test tables like that at the right, trapping exceptions:

 

Testing with  “Overloaded” Types

Testing with Proper Types

!|Test              |

|Input|Result?      |

|a    |True         |

|b    |False        |

|c    |error message|

!|Test                               |

|Input|Result?                       |

|a    |True                          |

|b    |False                         |

|c    |exception[ArgumentException]  |

|c    |exception["exception message"]|

Prefer Properties over Fields

FitNesse lets you use public fields or publicly settable properties for input; public fields, publicly readable properties, or public methods for output. But just as good encapsulation style in C# code dictate using properties over fields, you should observe the same practice in a FitNesse test. A second compelling reason to do so is that you get another form of strong typing. If you use fields (left side in the example) there is nothing preventing you from inadvertently mixing up what is an input and what is an output. If you use properties, you can define output properties to have a private setter so they cannot accidentally be misused as inputs.

 

With  Fields

With Properties

// inputs

public int IdBankClient;

public string BankReferences;

public string IncentiveInvoices;

 

// outputs

public string Details;

public int IdMatch;

// inputs

public int IdBankClient         { get; set; }

public string BankReferences    { get; set; }

public string IncentiveInvoices { get; set; }

 

// outputs

public string Details     { get; private set; }

public int IdMatch        { get; private set; }

Discard Superfluous DLLs

A very short item, but useful for economy’s sake. When you write fixtures, you need to add references to fit.dll, fitSharp.dll, and possibly dbfit.dll. In the process of adding these references in Visual  Studio the Copy Local  property defaults to true. But there is no need to copy these standard libraries with your custom library, so just set Copy Local to false.

More to Come…

Part 6 delves into the nuances of multiple inputs vs. multiple outputs, multiple rows vs. multiple columns, as well as things that can trip you up when attempting to validate a value.

Michael Sorens

Author profile:

Michael Sorens is passionate about software to be more productive, evidenced by his open source libraries in several languages (see his API bookshelf) as well as SqlDiffFramework (a DB comparison tool for heterogeneous systems including SQL Server, Oracle, and MySql). With degrees in computer science and engineering he has worked the gamut of companies from Fortune 500 firms to Silicon Valley startups over the last 25 years or so. Current passions include PowerShell, .NET, SQL, and XML technologies (see his full brand page). Spreading the seeds of good design wherever possible, he enjoys sharing knowledge via writing (see his full list of articles), teaching, and StackOverflow. Like what you have read? Connect with Michael on LinkedIn and Google +

Search for other articles by Michael Sorens

Rate this article:   Avg rating: from a total of 2 votes.


Poor

OK

Good

Great

Must read
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: Fitnesse
Posted by: Sonali (view profile)
Posted on: Wednesday, February 05, 2014 at 4:00 AM
Message: It was very difficult to start with Fitnesse as basic documentation is missing. Now after reading your articles, I know what am I doing and how should I do it.
Thanks a lot for putting it all together.

Waiting for next article.

Subject: article navigation doesn't work
Posted by: Anonymous (not signed in)
Posted on: Wednesday, February 12, 2014 at 6:06 AM
Message: but nice to read,
I was about giving up on Fitnesse with .NET.

I think it is old, but I have not found any newer / better way of doing automated acceptance tests.

2003 is about last millinum in sw time..

Subject: re: article navigation doesn't work
Posted by: Andrew Clarke (view profile)
Posted on: Wednesday, February 12, 2014 at 11:11 AM
Message: Does now!

Subject: Thank you!
Posted by: MD (not signed in)
Posted on: Tuesday, February 18, 2014 at 4:59 AM
Message: Tremendously useful series of articles on FitNesse - the original wiki authors should link to your articles. Really helped get me beyond the basic level of docs included with FN to something more useful and advanced. Thanks for your time and help!

 

Top Rated

Acceptance Testing with FitNesse: Multiplicities and Comparisons
 FitNesse is one of the most popular tools for unit testing since it is designed with a Wiki-style... Read more...

Acceptance Testing with FitNesse: Symbols, Variables and Code-behind Styles
 Although FitNesse can be used as a generic automated testing tool for both applications and databases,... Read more...

Acceptance Testing with FitNesse: Documentation and Infrastructure
 FitNesse is a popular general-purpose wiki-based framework for writing acceptance tests for software... Read more...

TortoiseSVN and Subversion Cookbook Part 11: Subversion and Oracle
 It is only recently that the tools have existed to make source-control easy for database developers.... Read more...

TortoiseSVN and Subversion Cookbook Part 10: Extending the reach of Subversion
 Subversion provides a good way of source-controlling a database, but many operations are best done from... Read more...

Most Viewed

A Complete URL Rewriting Solution for ASP.NET 2.0
 Ever wondered whether it's possible to create neater URLS, free of bulky Query String parameters?... Read more...

Visual Studio Setup - projects and custom actions
 This article describes the kinds of custom actions that can be used in your Visual Studio setup project. Read more...

.NET Application Architecture: the Data Access Layer
 Find out how to design a robust data access layer for your .NET applications. Read more...

Calling Cross Domain Web Services in AJAX
 The latest craze for mashups involves making cross-domain calls to Web Services from APIs made publicly... Read more...

Web Parts in ASP.NET 2.0
 Most Web Parts implementations allow users to create a single portal page where they can personalize... Read more...

Why Join

Over 400,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.