Chapter 5. Writing simple test scripts

Tests are rarely as simple as the one described in Chapter 4, Writing basic tests. Generally, they involve several steps and verifications. FitNesse allows us to write multi-step test scripts as easily as simple verifications. In this chapter we develop the next story, and learn how to write test scripts and pass values between tables. Our task for this chapter is to implement the second user story:

Register player

As an operator, I want players to register and open accounts before purchasing tickets, so that I have their details for marketing purposes and to prevent fraud.

Again, we speak to our business analysts about what to test and how to verify that the story has been implemented correctly. The first thing they say is that, upon successful registration, personal details should be stored correctly in the system and the player should be able to log in with their registered username and password. Also, the balance for new accounts in the system should always be zero.

[Important]Focus on fixture code

To keep things simple, we will focus on test fixtures and test-specific code in this and the following chapters. Business classes will be discussed just enough to support the story. You can see the implementations of these classes in Appendix D, Source code. You can also download them from http://gojko.net/fitnesse.

To implement the story, we need to allow players to register and log in. Each player will have a unique numeric ID, which should be returned from the registration method (let's call it RegisterPlayer) if the operation is successful. Let's create a data-transfer interface where we will store all the personal details of the player required for the registration — we will call it IPlayerRegistrationInfo. The LogIn method should return the corresponding player ID, if the username and password are correct. If not, an exception is thrown. We also need a way to retrieve player details to verify that they are stored correctly.

So, let's create a PlayerManager class, responsible for managing players in our system. To begin with, we give it this API:

Tristan/src/IPlayerManager.cs


30      int RegisterPlayer(IPlayerRegistrationInfo p);
31      IPlayerInfo GetPlayer(int id);
32      IPlayerInfo GetPlayer(String username);
33      int LogIn(String username, String password);

Passing values between tables

The test that we need to write for this chapter actually involves a few stages:

  1. Register a new player.

  2. Check that user details were stored correctly and that the balance on the new account is 0.

  3. Try to log in with the username and password provided during registration.

Although everything could be described by one (huge) table, the resulting test page would be completely unreadable, which defeats the whole point of using FitNesse. We can put more than one table on a single page and they will be executed in sequence. This allows us to create a test script that describes the steps with small and focused tables.

Use setup fixtures to store static context

Once we divide the test into several tables, we have to handle issues associated with their interdependence. The first one is that the tables must work with the same PlayerManager instance. A typical solution for sharing this kind of information between tables is to use a separate fixture to set up the test environment.

This setup fixture stores the contextual information into static properties. (There is also a standard fixture class called SetUpFixture, which will be explained in Use SetUpFixture to prepare the stage for tests . In this case, we are talking about generic setup fixtures that prepare the stage for other fixtures, not necessarily of any particular class). Let's call this class SetUpTestEnvironment.

Tristan/test/PlayerRegistration.cs


6     public class SetUpTestEnvironment : Fixture
7     {
8       internal static IPlayerManager playerManager;
9       public SetUpTestEnvironment()
10      {
11        playerManager = new PlayerManager();
12      }
13    }

This class would typically be responsible for initialising service objects, connecting to the database, and anything else our test environment requires to run correctly. In the example we extend fit.Fixture directly, because no parameters are being passed to the class. When you need to pass connection strings or other setup information, you can use any other fixture class. ColumnFixture is a good candidate.

Use symbols to pass dynamic information

Static values and singletons[14] are fine for storing resources that we can anticipate while writing the FIT integration class, like database connections and service objects. However, tables in the same script often have to share dynamic information. By dynamic, I mean values created on the fly in the tests. For example, we might write a table that registers a new player, and another one that verifies that the player data was stored correctly. The second table needs to know the ID of the player created by the first table. If we were to use a static variable for this, the two test tables would be coupled quite strongly. As soon as we need to perform checks on two players, we would have to modify the registration test class code and add another static variable, making the verification class even more complex, as it would need a switch to indicate which variable to use as the ID.

A better solution for passing dynamic information between tables is to use FitNesse symbols. Symbols are global variables that can be accessed using a simple syntax. To store a value of an output column into a symbol named player, write >>player into the cell. To read a symbol value, and store it into an input column, use <<player. Think of << and >> as arrows pointing the way.

[Tip]What is the scope of a symbol?

Symbols are stored in a static collection inside the Fixture class, so their scope is global from the point where they are defined until the test runner stops. So far, we have only executed a single page at a time, so the scope of a symbol is from its first use until the end of the current page. You will learn how to run multiple pages within a single test runner in Group related tests into test suites. A symbol scope will effectively extend to all pages executed after the page where it is defined. However, do not count on sharing symbols between pages because the order of page execution is not guaranteed — it is best to define and use the symbol on the same page.

[Note]Stuff to remember
  • If a page contains several tables, FitNesse executes them in a sequence.

  • Symbols are global variables in FitNesse. You can use them to pass dynamic values between tables and connect interdependent tests.

  • The current symbol value is shown next to the symbols in test results.

  • Use GetTargetObject to bind test tables directly to your business class.

  • Use Fixture.Recall to read a symbol value from your code.

  • Use the error keyword to verify that an exception is thrown. Use the exception keyword to check for a particular exception code or message.



[14] A design pattern that restricts classes to only one instance, see http://en.wikipedia.org/wiki/Singleton_pattern