Как получить полное покрытие ветки OpenCover для этих примеров кода? - PullRequest
0 голосов
/ 24 ноября 2011

Я хочу протестировать следующие два (не связанных) метода и добиться полного охвата ветвлений и операторов с помощью 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;
    }
}

Я написал следующие (NUnit) тесты, один из которых использует фиктивный объект Moq:

[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"));
}

Однако после запуска тестов через OpenCover файл coverage.xml всегда содержит точку ветвления с нулевым числом посещений для обоих тестов. Последовательность покрытия показывает 100%.

Не будучи экспертом по IL, я не уверен, как мне написать дополнительные тесты, чтобы охватить филиал до 100%.

1 Ответ

2 голосов
/ 25 ноября 2011

Хорошо, давайте сначала посмотрим на первый метод в IL (я использую 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

Как вы можете видеть, в IL_0007 есть условная ветвь, которая только выполняетсяесли кешированный анонимный делегат был установлен в противном случае, он проходит через основной код настройки вашего делегата, а затем вызывает его.

РЕШЕНИЕ: дважды запустите тест - или забудьте об этом, так как он немного оптимизирован для .NET

Теперь для второй задачи на этот раз лучше посмотреть, что на самом деле было произведено в C #- вы написали операторы switch, но вместо этого компилятор использовал ifs

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;
}

Как вы можете видеть, компилятор ввел тест if null с оператором switch, но поскольку он уже есть, он никогда не будет выполнен..

РЕШЕНИЕ: удалите исходный тест null (так как он не нужен).

...