Chapter 4. Writing basic tests

In Chapter 2, Installing FitNesse, you had a brief introduction to FitNesse tests. Now is the time to take a deeper look at the bridge between test and domain code. In this chapter you also learn why FitNesse tests are more understandable than unit tests. Our task for this chapter is to implement the first user story:

Calculate expected winnings

As an operator, I want the system to display expected winnings, so that players will be enticed to buy tickets.

To implement the story, we will write a WinningsCalculator class, which will be responsible for calculating expected winnings for a given draw pool value.

Instead of writing the WinningsCalculator immediately, we take a step back. Remember Guiding the development? The first step of implementation is to decide how we are going to verify that the result behaves correctly. Just to make sure you did not jump over this idea, repeat out loud: The first step of implementation is to decide how we are going to test the result! And we need to agree on that with someone from the business side, to be sure that the result is really what they want. So, after a short discussion with our business analysts, we decide that the best way to test the winnings calculator is to take the results from the last month's draw (Figure 4.1, “Last month's results”), put the pool size into the calculator and check whether the numbers match.

Test-driven development by the book advocates writing the test before we actually write the production code whenever possible. The test then serves as a target for development. So, let's write just enough of the WinningsCalculator class to be able to compile the test. We need to test two things: calculating the pool percentage, and calculating the prize pool.

Figure 4.1. Last month's results

Winning combinationPool allocationValue

Total pool

100%

$4,000,000

Payout pool (PDP)

50%

$2,000,000

6 out of 6

68% of the PDP

$1,360,000

5 out of 6

10% of the PDP

$200,000

4 out of 6

10% of the PDP

$200,000

3 out of 6

12% of the PDP

$240,000


[Important]Write the FitNesse page before you write code

In order to explain the syntax of various FitNesse tables in this book, we typically write the fixture code first and then look at how that code maps to FitNesse tables. But once you learn what those fixtures can do for you, it is actually much better to create the FitNesse page first and let that lead you while writing the fixture class and changing the underlying business interfaces.

Writing the FitNesse test page first will allow you to make sure that programmers and customers have the same understanding of the problem. Keep working on the FitNesse pages until both you and the customer think that you have sufficient examples to start programming.

Tristan/src/InitialWinningsCalculator.cs


1   namespace Tristan 
2   {
3     public class WinningsCalculator 
4     {
5       public int GetPoolPercentage(int combination) 
6       {
7         throw new Exception("Not implemented");
8       }
9       public decimal GetPrizePool(int combination, decimal payoutPool) 
10      {
11        throw new Exception("Not implemented");
12      }
13    }
14  }

ColumnFixture — the Swiss Army knife of FitNesse

The A quick test touched upon the subject of the thin integration layer built on top of business code that FIT requires. This is the integration class fit.Fixture, which provides hooks to relevant properties and methods of business objects and tells FIT how to run the test. Although fit.Fixture is always the base class for all integration classes, it does not specify how to run the test. Instead, we typically extend a subclass of Fixture for our tests. In the rest of the book, we'll call such subclasses fixtures. There are many ready-made fixtures in the basic FIT package (fit.dll) and the popular extension FitLibrary[13] (fitlibrary.dll). Also, you can develop your own fixtures to extend the functionality of FitNesse (see Implement domain-specific tests using custom fixtures) so there are quite a few candidates to choose from. Which fixture class should we use in this case?

To test the WinningsCalculator class, we need to check that the allocated percentage of the payout pool and prize value are correct for all winning combinations (and a given value of the payout pool). If the test is a calculation, described in the form of “check that results are correct for given inputs” and there are a few known inputs to try out, we should use ColumnFixture as the base for the integration class.

The fixture class should allow us to define the total value of the payout pool and the winning combinations, and check allocated percentages and prize pool values. So let's create two properties for the inputs and two methods to calculate results:

[Tip]What else can I use ColumnFixture for?

The ColumnFixture class can be used to perform almost any test. It can also be used to set up data for other tests and execute methods to clean up after the tests. In fact, ColumnFixture is so easy to understand and use and can be used in so many situations, that it is like a Swiss Army knife for FitNesse tests.

ColumnFixture is a good choice if the same tests should be repeated for a specified number of different combinations of input parameter values. When you don't know the number of tests in advance, or there is only one check to perform, there are better solutions that can save you a lot of time and effort. These solutions are described in later chapters.

