2
\$\begingroup\$

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:

  1. be short and easy to use
  2. be configurable

And I have two concerns about the class as is:

  1. Am I rewriting some existing functionality (ie, does Trace, Debug, NUnit, log4net, etc already do this?
  2. 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));
    }
}
\$\endgroup\$
2
\$\begingroup\$

I use log4net in my test code along with the ObjectPrinter library and the log4net ConsoleAppender.

When I'm actively working on a test where I want to see additional output I just crank up my logging level in my log4net config file so I can see the additional debug information in my ReSharper test runner console window.

private static readonly ILog Log = LogManager.GetLogger(typeof (MyTestFixture));

[TestCase("US1")]
[TestCase("123")]
[TestCase("E$R")]
public void Get_From_IsoAlphaCode_IfThreeLetterCodeIsNotAllAlpha_Error(string val)
{
    var ex = Assert.Throws<IsoAlphabeticCodeFormatException>(() => Currency.Get(val));
    Log.Debug(ex); // <-- only prints when log4net is configured to log DEBUG msgs
    Assert.That(ex.Message, Is.StringStarting(IsoAlphabeticCodeFormatException.Not3AlphaCharsMessage));
    Assert.That(ex.ParamName, Is.StringMatching("isoAlphaCode"));
    Assert.That(ex.ActualValue, Is.EqualTo(val));
}

ObjectPrinter isn't required, but it outputs more information into the log by reflecting over the passed in objects public properties. The Log.Debug statement outputs something like this in the console window.

2012-01-01 19:38:26,362 [7] DEBUG NUnitTestHarness1.MyTestFixture [IsoAlphabeticCodeFormatException]: hashcode { 11076195 }
{
    Message : Badly formed ISO 4217 Aplhabetic Code.
    StackTrace :    at Currency.Get(String val) in d:\source\spikes\NUnitTestHarness1\NUnitTestHarness1\Class1.cs:line 31
           at NUnitTestHarness1.MyTestFixture.c__DisplayClass1.b__0() in d:\source\spikes\NUnitTestHarness1\NUnitTestHarness1\Class1.cs:line 18
           at NUnit.Framework.Assert.Throws(IResolveConstraint expression, TestDelegate code, String message, Object[] args)
    Source : NUnitTestHarness1
    TargetSite : Int32 Get(System.String)
    HelpLink : {NULL}
    ParamName : {NULL}
    ActualValue : {NULL}
    Data : {}
    InnerException : {NULL}
}
\$\endgroup\$
  • \$\begingroup\$ Can you please show me a sample usage to that would be the equivalent of my 'exception.Look()' extension? I want to decide if it is worth spending some quality time with log4net and learning the ObjectPrinter library. Cheers \$\endgroup\$ – Berryl Jan 1 '12 at 22:19
1
\$\begingroup\$

I like Sneal's suggestion lots and I may go that route at some point.

For now, I just don't have time or inclination to learn the ObjectPrnter library or rewrite existing code that uses; so the path of least resistance is just to use a VisualStudio Setting.

It's easy, provides a single point of configuration change, doesn't require a build.

Cheers,
Berryl

changes in UnitTestLoggingExtensions

#region Configuration

public enum VerbosityLevel
{
    OFF = 0,
    VERBOSE,
    TERSE,
}

public static readonly VerbosityLevel Verbosity;

static TestingOutputExtensions() { 
    Verbosity = (VerbosityLevel) Properties.Settings.Default.UnitTestOutputVerbosityLevel; }

#endregion
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.