Below is a set of extensions that I use in unit tests.
The basic idea is that when creating or modifying some sort of user output type of string, I want to eyeball the output to see that it makes sense, even though it might be 'correct' in a unit test sense. But when doing almost any other tests and certainly when regression testing, I don't want the added noise or time it takes to do so.
For example:
[TestCase("US1")]
[TestCase("123")]
[TestCase("E$R")]
public void Get_From_IsoAlphaCode_IfThreeLetterCodeIsNotAllAlpha_Error(string val)
{
var ex = Assert.Throws<IsoAlphabeticCodeFormatException>(() => Currency.Get(val));
ex.Look(); // *** eyeball the whole exception using extension ***
Assert.That(ex.Message, Is.StringStarting(IsoAlphabeticCodeFormatException.Not3AlphaCharsMessage));
Assert.That(ex.ParamName, Is.StringMatching("isoAlphaCode"));
Assert.That(ex.ActualValue, Is.EqualTo(val));
}
// OUTPUT
IsoAlphabeticCodeFormatException: Badly formed ISO 4217 Aplhabetic Code.
The passed code was not three (3) alphabetic characters.
Parameter name: isoAlphaCode
Actual value was bubba.
at Support.Guard.AgainstBadIsoAlphaCodeFormat(String paramName, String value) in C:\Support\Guard.cs:line 26
at Currency.Get(String isoAlphaCode) in C:\Currency.cs:line 731
at Tests.Currencies.CurrencyTests.<>c__DisplayClass6.<Get_From_IsoAlphaCode_IfThreeLetterCodeIsWrongSize_Error>b__5() in C:.Tests\Currencies\CurrencyTests.CacheFetching.cs:line 95
at NUnit.Framework.Assert.Throws(IResolveConstraint expression, TestDelegate code, String message, Object[] args)
21 passed, 0 failed, 0 skipped, took 4.71 seconds (NUnit 2.5.10).
So, the design goals here are:
- be short and easy to use
- be configurable
And I have two concerns about the class as is:
- Am I rewriting some existing functionality (ie, does Trace, Debug, NUnit, log4net, etc already do this?
- What is a better way to configure the verbosity level?
Here's my code:
/// <summary>
/// Helpers to 'eyeball' artifacts of a unit test
/// </summary>
public static class UnitTestLoggingExtensions
{
private enum OutputOption { OFF, VERBOSE, TERSE, }
private static OutputOption option = OutputOption.TERSE;
/// <summary>Writes the passed string and any passed format arguments.</summary>
public static void Look(this string msg, params object[] args)
{
if (msg == null) {
Debug.Write("");
return;
}
switch (option)
{
case OutputOption.OFF:
break;
case OutputOption.VERBOSE:
Debug.WriteLine(_createVerboseMessage(msg, args), "INFO");
break;
case OutputOption.TERSE:
Debug.WriteLine(string.Format(msg, args), "INFO");
break;
default:
throw new ArgumentOutOfRangeException();
}
}
/// <summary>Writes the passed exception's message.</summary>
public static void Look(this Exception ex) { Look(ex.Message); }
/// <summary>Writes the passed object, invoking ToString().</summary>
public static void Look(this object o) { Look(o.ToString()); }
/// <summary>Writes the passed items, invoking ToString().</summary>
public static void Look<T>(this IEnumerable<T> items)
{
foreach (var o in items)
{
o.Look();
}
}
/// <summary>Writes the passed items along with a header.</summary>
public static void Look<T>(this IEnumerable<T> items, string header)
{
header.Look();
items.Look();
}
private static string _createVerboseMessage(string format, params object[] args)
{
return string.Format("[{0}]{1}-> {2}",
DateTime.Now.ToString("o"),
Environment.NewLine,
string.Format(format, args));
}
}