Introduction
One TDD mantra is that there should be only one "assert" per test. This is only a guideline and as a general rule, it is a pretty good one. Often times however, I find myself wanting to assert on a set of data.
So if you look at it in a certain way you could say that I want a single assert, but on a set of data, not just a single component of that data. Another way to look at is that I am in fact testing too many things in one test. I think the below method reads very well and communicates the intent of the test much better than either manually doing multiple Assert.AreEqual
calls or trying to create a bunch of tests to test each particular piece of the set of data.
Background
You should be familiar with a unit testing framework and TDD to get the most out of this article. The examples here are in C# but I think the general idea applies to any language/testing framework.
Using the Code
Let's say we wanted to test a set of data and in our test, we were checking this:
Assert.AreEqual(75, allocations["FUND1"].Quantity);
Assert.AreEqual(15, allocations["FUND2"].Quantity);
Assert.AreEqual(10, allocations["FUND3"].Quantity);
Since I haven't yet provided the context in which I'm doing this, I don't think you can yet make the determination of whether you think it is a good idea or not inside a single test. So for now, bear with me and look at the following which does the same thing (and more since you don't have to check for null
s before accessing the Quantity
property in the above example).
ExpectFundAllocations(allocations, @"
FUND1 75
FUND2 15
FUND3 10
");
Here is the code for the helper method ExpectFundAllocations
:
public void ExpectFundAllocations(Allocations allocs, string matchLinesOneString)
{
Regex regex = new Regex(@"^(\w+)\s+(-?\d+)$");
string[] matchLines = Regex.Split(Environment.NewLine, matchLinesOneString);
foreach (string inputRaw in matchLines)
{
string input = inputRaw.Trim();
if (String.IsNullOrEmpty(input)) continue;
Match match = regex.Match(input);
if (!match.Success) throw new Exception("Unable to match " + input);
string FundName = match.Groups[1].Value;
decimal Quantity = Convert.ToDecimal(match.Groups[2].Value);
Allocation alloc = allocs[FundName];
Assert.IsNotNull(alloc, "No allocation for " + FundName);
Assert.AreEqual(FundName, alloc.Fund.Name);
Assert.AreEqual(Quantity, alloc.Quantity);
}
}
The full test:
[Test]
public void TradeAllocatesEvenlyToMultipleFunds()
{
Trade trade = new Trade
{
Investment = new Investment { Symbol = "IBM" },
Type = TradeType.Buy,
FilledQuantity = 100
};
Funds fundsToAllocateTo = new Funds {
new Fund { Name = "FUND1", RelativeWeighting = .75M },
new Fund { Name = "FUND2", RelativeWeighting = .15M },
new Fund { Name = "FUND3", RelativeWeighting = .10M },
};
FundAllocationCalculator calculator =
new FundAllocationCalculator(fundsToAllocateTo);
Allocations allocations = calculator.CreateAllocationsForTrade(trade);
ExpectFundAllocations(allocations, @"
FUND1 75
FUND2 15
FUND3 10
");
}
Business Context
I don't like having arguments over abstract concepts, so I've used a very specific business example for this article, one in which I have used this method myself. Let's say you work for some form of finance company which engages in trades. In this particular example, we are buying 100 shares of IBM. This particular place you happen to work at executes those trades for multiple funds, with some of those funds being larger than others. So if you had three funds, you would not execute three trades, but would execute one and then allocate those shares to the funds based on various criteria. Here I have simplified the criteria so it is just a percentage and all the percentages for the funds add up to 100%.
The idea in the above test is that we have ratios for each of the funds and when we allocate, we want all of those to correspond to the ratio of the fund.
In this particular example, you could argue that I should be testing just one fund times the ratio equals the number of shares for that fund. But that would be a test for simple multiplication and wouldn't really communicate the business scenario I was trying to test. Also, there are additional complications such as rounding and making sure that all the shares were allocated that I am not showing here that would make testing for a single fund not very useful.
For another business example in which you need to look at the set, there could be another test in which we were disposing shares instead of acquiring them and one of the funds didn't have enough shares in that particular trade to participate at its normal fund percentage. Other funds would then pick up the extra shares since the shares have already been traded.
Points of Interest
In the above example, I am demonstrating the usage of regular expressions to test sets. The same concept could be used for the portion of the test where the fund allocations are built. It would be more readable and easier to specify for multiple tests. Likewise if we were involving the position held for the trade, we could specify that using the same method, again making it more readable.
History
- Initial version - 01-Mar-2009