Storyteller 4.2.0


Next

Verifying Sets

Previous

'Macros' with Paragraph Grammars

Tables Edit on GitHub


Storyteller is at its best when you are able to express your specifications as declarative tables. In practice, teams use Table grammars to set up input data for specifications and to express decision tables with each row representing a use case of inputs and expected outcomes.

As a Single Method

The easiest way to create a Table grammar is to decorate a public method in a Fixture class with the [ExposeAsTable] attribute.

Let's say that we want to create a table that verifies that .Net can successfully add two integers together. We can write a table grammar like this one below:


[ExposeAsTable("Adding numbers together", "sum")]
[return: AliasAs("sum")]
public int Sum(int x, int y)
{
    return x + y;
}

In usage, the results of that grammar would look like this:

Some things to note:

  • The method is executed for every row of the table
  • Normal method parameters are treated as inputs
  • The return value of a method, if there is one, is treated as an expectation
  • Any output parameters of the method are also treated as an expectation
  • By default, the header string in the table column is the name of the parameter, but that can be overridden as shown in the next section

Overriding Headers and Cells

Assuming that you are using a method as the basic for a Table grammar, you can use the [Header] attribute to override the header for that parameter in the rendered html.


[ExposeAsTable("Table with lots of options")]
public void TableWithLotsOfOptions(
    [Header("Player Name")]string player,
    [Header("Position"), DefaultValue("Outfield")]
    [SelectionValues("Pitcher", "Outfield", "Catcher")]
    string position
    )
{
    // Set up your roster
}

See Cells and Selection Lists for more information;

Before and After Actions

You may find scenarios where you want to execute some kind of action before or after all of the rows in a Table are processed. The easiest way to accomplish that is to create two grammars:

  1. A sentence grammar that performs the action for each row
  2. A table grammar built in a second method that wraps the first sentence grammar in a table and registers before and after actions.

The grammars below demonstrate this usage:


public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

private readonly IList<User> _users = new List<User>();

// This grammar is only useful as a building block
// for our table below, so we hide it from the 
// grammar selector in the UI editor
[Hidden]
public void BuildUser(string First, string Last)
{
    var user = new User { FirstName = First, LastName = Last };
    _users.Add(user);
}

public IGrammar TableWithBeforeAndAfterSteps()
{
    return this["BuildUser"].AsTable("The Users are")
        .Before(() => _users.Clear())
        .After(() => saveUsersToTheDatabase(_users));
}

private void saveUsersToTheDatabase(IEnumerable<User> users)
{
    // do whatever it takes to persist the new user objects
    // to your database
}

In action, those two grammars are just a table:

From a Paragraph

The AsTable(the title of the table grammar) usage above can also be used against a Paragraph grammar if it only consists of Sentence grammars. This ability is frequently exploited for special cases of Paragraph's that either set up a new object for a test input or verify the state of some objects in your system.

Here's a contrived example taken from the internal tests in Storyteller itself:


public IGrammar Divide()
{
    return Paragraph("Divide numbers", x =>
    {
        x += c => _first = _second = 0;
        x += Read<double>("x", o => _first = o);
        x += Read<double>("y", o => _second = o);
        x += Check("quotient", () => _first/_second);
    }).AsTable("Do some division");
}

In usage, the grammar above will render to this:

Using DecisionTableGrammar

Finally, there is an older mechanism for creating decision tables with multiple inputs and outputs that has been retained for the sake of backward compatibility. If you are starting fresh with Storyteller 3.0, we recommend simply using Sentence methods with multiple output parameters if you want to assert on multiple columns in the same table.

That being said, the legacy mechanism is to create a subclass of DecisionTableGrammar like this one:


public class Decisions : DecisionTableGrammar
{
    private string _first;
    private string _last;

    public Decisions()
        : base("What's my name?")
    {
    }

    public string FirstName { set { _first = value; } }

    public string LastName { set { _last = value; } }

    public string FullName { get { return _first + " " + _last; } }

    public string LastNameFirst { get { return _last + ", " + _first; } }
}

And then add that grammar to a Fixture class like this:


public TablesFixture()
{
    Title = "Using Table Grammars";

    // I'm adding the decision table programmatically
    this["decisions"] = new Decisions();
}

The html rendering of the grammar above will be:

Things to note:

  1. All public properties are Cell's in the grammar. Fields are not supported
  2. Properties with a getter are considered to be expectations
  3. Properties with a setter only are considered to be inputs
  4. There are two virtual methods beforeLine() and afterLine() that can be implemented to perform actions before or after every single row is executed