Introduction
This tutorial simply demonstrates each of the kinds of tables available in Slim using C#. Before getting started with this tutorial, make sure you understand this tutorial first.
Table Types
In Slim there are 5 major table types and one minor type (the import table):
Decision Table | A decision table allows individual rows to execute, one at a type. Typically used to insert data into the system for testing. This replaces the Column Fixture Table from Fit. |
Query Table | A query table allows you to specify a number of rows expected from querying the system. Surplus or missing rows are clearly labeled. This replaces the Row Fixture Table from Fit. |
Script Table | Individual rows represent individual method invocations. Can make tests more expressive, often used by developers looking for something more familiar. This replaces the doFixture from Fit Library. |
Scenario Table | Describes a logical sequence of steps, the details of which can be deferred to different script tables. There is no equivalent to this table type in Fit or Fit Library. |
Table Table | The body of the table is given to the fixture to do as it seems fit. This is equivalent to the Table Table from Fit. |
Import Table | Lists namespaces to be searched for classes when executing tests. This is equivalent to the import table from Fit. |
The Examples
Here is the configuration information I used for the following examples:
Location of FitNesse | C:\tools\fitnesse |
Command to start FitNesse | run -p 8080 |
Version of Slim | 1.2 |
Location of Slim | C:\tools\nslim |
Location of C# Solution | C:\projects\slim_example\slim_example.sln |
Location of generated DLL | C:\projects\slim_example\slim_example\bin\Debug |
Full path to top-level of FitNesse Tests | http://localhost:8080/CsharpWithSlim |
Given this information, the following definitions will make these examples work:
!define TEST_SYSTEM {slim}
!define COMMAND_PATTERN {%m -r fitSharp.Slim.Service.Runner,c:\tools\nslim\fitsharp.dll %p}
!define TEST_RUNNER {c:\tools\nslim\Runner.exe}
!path C:\projects\slim_example\slim_example\obj\Debug\slim_example.dll
Also, all of classes for these tables reside in the same namespace, so an import table will help with that. The import table must appear on the page that is executing. Simply putting it in the top level page will not work. Instead, make it a SetUp page. Here’s the contents of http://localhost:8080/CsharpWithSlimExamples.SetUp:
| import |
| slim_example |
The Page Hierarchy will ultimately be:
CsharpWithSlimExamples
SetUp
DecisionTableExample
QueryTableExample
ScripTableExample
ScenarioTableExample
TableTableExample
Decision Table
Here’s a example of a decision table (http://localhost:8080/CsharpWithSlimExamples.DecisionTableExample):
|Create Shows|5/6/2009 |
|Name |Episode |Channel|Start Time|Duration|Id? |
|House |Wilson Gets Mad |8 |19:00 |60 | |
|Chuck |He Gets Kung Fu Power |9 |19:00 |60 | |
|Dr. Phil |Episode #405:Teens in Trouble|3 |16:00 |60 | |
|Dr. Who |Yet another doctor |12 |20:00 |30 |$lastProgram=|
The first row names the table, Create Shows, and requires that the underlying class have a constructor taking a single parameter.
The second row names the columns and requires that the underlying class:
- Has a field/property/method named Name, _name, _Name, setName (and for each of the columns up to and including Duration).
- Has a method named Id that returns some value.
The remaining rows represent data going into the system, one row at a time.
The last row uses the value returned from the Id() method and stores it in a variable. That variable is available for the rest of the page.
Here is a C# fixture to handle this Decision Table (there are more ways to write this class):
using System;
namespace slim_example
{
public class CreateShows
{
public String ProgramDate { get; set; }
public String Name { get; set; }
public String Episode { get; set; }
public int Channel { get; set; }
public String StartTime { get; set; }
public int Duration { get; set; }
public String LastId { get; set; }
public CreateShows(string programDate)
{
ProgramDate = programDate;
}
public void Execute()
{
LastId = string.Format("{0}:{1}", Name, Channel);
}
public String Id()
{
return LastId;
}
}
}
Note that I’ve chosen to use auto properties, but you could use private fields (with or without a leading _, starting with or without a capital letter), setX() methods or regular properties.
The Constructor takes one parameter (and stores it). You’d probably use this as part of constructing objects. This information is passed into the constructor because it is the same for all rows. If you wanted to vary it by row, then you’d pass it in as one of the columns. The ID() method was mentioned above. Could you duplicate it per row? Yes. I just didn’t do that.
The one thing not yet discussed is the Execute() method. This method is called after all of the columns without ? in their name have been processed but before the columns with ? in their name have been processed. In this example:
- Slim sets each of the fields, left to right (Name to Duration).
- Calls the Execute method.
- Calls the Id() method
As mentioned above, the last row will take the result of the Id() method, Dr. Who:12, and assign that value to $lastProgram.
Query Table
Now for a query table (http://localhost:8080/CsharpWithSlimExamples.QueryTableExample):
!|Query:Get Programs On A Given Day And Channel|3/4/2009|3 |
|Name |Episode |StartTime|Duration|
|N1 |E1 |18:00 |60 |
|N1 |E2 |19:00 |60 |
|N1 |E3 |20:00 |60 |
The first row describes a table expecting a class called “GetProgramsOnAGivenDayAndChannel” with a constructor taking two parameters.
The second row names the fields that should be in the returned object. Note you ultimately create the results, to the actual names can be anything you want. You do not have to list all of the fields, just what you want to check. You can return more fields than you check.
The final 3 rows show the expected results from this query into the system.
The ! as the first character tells FitNesse to ignore embedded Wiki Words. If you did not put this, the “StartTime” column would be interpreted as a WikiPage name and would not process correctly. Your two options to fix this are to either introduce a space or put ! at the beginning of the table. You can do both, it won’t hurt anything, but it’s not necessary.
Here’s a class that will get this table to pass:
using System;
using System.Collections.Generic;
namespace slim_example
{
public class GetProgramsOnAGivenDayAndChannel
{
public GetProgramsOnAGivenDayAndChannel(String date, int channel)
{
}
public List<Object> Query()
{
var result = new List<Object>();
for (int i = 1; i <= 3; ++i)
AddOneObject(result, i);
return result;
}
private void AddOneObject(ICollection<object> objects, int index)
{
var objectFields = new List<object>();
AddName(objectFields);
AddEpisode(objectFields, index);
AddStartTime(objectFields, index);
AddDuration(objectFields);
objects.Add(objectFields);
}
private void AddName(List<object> objectDescription)
{
AddFieldNameAndValue(objectDescription, "Name", "N1");
}
private void AddEpisode(List<object> objectDescription, int index)
{
string episodeName = string.Format("E{0}", index);
AddFieldNameAndValue(objectDescription, "Episode", episodeName);
}
private void AddStartTime(List<object> objectDescription, int index)
{
string startTime = string.Format("{0}:00", index + 17);
AddFieldNameAndValue(objectDescription, "StartTime", startTime);
}
private void AddDuration(List<object> objectDescription)
{
AddFieldNameAndValue(objectDescription, "Duration", "60");
}
private void AddFieldNameAndValue(List<object> objects, string name, string value)
{
var fieldNameValue = new List<Object>();
fieldNameValue.Add(name);
fieldNameValue.Add(value);
objects.Add(fieldNameValue);
}
}
}
Script Table
A script table allows you more control over information going into methods. You can call different methods with a variable number of parameters. Here is one example (http://localhost:8080/CsharpWithSlimExamples.ScriptTableExample):
!|Script |Generate Programs |
|$P1= |Create Weekly Program Named|W1|On Channel|7|Starting On|3/4/2008|at|21:00|Length|60|Episodes|8 |
|Create Weekly Program Named|W2|On Channel|8|Starting On|3/4/2008|at|21:00|Length|60|Episodes|8 |
|show |TotalEpisodesCreated |
|Create Daily Program Named |D1|On Channel|7|Starting On|3/4/2008|at|20:30|Length|30|Episodes|56 |
|Create Daily Program Named |D2|On Channel|8|Starting On|3/4/2008|at|22:00|Length|30|Episodes|56 |
|check |TotalEpisodesCreated|128 |
The first line, as with the previous examples, names the class. In this case it is GeneratePrograms. While there are no parameters sent to a constructor, you can add additional cells to this line to pass parameters into a constructor.
The second line executes a method with a rather long name: CreateWeeklyProgramNamedOnChannelStartingOnAtLengthEpisodes. The name is formed by taking the first cell that involves part of a method name (in this case the cell with “Create Weekly Program Named”) and then taking each of the alternating cells. The parameters passed into this method are: W1, 7, 3/4/2008, 21:00, 60, 8. The result of that method invocation is stored in the variable $P1.
The third line uses a keyword,show, that simply calls the method named “TotalEpisodesCreated” and then displays the result during test execution.
The next two lines call another method with a long name: CreateDailyProgramNamedOnChannelStartingOnAtLengthEpisodes. The naming rules are the same. Since the first cell does not involve a variable assignment or a keyword likeshow orcheck, the name of the method starts in the first cell. The parameters passed in are the alternating cells starting with D1, and they are: D1, 7, 3/4/2008, 20:30, 30, 56.
The final line uses a keyword,check, to call a method called “TotalEpisodesCreated” and compares the return of that method to the next value, 128.
Here’s the code to get this table to pass:
namespace slim_example
{
public class GeneratePrograms
{
private int _totalEpisoesCreated;
public string CreateWeeklyProgramNamedOnChannelStartingOnAtLengthEpisodes(
string name, int channel, string startDate, string startTime, int length,
int episodeCount)
{
_totalEpisoesCreated += episodeCount;
return string.Format("{0}:{1}", name, channel);
}
public int TotalEpisodesCreated()
{
return _totalEpisoesCreated;
}
public void CreateDailyProgramNamedOnChannelStartingOnAtLengthEpisodes(
string name, int channel, string startDate,
string startTime, int length,
int episodeCount)
{
_totalEpisoesCreated += episodeCount;
}
}
}
Scenario Table
A scenario table describes a logical sequence of steps. By itself it does not require a backing class. Here is an example scenario table (http://localhost:8080/CsharpWithSlimExamples.ScenarioTableExample):
!|Scenario |dvrCanSimultaneouslyRecord|number|andWithThese|seasonPasses|shouldHaveTheFollowing|toDoList|
|givenDvrCanRecord |@number |
|whenICreateSeasonPasses |@seasonPasses |
|thenTheToDoListShouldContain|@toDoList |
The first line serves two purposes:
- Names the scenario
- Defines the parameters.
The name of the scenario starts with the first cell after the “Scenario” cell and it has the value: dvrCanSimultaneouslyRecord. The full name of the scenario involves all of the even numbered cells:
- dvrCanSimultaneouslyRecord
- andWithThese
- shouldHaveTheFollowing
To determine the full name, FitNesse will capitalize the first letter of each of the parts and allow spaces (or not). So the name of the scenario is: Dvr Can Simultaneously Record And With These Should Have The Following.
The parameters are in the odd numbered cells excluding the first cell. Those parameters are:
- number
- seasonPasses
- toDoList
After the first row, subsequent rows describe individual steps and optionally refer to input parameters. In this example, the scenario has three steps:
- givenDvrCanRecord
- whenICreateSeasonPasses
- thenTheToDoListShouldContain
This particular naming style derives from a story running tool called Cucumber. The first line starting with the word “given” is something related to test setup. The second line, starting with “when” is an execution step. The final step, starting with “then” is a validation step. This has nothing to do with FitNesse, this is just a style of writing tests.
As mentioned, scenario tables do not require any class to exist. However, when a pageuses a scenario, then the most recent script is will require some methods based on most recently mentioned script.
Here is a complete page that(http://localhost:8080/CsharpWithSlimExamples.ScenarioTableExample):
- Defines a scenario
- Mentioned a random script
- Uses the scenario
!|Scenario |dvrCanSimultaneouslyRecord|number|andWithThese|seasonPasses|shouldHaveTheFollowing|toDoList|
|givenDvrCanRecord |@number |
|whenICreateSeasonPasses |@seasonPasses |
|thenTheToDoListShouldContain|@toDoList |
|Script|Some Random Script|
|Dvr Can Simultaneously Record And With These Should Have The Following|
|number |seasonPasses |toDoList |
|1 |D5_1:5,D6_1:6 |D5_1:E:1-7 |
The middle table mentions a script literally called “Some Random Script”.
The final table names the scenario and therefore requires some class handle the scenario. Since the most recently mentioned script table was “Some Random Script”, Slim will look for a class called SomeRandomScript. Furthermore, the scenario has three lines, and therefore three required methods that the class SomeRandomScript must have:
- givenDvrCanRecord
- whenICreateSeasonPasses
- thenTheToDoListShouldContain
Note that the name of the method can start with a lower case letter, but Slim will also look for a capital first letter.
Here is a class that will get this page to pass:
namespace slim_example
{
public class SomeRandomScript
{
public void GivenDvrCanRecord(int numberOfRecorders)
{
}
public void WhenICreateSeasonPasses(string seasonPasses)
{
}
public void ThenTheToDoListShouldContain(string expectecdContents)
{
}
}
}
Table Table
With a table table, you are given the table, minus the first row, and can do anything you wish. Here is one such example (http://localhost:8080/CsharpWithSlimExamples.TableTableExample):
!|Table:CreateOneDayProgramGuide|1:00|3/4/2008|
| |1 |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 |12 |13 |14 |
|200|aaaa|BBcc|cccc|ccDD|DDee|efff|ffff|fffg|gggg|gggh|hhii|jklm|nopq|rstt|
|247|aaaa|BBBB|cccc|DDDD|eeee|FFFF|gggg|HHHH|iiii|JJJJ|kkkk|LLLL|mmmm|NNNN|
|302|aaBB|ccDD|eeFF|ggHH|iiJJ|kkLL|mmNN|ooPP|qqRR|ssTT|uuVV|wwww|wwXX|XXXy|
|501| | | | | |aaBB|ccDD|eeFF|ggHH|iiJJ|kkLL|mmNN|ooPP|qqRR|
|556| |__aa|BBcc|DDee|FFgg|HHii|JJkk|LLmm|NNoo|PPqq|RRss|TTuu|VVxx|xxxx|
The class is called CreateOneDayProgramGuide. Note you can use spaces if you wish. This class, apparently, has a 2-argument constructor. When the fixture is executed, it receives all but the first row as a List<List
Here is a fixture to handle this example:
using System;
using System.Collections.Generic;
namespace slim_example
{
public class CreateOneDayProgramGuide
{
public CreateOneDayProgramGuide(string startDate, string startTime)
{
}
public List<object> DoTable(List<List<String>> table)
{
return null;
}
}
}
Summary
This is simply a quick summary of the types of tables available and minimal code to get those fixtures to pass. There’s much more to consider in terms of test design and connecting test fixtures to production code. You can get an idea of how to proceed looking at the Java tutorials.
Comments