博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
代码覆盖从简到繁 (三) – 划分Block
阅读量:3979 次
发布时间:2019-05-24

本文共 4886 字,大约阅读时间需要 16 分钟。

      上一篇博客 《》介绍了Visual Studio所采用的Block覆盖中Block是如何定义的,并且展示了代码行与Block之间其实并不是严格对应的。本篇博客将通过.NET中间语言(IL)进一步分析Visual Studio是如何划分Block的,从而更准确回答代码行与Block不能严格对应的原因。

      使用Visual Studio获取code coverage数据是非常简单的,只需要在配置中选择“Code Coverage”选项,然后执行测试用例就可以了,覆盖数据会直接在"Code Coverage Results”窗口中呈现出来,这些在《 》中都有介绍。其实要获取覆盖数据,首先要对被测试的.exe或者.dll进行instrument,所谓instrument实际上就是向文件注入特定的用于收集覆盖数据的代码;然后,启动覆盖数据的监听服务,刚才注入代码会在被指定到时项监听程序发出报告;接下来就是要执行你的测试用例(可以是自动或者手动测试用例);停止监听服务,生成代码覆盖报告。为了易于使用,Visual Studio自动为执行了上述很多工作。除了Visual Studio IDE, 还可以通过命令行工具 VsInstr.exe,VsPerfmon和VsPerfCmd来完成获取覆盖数据的操作, 中有详细的介绍,这里就不再赘述!这里需要注意:这些命令不只是用于代码覆盖,而是性能Profiling的工具。

      这里我们用到了 -coverage命令,它负责instrument我们前面编写的代码,然后使用.NET的  在IL层观察上一篇博客中使用的GetInteger()函数是如何被划分block的,下面就是Instrument之后的GetInteger()函数的IL代码(这里使用的Visual Studio 2010带的C#编译器,编译器不同产生的代码也会不同):

.method public hidebysig instance int32  GetInteger(int32 arg1, int32 arg2) cil managed

{
  // Code size       204 (0xcc)
  .maxstack  3
  .locals init ([0] int32 CS$1$0000, [1] bool CS$4$0001)
  IL_0000:  call       void Microsoft.VisualStudio.Coverage.Init_bbf9568946f2545aaa9b589093700f85::Register()
  IL_0005:  ldsfld     uint64[] Microsoft.VisualStudio.Coverage.Init_bbf9568946f2545aaa9b589093700f85::m_vscov
  IL_000a:  ldc.i4     0x5
  IL_000f:  ldelem.i8
  IL_0010:  ldc.i8     0x1
  IL_0019:  add
  IL_001a:  conv.i
  IL_001b:  ldc.i4.1
  IL_001c:  stind.i1
  IL_001d:  nop                      判断
arg1 > 0
  IL_001e:  ldarg.1   
 
IL_001f:   ldc.i4.0   
  IL_0020:  ble.s   IL_0043   如果 arg1 <= 0,跳转到0043处。
  IL_0022:  ldsfld     uint64[] Microsoft.VisualStudio.Coverage.Init_bbf9568946f2545aaa9b589093700f85::m_vscov
  IL_0027:  ldc.i4     0x5
  IL_002c:  ldelem.i8
  IL_002d:  ldc.i8     0x2
  IL_0036:  add
  IL_0037:  conv.i
  IL_0038:  ldc.i4.1
  IL_0039:  stind.i1
  IL_003a:  ldarg.2         判断 
arg2 < 0
  IL_003b:  ldc.i4.0
  IL_003c:  clt                 
  IL_003e:  ldc.i4.0
  IL_003f:   ceq              
如果 arg2 < 0,  向求值栈(evaluation stack)加载 0;否则为1;
  IL_0041:  br.s  IL_005c      
  IL_0043:  ldsfld     uint64[] Microsoft.VisualStudio.Coverage.Init_bbf9568946f2545aaa9b589093700f85::m_vscov
  IL_0048:  ldc.i4     0x5
  IL_004d:  ldelem.i8
  IL_004e:  ldc.i8     0x3
  IL_0057:  add
  IL_0058:  conv.i
  IL_0059:  ldc.i4.1
  IL_005a:  stind.i1
  IL_005b:  ldc.i4.1      
(arg1 <= 0时)向求值栈(evaluation stack)加载 1
  IL_005c:  ldsfld     uint64[] Microsoft.VisualStudio.Coverage.Init_bbf9568946f2545aaa9b589093700f85::m_vscov
  IL_0061:  ldc.i4     0x5
  IL_0066:  ldelem.i8
  IL_0067:  ldc.i8     0x4
  IL_0070:  add
  IL_0071:  conv.i
  IL_0072:  ldc.i4.1
  IL_0073:  stind.i1
  IL_0074:  stloc.1         判断 arg1 > 0 && arg2 < 0 最终结果
  IL_0075:  ldloc.1  
  IL_0076:  brtrue.s   IL_0095
  IL_0078:  ldsfld     uint64[] Microsoft.VisualStudio.Coverage.Init_bbf9568946f2545aaa9b589093700f85::m_vscov
  IL_007d:  ldc.i4     0x5
  IL_0082:  ldelem.i8
  IL_0083:  ldc.i8     0x5
  IL_008c:  add
  IL_008d:  conv.i
  IL_008e:  ldc.i4.1
  IL_008f:  stind.i1
  IL_0090:  nop                 
准备return 0
  IL_0091:  ldc.i4.0
  IL_0092:  stloc.0
  IL_0093:  br.s       IL_00b2
  IL_0095:  ldsfld     uint64[] Microsoft.VisualStudio.Coverage.Init_bbf9568946f2545aaa9b589093700f85::m_vscov
  IL_009a:  ldc.i4     0x5
  IL_009f:  ldelem.i8
  IL_00a0:  ldc.i8     0x6
  IL_00a9:  add
  IL_00aa:  conv.i
  IL_00ab:  ldc.i4.1
  IL_00ac:  stind.i1
  IL_00ad:  nop                   准备return 1
  IL_00ae:  ldc.i4.1
  IL_00af:   stloc.0
  IL_00b0:  br.s       IL_00b2
  IL_00b2:  ldsfld     uint64[] Microsoft.VisualStudio.Coverage.Init_bbf9568946f2545aaa9b589093700f85::m_vscov
  IL_00b7:  ldc.i4     0x5
  IL_00bc:  ldelem.i8
  IL_00bd:  ldc.i8     0x7
  IL_00c6:  add
  IL_00c7:  conv.i
  IL_00c8:  ldc.i4.1
  IL_00c9:  stind.i1
  IL_00ca:  ldloc.0
  IL_00cb:  ret 
} // end of method Program::GetInteger

      与没有instrument过的IL代码相比,被instrument的代码是多出了上面用灰色标识的部分,它们就是真正用来标记哪些代码被执行的。仔细数数正好是 7 段,每一段标识了一个block划分的开始,数组的索引值(例如:IL_0010: ldc.i8 0x1 )给每个block从 1 到 7 进行了编号。当这些标识block代码被执行,则代表它们所标识真正被测试代码一定被执行到,代码覆盖收集的监听程序,会时刻监听和收集这些标记代码的执行情况,并由此生成最终的覆盖报告。再对照上篇博客多提到的block的定义 - a single entry point, a single exit point, and a set of instructions that are all run in sequence - 仔细检查一下,确实是这样每一个block都是只有一个唯一入口和一个为出口,block标记到大都是加载在br.s、brtrue.s、ble.s等分支跳转语句前面。

      对于GetInteger()而言,最有意思就要数 if( arg1 >0 && arg2 < 0 ),别看只有一行代码,但由于条件与操作&&的存在,在IL级这一行代码时间上是被划分4个block的,如上面的粗体代码所示,这些代码并不是很难理解。这里出个小问题:对于GetInteger()函数,测试用例(arg1 =1, arg2 = -1),能够对 if( arg1 >0 && arg2 < 0 ) 行进行完全覆盖吗?答案:不能,因它漏掉了仅有一条IL指令(IL_005b: ldc.i4.1)的哪个block,随意仍是部分覆盖。要想达到对该if行的完全覆盖,最少需要两个用例, 例如:(arg1 = -1, arg1 =-1)和 (arg1 =1, arg2 = -1)。

      最后需要提示一下:Reflector工具可以将IL代码反编译为C#等语言代码,这样阅读起来会更方便一些,但是有一些instrument过的IL,Reflector反编译的结果可能会丢失一些block划分信息。例如:GetInteger()的发编译结果如下。其中,Block#2和#3并没有显示在代码中体现出来,所以在有些情况下,直接阅读IL代码能更准确把握block的划分情况。

    

转载地址:http://awgki.baihongyu.com/

你可能感兴趣的文章
linux sed 流编辑器的一些应用实例
查看>>
sed 流编辑器的特殊应用( 转载 )
查看>>
Linux 进程通信(System V) 第一节 ------> 管道 pipe
查看>>
一个简单web服务器的java实现
查看>>
C++ 中成员函数指针?
查看>>
linux shell 历史命令记录功能
查看>>
Django用户认证系统 authentication system----登陆访问限制@login_required
查看>>
Change data directory – PostgreSQL
查看>>
debian下postgresql数据迁移
查看>>
Twisted
查看>>
Guide to boto -- MWS package
查看>>
An Example Using boto Amazon MWS Package
查看>>
linux下源码安装zbar
查看>>
Python 的生成二维码生成库 -- qrcode
查看>>
odoo教程---在odoo8中创建自定义的reports
查看>>
"go back" step in a workflow stops everything
查看>>
如何成为一名黑客
查看>>
英语学习资源收藏
查看>>
ubuntu下如何安装NFS服务用于文件共享
查看>>
VMware中Ubuntu安装VMware Tools步骤及问题解决方法
查看>>