Chapter 8. Coordinating fixtures

In Chapter 6, Writing efficient test scripts, we saw how we can write tests almost in plain English with DoFixture. However, for some test steps it is better to use a more compact format, especially when actions or data have a repetitive structure. Luckily, we can mix and match with DoFixture: use story-like rows when this makes sense, and use other fixtures when we need a more compact structure. Let's see how DoFixture can embed other fixtures.

In this chapter, we'll implement the following user story:

Pay out winnings

As an operator, I want the system to find winning tickets, calculate winnings and pay money into ticket holders' accounts when I enter draw results.

Just as with previous stories, our first concern is how to test that the system works as expected. Our business analysts come up with a few ideas:

  • Let's put four tickets in a draw, each worth 50 dollars, for completely different numbers. Then let's enter draw results that match all six numbers on one of the tickets. Other tickets do not match any drawn numbers. The owner of the winning ticket should take 68% of the payout pool. The total pool value is 200 dollars and the operator takes 50%, which leaves 100 for prizes. The prize for six winning numbers is 68 dollars. So the owner of this ticket should have 18 dollars more than he had before (68 in prize money minus 50 paid for the ticket). The other three players should have 50 dollars less than they started with.

  • Let's register four tickets as before, but have two tickets with four common numbers, and then draw these numbers along with two others not appearing on any ticket. The owners of two winning tickets should split 10% of the payout pool in proportion to their ticket values. To test the proportional split, let's have one ticket at 80 dollars and the other at 20 dollars, so the prize should be split 4/1.

The first thing that these tests signal is that tickets can have different values. Luckily, our ITicket interface already provides this, so we don't have to refactor. Also, the test scripts hint that some parts of the tests need a story-based structure (draw results) and other parts need a repetitive structure (buy multiple tickets, check winnings for each ticket). So we have to combine what we learned in the previous two chapters.

Embed fixtures for best results

DoFixture allows us to embed other fixtures into tests. To embed fixtures, we first need to split one big table into several smaller ones. DoFixture allows this with a feature called flow: if the first test class on a page is a subclass of DoFixture, it takes over the whole page, and allows us to split the rows into individual tables.

When the page is in flow mode, the test rows are first matched to flow fixture methods. If no corresponding method exists, then a fixture is created normally, taking the class name from the first row. So, we can keep using tables for test steps where needed. If a method of a flow test returns a Fixture instance, the rest of the current table is then analysed as if it was specified for this fixture. So we can use a DoFixture method to prepare a ColumnFixture for execution. In flow mode we can embed and reuse fixtures without depending on them to read the context. This is the FitNesse version of dependency injection,[21] which makes writing and combining fixtures much easier.

Flow scripts are much easier to read, as the first row of a table does not have to be devoted to specifying the class name. Also, flow mode allows us to define and store the context of a test script in private variables of a test fixture, rather than static global variables.

Settlement tests in flow mode

We'll use a DoFixture in flow mode for the settlement test script. This fixture provides context to other fixtures. We'll need to give other fixtures a reference to a player manager and a draw manager, so that they can work on the same accounts and tickets. In addition, we'll need to open a draw in order to register tickets. Because draw details are not really important for this test, let's just create a draw in the background, without requiring anything to be done explicitly in the test. This is an example of simplifying tests as explained in Remove irrelevant information. Our flow fixture initialises these “service objects” and other fixtures use them later:

Tristan/test/Settlement.cs


62    public class SettlementTest:DoFixture
63    {
64      private IDrawManager drawManager;
65      private IPlayerManager playerManager;
66      private DateTime drawDate;   
67      public SettlementTest()
68      {
69        playerManager = new PlayerManager();
70        drawManager = new DrawManager(playerManager);
71        drawDate = DateTime.Now;
72        drawManager.CreateDraw(drawDate);
73      }
90    }

To start a test in flow mode, create a table for the flow fixture class at the top of the page. This table typically contains just the class name. Because this must be the first table on the page, the class name must be fully qualified (with the namespace). We cannot even use the import directive before a flow table.

SettlementTests.SetUp


1   !|Tristan.Test.Settlement.SettlementTest|

If this first table, which just holds the test name, starts confusing your non-technical users or customers, put a short test description after the initial setup (use !3 before it to create a third-level heading). Then tell customers to ignore everything above the heading and focus on the part below the title.

[Note]Stuff to remember
  • If DoFixture is the first table on the page, it takes over page processing (flow mode), and allows rows to be separated into different tables.

  • DoFixture in flow mode can embed other fixtures for easier testing and better re-use. Just return the Fixture from a test method and then use the rest of the current table as if it described a test for this fixture.

  • In flow mode, DoFixture can initialise embedded fixtures, allowing you to use private instance variables for test script context instead of global static variables.

  • SetUpFixture is cleaner than ColumnFixture for preparing test data.

  • To write test scripts in flow mode, put the common part of the test script into a SetUp page.

  • A flow mode test must begin with the flow fixture class name. Not even import can be used before the test class name in flow mode.



[21] A software pattern in which service references and configuration are passed to the object by the framework, without any code in the object to request or locate the services and configuration. This pattern leads to loose coupling and objects that are much easier to test and combine. Objects using this pattern also have less code because they are not responsible for reaching out to services or reading the configuration.