Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Using Multiple Asserts in One Test Considered Helpful

4.08/5 (4 votes)
1 Mar 2009CPOL3 min read 37.8K   48  
One TDD mantra is that there should be only one assert per test. Here is a finance/trading example where that doesn't work very well.

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:

C#
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 nulls before accessing the Quantity property in the above example).

C#
ExpectFundAllocations(allocations, @"
    FUND1   75
    FUND2   15
    FUND3   10
");

Here is the code for the helper method ExpectFundAllocations:

C#
public void ExpectFundAllocations(Allocations allocs, string matchLinesOneString)
{
	//get values - word, whitespace, possibly negative number
	Regex regex = new Regex(@"^(\w+)\s+(-?\d+)$");

	string[] matchLines = Regex.Split(Environment.NewLine, matchLinesOneString);

	foreach (string inputRaw in matchLines)
	{
		//trim, skip blank lines
		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:

C#
[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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)