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

发布于 2024-12-27 20:25:31 字数 702 浏览 2 评论 0原文

我将 MS-Test 与 Visual Studio 2010 和 Visual Basic 结合使用。

在下面的函数中,代码覆盖率告诉我,有一个未检查的块,并且带有 “End Try” 的行是红色的(请参见 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 Try”行是一个未覆盖的(红色)块(同样的情况也发生在最后的“End If”上)的函数)?

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

谢谢!

I use MS-Test with Visual Studio 2010 and Visual Basic.

In the following function the Code Coverage tells me, that there is one unchecked block and the line with the "End Try" is red (see 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

So, why is this "End Try" line an uncovered (red) block (the same happens to "End If" at the end of a function)?

Another question I have: Is there any resource that explains the different colors of in the code coverage results (blue is clear, but I have seen yellow, dark and light red, ...).

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

靖瑶 2025-01-03 20:25:31

除了丹尼尔关于序列点的观点之外,值得进一步研究这一点。如果我们采用一个简单的函数来重复您在调试中所做的事情

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

,我们会得到以下序列点(我正在使用 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 = 结束列,offset = 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

Now 时才有意义,如您所见结束如果您在偏移量 40 (IL_0028) 处命中 IL 指令,那么您担心的 Try 行只会被标记为命中,但是当您查看生成的 IL 时,由于奇怪的 IL,我看不到您将如何到达那里生成(leave.s 是一个类似指令的小跳转,用于退出 try/catch/finally 块),如果您遵循代码,您会发现您总是会到达 leave.s 跳转到首先是IL_0029。

在发布中,IL 发生了变化

.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

,序列点也发生了变化,

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

所以无论哪种方式,你都有点松散,因为现在你永远不会看到你的 try/catch 行被标记为覆盖

所以让我们尝试按照 Hans 的建议更改你的代码并返回调试(因为那是通常,您将在其中运行覆盖)

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

我们再次查看序列点

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

和 IL

.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

,因此为了覆盖您的 End Try,我们需要命中第 21 行,即偏移量 31 (IL_001F) 和正如我们所看到的leave.s 指令跳转到该点,因此现在该行将被标记为已覆盖。

所以汉斯和丹尼尔都是正确的,我希望以上解释了原因

Further to Daniel's point on sequence points it's worth looking at this further. If we take a simple function that repeats what you are doing

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

In Debug we get the following sequence points (I am using OpenCover for this)

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

(where sl = start line, el = end line, sc = start column, ec = end column and offset = IL offset in decimal)

However these only make sense when you look at the 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

Now as you can see the End Try line you are concerned about would only be marked as hit if you hit the IL instruction at offset 40 (IL_0028) however when you look at the IL produced I cant see how you would ever get there due to the odd IL produced (leave.s is a small jump like instruction that is used to exit try/catch/finally blocks) and if you follow the code you see that you will always reach a leave.s that jumps to IL_0029 first.

In release the IL changes

.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

and so do the sequence points

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

So you sort of loose either way as now you will never see your try/catch lines marked covered

So lets try changing your code as suggested by Hans and go back to debug (because that is where you will be running coverage from usually)

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

Again we look at the sequence points

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

and the IL

.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

So for your End Try to be covered we need line 21 to be hit and that is offset 31 (IL_001F) and as we can see both leave.s instructions jump to that point so now that line will be marked as covered.

So both Hans and Daniel are correct and I hope the above explains why

青丝拂面 2025-01-03 20:25:31

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

解决方法是将 VersionInfo 保存在单个临时变量中,并在 End Try 之后返回该信息。一个猜测(我习惯了 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

Before control passes that End Try line, it reaches the Return line and exits the function. So (as far as Code coverage is concerned) you never reach that line. Not that it is any problem in this case.

A workaround would be to hold that VersionInfo in a single temp variable and return that after the End Try. A guess (I'm used to C#, not 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
满意归宿 2025-01-03 20:25:31

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

The PDB file of your assembly contains the information which IL instructions corresponds to which line(s) of your original source code. This piece of information is called a sequence point.
But not every line in your code corresponds exactly to one sequence point.
Your test coverage is calculated based on the sequence points, so it could happen that lines of your code appear uncovered although they were executed during your test.

念﹏祤嫣 2025-01-03 20:25:31

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

I have never used MS-Test but it will be flagging "New VersionInfo()" as unchecked.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文