Geeks With Blogs

News

My Blog

I was recently having a discussion with @tyarmer and @g0t4 about what is necessary to sufficently test a method that determines the previous business day. There are quite a few unique scenarios that can come up. Here are a few:

  • On a weekend, you should get Friday, unless it’s a holiday.
  • On a Monday you should get Friday, again unless it’s a holiday
  • If a day is holiday you should get the day before, unless that’s a holiday and a weekend
  • And so on… Obviously there’s a bit of a pattern here

I suggested that if we used a recursive implementation, such as follows, it would only be necessary to test a Saturday, Sunday, Monday, and the day before a holiday—one test per code path. @g0t4 countered that our tests should be independent of the implementation. Also we should at least smoke screen a more complex scenario.

public DateTime GetPreviousBusinessDay(DateTime date)
{
	var previousDay = date.AddDays(-1);
	if (previousDay.DayOfWeek == DayOfWeek.Saturday || previousDay.DayOfWeek == DayOfWeek.Sunday || IsHoliday(previousDay))
	{
		return GetPreviousBusinessDay(previousDay);
	}
	return previousDay;
}

I agree with that assessment. We then decided to see how my tests would compare to an iterative implementation of the function.

public DateTime GetPreviousBusinessDay(DateTime date)
{
	var previousDay = date.AddDays(-1);
	while(previousDay.DayOfWeek == DayOfWeek.Saturday || previousDay.DayOfWeek == DayOfWeek.Sunday || IsHoliday(previousDay))
	{
		previousDay = previousDay.AddDays(-1);
	}
	return previousDay;
}

Alright, so far so good. Looks like we’d still have 100% code coverage. However, in comparing the two methods we realized that there are actually two operations going on in both these implementations. The first operation is really just iteration over a decending list of days. The second is checking whether or not that day is a business day. Of course at this point we couldn’t help but decide that we needed to test these operations separately. Thus we decided to generate an enumeration (calculated on demand) for the days and have a separate function that determines if it’s a business day. So now we have:

public class HistoricalDates
{
	public static IEnumerable<DateTime> StartingFrom(DateTime start)
	{
		yield return start.AddDays(-1);
	}
}

public class BusinessDates
{
	public DateTime GetPreviousBusinessDay(DateTime date)
	{
		return HistoricalDates.StartingFrom(date).Where(IsBusinessDay).First();
	}

	public bool IsBusinessDay(DateTime date)
	{
		return !(date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday || IsHoliday(date));
	}

	public bool IsHoliday(DateTime date)
	{
		// ....
	}
}

Clearly, quite a bit more code, but we’ve managed to separate our concerns. Another cool thing about this code is that it is now very simple to expand on the intial method. Say you needed a method to determine three business days ago. Now we can leverage Linq to manipulate our “collection”.

public DateTime GetThreeBusinessDaysAgo(DateTime date)
{
	return HistoricalDates.StartingFrom(date).Where(IsBusinessDay).Skip(2).First();
}

You could also use the Take() method to generate a series of days matching your condition.

public IEnumerable<DateTime>GetFivePreviousBusinessDays(DateTime date)
{
	return HistoricalDates.StartingFrom(date).Where(IsBusinessDay).Take(5);
}

I think this is amazingly simple. Doing either of these types of calculations with the iterative or recursive implementations would have taken much more work and be much more likely to have a bug. By thinking with collections we were able to turn our somewhat complicated scenarios into simple Linq operations. Now that I’ve seen how simple this was I’m going to be watching out for more scenarios that could benefit from being broken into an enumerator and a condition.

Finally, here is our current implementation as expanded to include enumerating in both directions.

public class Dates
{
	public static IEnumerable<DateTime> BackwardsFrom(DateTime start)
	{
		yield return start.AddDays(-1);
	}

	public static IEnumerable<DateTime> ForwardsFrom(DateTime start)
	{
		yield return start.AddDays(1)
	}
}
Posted on Wednesday, February 10, 2010 1:33 PM | Back to top


Comments on this post: Thinking With Collections

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Ryan Ohs | Powered by: GeeksWithBlogs.net