Fitnesse Variables Assignment Definition

Included page: .FitNesse.SuiteAcceptanceTests.ScenarioLibrary(edit)

scenariogiven pagepagewith contentcontent
create page@pagewith content@content
$IT=echo@page

scenariogiven pagepage
given page@pagewith contentnothing
$CONTENT=echo

scenariogiven test pagepage
given page@page
make@pagea test page

scenariogiven slim test pagepage
given page@pagewith content!define TEST_SYSTEM {slim}
make@pagea test page

scenariopagesourceshould have link totarget
checkrequest page@source200
ensurecontent contains<a href="@target"
$IT=echo@source

scenarioit should have link totarget
page$ITshould have link to@target

scenarioand it should have link totarget
page$ITshould have link to@target

scenariopagesourceshould have creating link totarget
checkrequest page@source200
ensurecontent contains@target<a title="create page" href="@target?edit&nonExistent=true">[?]</a>

scenarioit should have creating link totarget
page$ITshould have creating link to@target

scenariopagesourceshould containtext
checkrequest page@source200
ensurecontent contains@text
showcontent

scenariopagesourceshould not containtext
checkrequest page@source200
rejectcontent contains@text
showcontent

scenariopagesourceshould matchtext
checkrequest page@source200
ensurecontent matches@text
showcontent

scenarioit should containtext
page$ITshould contain@text

scenarioit should not containtext
page$ITshould not contain@text

scenarioit should containtextin linesymbol
checkrequest page$IT200
$@symbol=line number containing@text

scenarioit should matchtext
page$ITshould match@text

scenariotest results for pagesourceshould containtext
checkrequest page@source?test200
ensurecontent contains@text
showcontent

scenariotest results for suitesourceshould containtext
checkrequest page@source?suite200
ensurecontent contains@text
showcontent

scenarioits test results should containtext
test results for page$ITshould contain@text

scenariotest ressults for pagesourceshould not containtext
checkrequest page@source?test200
rejectcontent contains@text
showcontent

scenarioand should containtext
ensurecontent contains@text
showcontent

scenarioand should matchtext
ensurecontent matches@text
showcontent

scenarioand should not containtext
rejectcontent contains@text
showcontent

scenariowidgetwikiTextshould renderhtmlText
create pageWidgetPagewith content@wikiText
checkrequest pageWidgetPage200
ensurecontent matches@htmlText
showcontent

scenariothe lineaftershould come afterbefore
checkecho int$@before< $@after

scenariopass
checkechopasspass

scenarioshow collapsedcontent
show@content

scenarioshow Symbolresult

scenariothenpassassertions pass,failfail,ignoreare ignoredexceptionexceptions thrown
ensurecontent matchesAssertions:<[^<]*@pass right, @fail wrong, @ignore ignored, @exception exceptions
showextract match;Assertions:<[^<]*exceptionscontents0

scenarioand celltexthas resultresult
ensurecontent matchesclass="@result">@text<
showextract match;class="[^"]+">@text<contents0

variable defined: TestSTART=@@@START: Test specific content
variable defined: TestEND=@@@END: Test specific content

scenarioand TestSystem setup iscontent
$CONTENT=echo$CONTENT @content

scenarioand setup content iscontent
$CONTENT=echo$CONTENT @content

scenarioand test content iscontent
given page$ITwith content$CONTENT @@@START: Test specific content@content@@@END: Test specific content
make$ITa test page

scenarioget HTML result
startResponse Examiner.
setTypecontents
setPattern@@@START: Test specific content[^<]*(.*>)\s*@@@END: Test specific content
setGroup1
$HTML_Result=found

scenarioget HTML input
startResponse Examiner.
setTypepageHtml
setPattern@@@START: Test specific content[^<]*(.*>)\s*@@@END: Test specific content
setGroup1
$HTML_Input=found
show collapsedget value


scenarioget collapsed executon log for pagesource
checkrequest page@source?executionLog200
showcontent

scenariowhen pagesourceis tested
checkrequest page@source?test200
show collapsedcontent

scenariowhen pagesourceis tested and HTML is extracted
when page@sourceis tested
get HTML result
get HTML input


Test that variables are properly defined and expressed on a page.

  • Variables are defined as !define name {value}.
  • Alternately they are defined as !define name (value).
    • This is so that you can create variables with {} or () in them.
  • They are expressed using ${name}


Test a variable defined and used on the same page.

  • Create a page with a variable definition and use..
