1 Executing Scripts using Script Tables - essenius/FitNesseFitSharpGameManagementDemo GitHub Wiki
Adding Players
We talk to the business rep about the user story “As a game administrator, I am able to register users with their skill levels, so I can manage game competitions”, and he starts explaining: “There are 3 levels: beginner, intermediate and advanced. When a player is added, the user name and the skill level are registered.” Let’s create a FitNesse script to reflect that. We create a few users with associated skill levels.
Ensure that you have started FitNesse as per the readme. The current directory doesn't matter very much right now as we are not running tests yet.
Create a page GameManagementTest
(http://localhost:8080/GameManagementTest) and enter the following content:
| Script | player management driver |
| ensure | add player | Steve | with skill | beginner |
| ensure | add player | Julie | with skill | intermediate |
| ensure | add player | Bill | with skill | intermediate |
| ensure | add player | John | with skill | advanced |
The first line identifies the fixture: It is a script fixture with name player management driver
. This indicates that it stimulates the player management subsystem of our application. The following lines call fixture methods to execute a test step. The first column in a line can contain a (case insensitive) command, which affects how it interacts with the fixture code. The table below shows all available commands. The default command (i.e. the command taken if you don’t specify one) is ensure
.
Command | Behavior |
---|---|
Ensure | Expects a bool return value from the fixture method, and the test passes if the bool is True |
Reject | Expects a bool return value from the fixture method, and the test passes if the bool is False |
Check | Compares the return value of the fixture method to the last column in the row, and the test passes if that check passes. Values can be boolean, numbers or strings |
Show | Add a column to the row showing the return value of the fixture method. It is not evaluated as a test |
Note | Treat as comment, i.e. ignore everything following this. |
$symbol= | Assign the return value of the fixture method to a symbol (discussed later) |
Let’s have a closer look at the first line of the script after the definition.
| ensure | add player | Steve | with skill | beginner |
FitNesse will try and convert the script line into a fixture method to be called with parameters. The parameters can be inserted within the fixture name if they are in a column of themselves. Make sure to put at least a part of the fixture name between each parameter, or use a double pipe between parameters. So, if the first column is a command, the second column is part of the fixture name and the third column can be a parameter. The fourth is again part of the fixture method name, and so on. FitNesse creates the fixture name by concatenating all these parts, “PascalCasing” the words, and eliminating spaces. So, in this case, when executing the test, it will look for the method bool AddPlayerWithSkill(param1, param2)
.
It searches for a bool return value since the line starts with ensure
. Actual parameter types or names are not important; it will work if it can cast the value to it. Here, string
will be a good fit. The method should return true
if the player was successfully added, and false
if not. Our signature becomes bool AddPlayerWithSkill(string playerName, string skillLevel)
.
The following lines in the script are just repeats of the same pattern, to have more test data. Note that FitNesse generally ignores leading and trailing spaces in cells – the pipe symbols indicate the start of a new column, and it is not important whether they align over different rows. Some form of alignment does usually make it easier to read for people though. That is why the FitNesse page editor has a facility to format the table for you. You can also use autoformat, to automatically format the page when it is saved.
The business rep continues "The user name uniquely identifies a player, so there can’t be two players with the same name”. We can test that by trying to add a user with the same name, and expecting that to fail. Furthermore, we expect that the total number of users does not increase.
Storing Temporary Results with Symbols
The requirement posed in the previous section implies we need to retrieve the number of players before the attempt, and check that against the number of players after the attempt. In other words, we need to store a temporary result. For that, FitNesse has the concept of symbols. They start with a dollar sign ($
) followed by an identifier. Their scope is a test page, so you can’t share the values between different pages. They are quite similar to variables in programming languages.
We add the following lines to our script (without empty lines in between):
|note |check if duplicates are rejected |
|$players=|player count |
|reject |add player |John |with skill|intermediate|
|check |player count|$players |
The second line looks for a method PlayerCount()
and assigns its return value to symbol $players
. The third line tries to add player John again, expecting that to fail. The fourth line checks to see if the new player count is the same as it was before the attempt to add John a second time.
Checking for Skill Levels
The observant reader will see that the skill level is different from the first time that John was added. How should the system behave? Our business rep responds “Completely ignore a duplicate addition. If the skill levels are different, keep the one that was already there”.
We add another line:
|check |player |John |if skill |advanced |
To run this test, FitNesse will try to execute method string PlayerIfSkill(string playerName)
. It will then compare whether the return value of that method is “advanced”, and pass the test if so.
While we are at it, let’s also verify we didn’t have any side effects so far, and all are still the same as we specified them:
|check |player |Steve|if skill |beginner |
|check |player |Julie|if skill |intermediate|
|check |player |Bill |if skill |intermediate|
Another question pops up: what if we check for a non-existing player? The business rep tells us “the system should signal that in some way”. Jointly you agree that returning the value null
is sufficient.
|note |Check that non-existing players are handled OK|
|check |player |Bart |if skill |null |
Value Comparisons
As you have seen so far, you can specify values in the table cells to check whether the expected values and the actual values are exactly equal. But you can do relative numerical comparisons as well. The possibilities are shown below. Some examples also show that you can use the underscore (_) to denote the actual value.
Operator | Definition | Example | Passes if the actual value is |
---|---|---|---|
< | Less than | 2<_<7 | Between 2 and 7 (exclusive) |
> | Greater than | >5 | Greater than 5 |
<= | Less than or equal to | -10<=_<=0 | Between -10 and 0 (inclusive) |
>= | Greater than or equal to | >=100 | Not less than 100 |
!= | Not equal to | !=6 | Any other number than 6 |
~= | Approximately equal to | ~=3.0 | Equal to 3.0 if rounded to 1 decimal |
You cannot use these operators with string values. Instead, you can use a regular expression (Java standard) to do potentially very sophisticated matching operations. The syntax is =~/regex/
; see the table below for a few examples (for more details about regular expressions see e.g., Wikipedia).
Some examples of regular expression matching
Expression | Passes if the actual value |
---|---|
=~/FoundValue/ | Contains the value FoundValue |
=~/\d{4}/ | Contains at least 4 consecutive digits |
=~/^\d{4}$/ | Consists of exactly 4 digits (start, 4 digits, end) |
=~/^(?!WrongValue$).*/ | Is not equal to WrongValue |
To show the numerical comparison, let’s add a further check. We add another player and check whether the number of players has increased.
|ensure |add player |Tina |with skill |advanced |
|check |player count |>$players |
The comparison >$players
in the second line checks whether the value returned from PlayerCount()
is larger than the value of symbol $players
which we set earlier.
The complete test page should now look as follows:
|Script |player management driver |
|ensure |add player |Steve |with skill |beginner |
|ensure |add player |Julie |with skill |intermediate |
|ensure |add player |Bill |with skill |intermediate |
|ensure |add player |John |with skill |advanced |
|note |check if duplicates are rejected |
|$players=|player count |
|reject |add player |John |with skill |intermediate |
|check |player count |$players |
|check |player |John |if skill |advanced |
|check |player |Steve |if skill |beginner |
|check |player |Julie |if skill |intermediate |
|check |player |Bill |if skill |intermediate |
|note |Check that non-existing players are handled OK|
|check |player |Bart |if skill |null |
|ensure |add player |Tina |with skill |advanced |
|check |player count |>$players |
We have the test page ready to go. Let's check how we can implement a fixture to run this script.