Tristan/test/PayoutTable.cs


1   namespace Tristan.Test 
2   {
3     public class PayoutTable:fit.ColumnFixture 
4     {
5       private WinningsCalculator wc=new WinningsCalculator();    
6       public int winningCombination;
7       public decimal payoutPool;
8       public int PoolPercentage() 
9       {
10        return wc.GetPoolPercentage(winningCombination);
11      }
12      public decimal PrizePool() 
13      {
14        return wc.GetPrizePool(winningCombination, payoutPool);
15      }
16    }
17  }

Now we write the test page. Add a link to PrizeCalculation from the home page (as explained in Don't forget the test), then click this link and create a new page. Add the three setup lines, described in How FitNesse connects to .NET classes, defining the test runner and location of the project DLL. Make sure to enter the correct path to DLLs on your system; it may differ from the one in this book.

[Tip]Wait a moment... are those public fields?

Yes! It is bad practice to expose public fields in API classes, but PayoutTable class is not a part of the API; it will be used just for testing.

Using public fields in test classes makes them easier to write and read. We often use public properties in test classes in this book, to keep them short. Anyway, if you are picky about this issue, go ahead and implement them as properties or setter methods, FitNesse will not care.

The table for ColumnFixture tests has at least three rows: the first row specifies the fixture class name, the second names input and output methods and properties, and the following rows specify test data and expected results. In this case, payoutPool and winningCombination are inputs, and methods PoolPercentage and PrizePool calculate output values. (Fields and properties with a getter can also be used for test outputs.) To differentiate between inputs and outputs, PoolPercentage and PrizePool end with a question mark in the second row. Parentheses () can also be used to specify outputs, but to keep things consistent when properties are used for outputs, and to make tables easier to read, I recommend that you just use the question mark. Rows after the second row just contain last month's draw results. Here is the table that you should copy into the page:

PrizeCalculationFirstTry


4   !|Tristan.Test.PayoutTable|
5   |payoutPool|winningCombination|PoolPercentage?|PrizePool?|
6   |2000000|6|68|1360000|
7   |2000000|5|10|200000|
8   |2000000|4|10|200000|
9   |2000000|3|12|240000|

Save the page, then tell FitNesse that this is a test page (using page properties). You can run the test by clicking Test. Because we have not yet written the whole WinningsCalculator class, the test fails. Now that we have a clear understanding of what the class should do, let's write it to satisfy the test.

Tristan/src/WinningsCalculator.cs


1   namespace Tristan 
2   {
3     public class WinningsCalculator 
4     {
5       public int GetPoolPercentage(int combination) 
6       {
7         switch(combination) {
8           case 6: return 68;
9           case 5: return 10;
10          case 4: return 10;
11          case 3: return 12;
12          default: return 0;
13        }
14      }
15      public decimal GetPrizePool(int combination, decimal payoutPool) 
16      {
17        return payoutPool * GetPoolPercentage(combination) / 100;
18      }
19    }
20  }

Recompile the project, run the test again, and it passes (Figure 4.2, “Winnings Calculator works!”).

Figure 4.2. Winnings Calculator works!

Winnings Calculator works!

[Note]Stuff to remember
  • Write FitNesse pages first, and use them as a target for the production code.

  • Use ColumnFixture when you want to repeat the same check for several different combinations of input values.

  • The ColumnFixture class maps properties, methods and fields to table columns.

  • You specify that a column contains expected results by ending the column name (in the second row) with a question mark.

  • A table can have more than one column for expected results, enabling us to perform several verifications for the same combination of input values in a single table row.

  • FitNesse allows you to use names that are easy to read. It finds the correct .NET equivalent by joining words and ignoring character case.

  • Any text outside of tables is just ignored, so you can provide explanations and comments along with your tests and make them easier to verify and understand.

  • If you want non-technical people to understand and verify tests, then you should make sure the test tables are as close as possible to their natural language.



[13] FitLibrary is a set of extensions developed by Rick Mugridge, now considered part of the standard set of fixtures, although it is technically a separate library. The FitSharp package already contains the FitLibrary, so you do not have to download it separately. We use FitLibrary fixtures in Chapter 6, Writing efficient test scripts and Chapter 8, Coordinating fixtures.