script
startPage Builder
line!define x {1}
linex is ${x}
linePAGE_NAME is ${PAGE_NAME}
linePAGE_PATH is ${PAGE_PATH}
pageVariablePage
Response Requester.
urivalid?contents?
VariablePagetrue
  • Inspect the text to see if the variable was expressed.
Response Examiner.
typepatternmatches?
contentsx is 1true
contentsPAGE_NAME is <a href="VariablePage">VariablePagetrue
contentsPAGE_PATH is .true



Test a variable defined and used on the same page using () syntax.

  • Create a page with a variable definition and use..
script
startPage Builder
line!define x (1)
linex is ${x}
pageParenVariablePage
Response Requester.
urivalid?contents?
ParenVariablePagetrue
  • Inspect the text to see if the variable was expressed.
Response Examiner.
typepatternmatches?wrapped html?
contentsx is 1true



Test literals in variables with {} and () syntax.

The internal representation of a literal is changed from !lit(n) to !lit?n? so as to avoid conflict with the !define X () widget syntax.

  • Create a page with a variable definition and use..
script
startPage Builder
line!define xLitBRACE {!-xLitBRACE-!-!-!}
line!define xLitPAREN (!-xLitPAREN-!-!-!)
linexLitBRACE is ${xLitBRACE}
linexLitPAREN is ${xLitPAREN}
pageParenVariablePage
Response Requester.
urivalid?contents?
ParenVariablePagetrue
  • Inspect the text to see if the variable was expressed.
Response Examiner.
typepatternmatches?wrapped html?
contentsxLitPAREN is xLitPARENtrue
contentsxLitBRACE is xLitBRACEtrue



Test a variables with periods

  • Create a page with a variable definitions with periods.
script
startPage Builder
line!define xy. (1)
line!define x.y (2)
line!define .xy (3)
line!define .x.y. (4)
line!define .xy. (5)
line!define .x.y (6)
line!define x.y. (7)
line!define x..y (8)
line~xy. is ${xy.}~
line~x.y is ${x.y}~
line~.xy is ${.xy}~
line~.x.y. is ${.x.y.}~
line~.xy. is ${.xy.}~
line~.x.y is ${.x.y}~
line~x.y. is ${x.y.}~
line~x..y is ${x..y}~
pageParenVariablePage
Response Requester.
urivalid?contents?
ParenVariablePagetrue
  • Inspect the text to see if the variables were expressed properly.
Response Examiner.
typepatternmatches?wrapped html?
contents~xy. is 1~true
contents~x.y is 2~true
contents~.xy is 3~true
contents~.x.y. is 4~true
contents~.xy. is 5~true
contents~.x.y is 6~true
contents~x.y. is 7~true
contents~x..y is 8~true

Keyboard Shortcuts

  • General

  • Test page or suite
  • Edit page
  • View page (after test)
  • Add new page
  • Go To

  • User Guide
  • Quick Reference Guide
  • Full Reference Guide
  • Recent Changes
  • Edit Mode

  • Save page
  • Cancel edit
  • Tools

  • Properties
  • Where used?
  • Versions
  • Page History
  • Focus on search bar
  • Open search page
Close

Contents

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:

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:

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 value is not a testable condition, however; it is just storing the value.

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 because here we are using a standard test assertion.

 

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:

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

This notation may be used within DbFit’s method. (I also instrumented the 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 method, e.g.:

This provides a more concise way to store a value into a symbol than my 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

 

 

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: () {} []

 

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 statement) but when you have compound definitions like those shown above the references are dynamic. That is, if you later change to be “otherfile” then and 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.

 

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 in my CleanCode fixture library (but note this only works for , not !). In the example, right side, you can see how to retrieve a setting named into the symbol from the CleanCodeFixtures.dll.config file.

 

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 fixture from my fixture library attached to this article. Here is an example using a locally defined variable () and a value retrieved from a database ():

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. or the FitNesse-defined .

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 page; even the top-level page is fine if those values are needed in many tests. But do not put them in your page as logically tempting as that may seem! Here is why-with one variable in each (i.e. one in a page and one in a page) I ran this test:

 

 

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 instead of a 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 when expecting , 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 vs. 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:

 

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.

 

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 property defaults to true. But there is no need to copy these standard libraries with your custom library, so just set 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.

One thought on “Fitnesse Variables Assignment Definition

Leave a Reply

Your email address will not be published. Required fields are marked *