0

I want to test the following two (unrelated) methods and achieve full branch and statement coverage using OpenCover 2.0.802.1

public class Methods
{
    public static void MethodWithDelegate(SynchronizationContext context)
    {
        context.Send(delegate { Console.Beep(); }, null);
    }

    public static string MethodWithSwitchStatement(Type value)
    {
        string output = string.Empty;

        if (value != null)
        {
            switch (value.ToString())
            {
                case "System.Int32":
                    output = "int";
                    break;
                default:
                    output = "other type";
                    break;
            }
        }

        return output;
    }
}

I have written the following (NUnit) tests, one using a 'Moq' mock object:

[Test]
public void Test_ShouldCoverMethodWithDelegate()
{
    var context = new Mock<SynchronizationContext>();

    Methods.MethodWithDelegate(context.Object);

    context.Verify(x => x.Send(It.IsAny<SendOrPostCallback>(), It.IsAny<object>()));
}

[Test]
public void Test_ShouldCoverSwitchStatement()
{
    Assert.That(Methods.MethodWithSwitchStatement(null), Is.EqualTo(string.Empty));
    Assert.That(Methods.MethodWithSwitchStatement(typeof(int)), Is.EqualTo("int"));
    Assert.That(Methods.MethodWithSwitchStatement(typeof(float)), Is.EqualTo("other type"));
}

However, after running the tests through OpenCover, the coverage.xml file always contains a branch point with a zero visit count for both tests. The sequence coverage shows 100%.

Not being an IL expert, I'm not sure how I'd go about writing further tests to get the branch coverage to 100%.

g t
  • 7,287
  • 7
  • 50
  • 85

1 Answers1

3

Okay first lets look at the first method in IL (I am using IL SPY)

.method public hidebysig static 
void MethodWithDelegate (
    class [mscorlib]System.Threading.SynchronizationContext context
) cil managed 
{
// Method begins at RVA 0x2059
// Code size 41 (0x29)
.maxstack 8

IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldsfld class [mscorlib]System.Threading.SendOrPostCallback so8254847.Methods::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0007: brtrue.s IL_001c

IL_0009: ldnull
IL_000a: ldftn void so8254847.Methods::'<MethodWithDelegate>b__0'(object)
IL_0010: newobj instance void [mscorlib]System.Threading.SendOrPostCallback::.ctor(object, native int)
IL_0015: stsfld class [mscorlib]System.Threading.SendOrPostCallback so8254847.Methods::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_001a: br.s IL_001c

IL_001c: ldsfld class [mscorlib]System.Threading.SendOrPostCallback so8254847.Methods::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0021: ldnull
IL_0022: callvirt instance void [mscorlib]System.Threading.SynchronizationContext::Send(class [mscorlib]System.Threading.SendOrPostCallback, object)
IL_0027: nop
IL_0028: ret
} // end of method Methods::MethodWithDelegate

As you can see there is a conditional branch at IL_0007 which only gets executed if the cached anonymous delegate has been set otherwise it goes through the main code of setting up your delegate and then calling it.

SOLUTION: Run the test twice - or forget about it as it is a bit of .NET optimization

Now for the 2nd problem this time it is better to see what was actually produced in C# - you wrote switch statements but the compiler has used ifs instead

public static string MethodWithSwitchStatement(Type value)
{
    string output = string.Empty;
    if (value != null)
    {
        string a;
        if ((a = value.ToString()) != null && a == "System.Int32")
        {
            output = "int";
        }
        else
        {
            output = "other type";
        }
    }
    return output;
}

As you can see the compiler has introduced an if null test with the switch statement but because you already have this already then it will never be executed.

SOLUTION: remove your initial if null test (as it is not needed).

Shaun Wilde
  • 8,228
  • 4
  • 36
  • 56
  • Interesting... Thanks a lot for your answer - and OpenCover too, it's great! – g t Nov 25 '11 at 09:55
  • you're welcome - I've also done an article about sequence coverage as well that you may find useful http://scubamunki.blogspot.com/2011/08/problem-with-sequence-coverage.html – Shaun Wilde Nov 25 '11 at 11:33
  • Nice article, it's a shame that ReportGenerator doesn't (yet) support display of branch coverage fully. It was only whilst browsing through the coverage.xml file I found the 'problem' I raised above. – g t Nov 25 '11 at 12:21
  • 1
    It's a hard thing to display correctly and sometimes, as in your first example, something not really to put too much pressure over when doing TDD (if that is your thing). Being able to use IL SPY helps interpretation that simply applying branch coverage over C#/VB.NET will not help. You think the above was unusual - have a look at what yield does :). – Shaun Wilde Nov 25 '11 at 13:20