Home > other >  Speed up unit tests by not building the context message unless the assertion fails
Speed up unit tests by not building the context message unless the assertion fails

Time:06-10

I have a suite of unit tests that are running quite slowly because they all do something like this:

Assert.AreEqual(expected, actual, message: getDetailedErrorContext(expected, Actual));

when profiling unit tests, most time is spent generating these detailed error context messages that never end up getting used, because tests are passing.

Does Microsoft.VisualStudio.TestTools.UnitTesting inherently support lazily-generated error messages? I noticed all test methods have an overload e.g.:

Assert.AreEqual(expected, actual, message, params object[] parameters);

Which lets me pass objects into a format string, but I'm not clear on how I can use that when e.g. the message is generated as something like

Assert.AreEqual(expected.Count, actual.Count, "All the items in the list are: "  
    String.Join(", ", actual.Select(o => $"[{o.Id}: {o.Description}]"));

Is there some existing class I can use that will evaluate to my desired message if (and only if) the test assertion fails? Ideally I'd like to have a solution like:

Assert.AreEqual(expected, actual, () => getDetailedErrorContext(expected, Actual));

Where the lambda is never evaluated if the assertion doesn't fail.


Note, I've considered a solution like replacing all assertion methods with e.g.:

if (expected != actual)
    Assert.Fail(getDetailedErrorContext(expected, Actual));

But I don't like this for a few reasons (such as not wanting to replicate things like Assert.AreEqual<double>('s build-in delta tolerance, and overall difficulty this would create in refactoring our test suite.)

CodePudding user response:

So far, for lack of a more obvious solution, I've created the following wrapper around Lazy<T> whose only difference is that it will actually resolve the value when ToString() is called:

public class LazyString : Lazy<string>
{
    public LazyString(Func<string> stringBuilder) : base(stringBuilder) { }
    public override string ToString() => Value;
}

I can then use this as:

Assert.AreEqual(expected, actual, "{0}", new LazyString(() =>
    getDetailedErrorContext(expected, Actual)));

It's a bit ugly and error prone (if one forgets to insert the format string "{0}", but it works and is the best I could come up. Sadly the Assert class is sealed, so I can't even cleanly extend it with my own overloads.

  • Related