代码覆盖率:为什么结束标记为红色(End If,End Try,...)

时间:2012-01-19 12:32:19

标签: vb.net visual-studio mstest code-coverage

我在Visual Studio 2010和Visual Basic中使用MS-Test。

在以下函数中,代码覆盖率告诉我,有一个未经检查的块,“结束尝试”的行为红色(请参阅http://lts.cr/BVvP):

Private Function GetLatestVersionInfoForAsync()

    Try
        Return GetLatestVersionInfo()
    Catch ex As Exception
        RaiseEvent UnhandledAsyncException(Me, New UnhandledExceptionEventArgs(ex, False))
        Return New VersionInfo() With {.ExceptionOccoured = True, .Exception = ex}
    End Try

End Function

那么,为什么这个“结束尝试”行是一个未被覆盖的(红色)块(同样在函数结尾处出现“End If”)?

我有另一个问题:是否有任何资源可以解释代码覆盖率结果中的不同颜色(蓝色很明显,但我看到黄色,深色和浅红色......)。

谢谢!

4 个答案:

答案 0 :(得分:4)

继丹尼尔关于序列点的观点之后,值得进一步研究。如果我们采用一个简单的函数重复你正在做的事情

07    Function Method() As String
08        Try
09           Return ""
10        Catch ex As Exception
11           Return ""
12       End Try
13    End Function

在Debug中,我们得到以下序列点(我正在使用OpenCover

<SequencePoints> 
  <SequencePoint offset="0" ordinal="0" uspid="261" vc="0" ec="32" el="7" sc="5" sl="7"/> 
  <SequencePoint offset="1" ordinal="1" uspid="262" vc="0" ec="12" el="8" sc="9" sl="8"/> 
  <SequencePoint offset="2" ordinal="2" uspid="263" vc="0" ec="22" el="9" sc="13" sl="9"/> 
  <SequencePoint offset="19" ordinal="3" uspid="264" vc="0" ec="30" el="10" sc="9" sl="10"/> 
  <SequencePoint offset="20" ordinal="4" uspid="265" vc="0" ec="22" el="11" sc="13" sl="11"/> 
  <SequencePoint offset="40" ordinal="5" uspid="266" vc="0" ec="16" el="12" sc="9" sl="12"/> 
  <SequencePoint offset="41" ordinal="6" uspid="267" vc="0" ec="17" el="13" sc="5" sl="13"/> 
</SequencePoints>

(其中sl =起始行,el =结束行,sc =起始列,ec =结束列,偏移量= IL偏移量十进制)

然而,当你看到IL

时,这些才有意义
.method public static 
    string Method () cil managed 
{
    // Method begins at RVA 0x272c
    // Code size 43 (0x2b)
    .maxstack 2
    .locals init (
        [0] string Method,
        [1] class [mscorlib]System.Exception ex
    )

    IL_0000: nop
    IL_0001: nop
    .try
    {
        IL_0002: ldstr ""
        IL_0007: stloc.0
        IL_0008: leave.s IL_0029

        IL_000a: leave.s IL_0028
    } // end .try
    catch [mscorlib]System.Exception
    {
        IL_000c: dup
        IL_000d: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError(class [mscorlib]System.Exception)
        IL_0012: stloc.1
        IL_0013: nop
        IL_0014: ldstr ""
        IL_0019: stloc.0
        IL_001a: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
        IL_001f: leave.s IL_0029

        IL_0021: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
        IL_0026: leave.s IL_0028
    } // end handler

    IL_0028: nop

    IL_0029: ldloc.0
    IL_002a: ret
} // end of method Module1::Method

现在您可以看到您关注的End Try行只会在偏移40(IL_0028)处按下IL指令时被标记为命中,但是当您查看IL生成时我无法看到您的身份因为产生了奇数的IL而会到达那里(leave.s是用于退出try / catch / finally块的小跳转指令)如果你按照代码你会看到你总是会到达{{ 1}}首先跳转到IL_0029。

在发布时IL更改

leave.s

序列点

也是如此
.method public static 
    string Method () cil managed 
{
    // Method begins at RVA 0x2274
    // Code size 30 (0x1e)
    .maxstack 2
    .locals init (
        [0] string Method,
        [1] class [mscorlib]System.Exception ex
    )

    .try
    {
        IL_0000: ldstr ""
        IL_0005: stloc.0
        IL_0006: leave.s IL_001c
    } // end .try
    catch [mscorlib]System.Exception
    {
        IL_0008: dup
        IL_0009: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError(class [mscorlib]System.Exception)
        IL_000e: stloc.1
        IL_000f: ldstr ""
        IL_0014: stloc.0
        IL_0015: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
        IL_001a: leave.s IL_001c
    } // end handler

    IL_001c: ldloc.0
    IL_001d: ret
} // end of method Module1::Method

因此,无论哪种方式,你都会松散,因为现在你永远不会看到标记为覆盖的尝试/捕获线

因此,让我们按照Hans的建议尝试更改代码并返回调试(因为这是您通常会运行覆盖的地方)

<SequencePoints> 
  <SequencePoint offset="0" ordinal="0" uspid="33" vc="0" ec="22" el="9" sc="13" sl="9"/> 
  <SequencePoint offset="15" ordinal="1" uspid="34" vc="0" ec="22" el="11" sc="13" sl="11"/> 
  <SequencePoint offset="28" ordinal="2" uspid="35" vc="0" ec="17" el="13" sc="5" sl="13"/> 
</SequencePoints>

我们再看一下序列点

15   Function Method2() As String
16        Dim x As String
17        Try
18            x = ""
19        Catch ex As Exception
20            x = ""
21        End Try
22        Return x
23    End Function

和IL

<SequencePoints> 
  <SequencePoint offset="0" ordinal="0" uspid="268" vc="0" ec="33" el="15" sc="5" sl="15"/>
  <SequencePoint offset="1" ordinal="1" uspid="269" vc="0" ec="12" el="17" sc="9" sl="17"/> 
  <SequencePoint offset="2" ordinal="2" uspid="270" vc="0" ec="19" el="18" sc="13" sl="18"/> 
  <SequencePoint offset="17" ordinal="3" uspid="271" vc="0" ec="30" el="19" sc="9" sl="19"/> 
  <SequencePoint offset="18" ordinal="4" uspid="272" vc="0" ec="19" el="20" sc="13" sl="20"/> 
  <SequencePoint offset="31" ordinal="5" uspid="273" vc="0" ec="16" el="21" sc="9" sl="21"/> 
  <SequencePoint offset="32" ordinal="6" uspid="274" vc="0" ec="17" el="22" sc="9" sl="22"/> 
  <SequencePoint offset="36" ordinal="7" uspid="275" vc="0" ec="17" el="23" sc="5" sl="23"/> 
</SequencePoints>

因此,为了覆盖.method public static string Method2 () cil managed { // Method begins at RVA 0x282c // Code size 38 (0x26) .maxstack 2 .locals init ( [0] string Method2, [1] string x, [2] class [mscorlib]System.Exception ex ) IL_0000: nop IL_0001: nop .try { IL_0002: ldstr "" IL_0007: stloc.1 IL_0008: leave.s IL_001f } // end .try catch [mscorlib]System.Exception { IL_000a: dup IL_000b: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError(class [mscorlib]System.Exception) IL_0010: stloc.2 IL_0011: nop IL_0012: ldstr "" IL_0017: stloc.1 IL_0018: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError() IL_001d: leave.s IL_001f } // end handler IL_001f: nop IL_0020: ldloc.1 IL_0021: stloc.0 IL_0022: br.s IL_0024 IL_0024: ldloc.0 IL_0025: ret } // end of method Module1::Method2 ,我们需要命中第21行并且偏移31(IL_001F)并且我们可以看到两个End Try指令跳转到该点,所以现在该行将被标记为已覆盖。

所以Hans和Daniel都是正确的,我希望上面解释为什么

答案 1 :(得分:3)

在控制传递End Try行之前,它到达Return行并退出该函数。所以(就代码覆盖而言)你永远不会达到那条线。并非在这种情况下有任何问题。

解决方法是将VersionInfo保存在单个临时变量中,并在结束尝试后返回。一个猜测(我习惯了C#,而不是VB):

Private Function GetLatestVersionInfoForAsync()
    Dim vi As VersionInfo
    Try
        vi = GetLatestVersionInfo()
    Catch ex As Exception
        RaiseEvent UnhandledAsyncException(Me, New UnhandledExceptionEventArgs(ex, False))
        vi = New VersionInfo() With {.ExceptionOccoured = True, .Exception = ex}
    End Try
    Return vi
End Function

答案 2 :(得分:2)

程序集的PDB文件包含IL指令对应原始源代码的哪些行的信息。这条信息称为序列点。 但并非代码中的每一行都与一个序列点完全对应。 您的测试覆盖率是根据序列点计算的,因此尽管在测试期间执行了代码行,但可能会出现未覆盖的代码行。

答案 3 :(得分:0)

我从未使用过MS-Test,但它会将“New VersionInfo()”标记为未选中。