.NET 的单元测试之路 之三:计算代码覆盖率

  1. 准备业务代码和测试代码

    1. 新建一个.net framework类库,创建Calc.cs文件,添加如下代码:

      public class Calc
      {
          public int AbsAdd(int a, int b)
          {
              if (a > 0)
                  return a + b;
              else
                  return (-1) * a + b;
          }
          public int Abs(int a)
          {
              if (a > 0)
                  return a;
              return -1 * a;
          }
      }
    2. 新建一个UT项目,对Calc的方法添加测试

      [TestClass]
      public class CalcTest
      {
          [TestMethod]
          public void Add_Test()
          {
              Calc calc = new Calc();
              var s = calc.AbsAdd(3, 6);
              Assert.AreEqual(s, 9);
          }
          [TestMethod]
          public void AbsAdd_Test()
          {
              Calc calc = new Calc();
              var s = calc.Abs(-3);
              Assert.AreEqual(s, 3);
          }
      }
  2. 在VS中执行测试、并检查测试覆盖度,用颜色标记覆盖代码行

    1. 在测试资源管理器中,点相应测试用例,右键“分析所选测试的代码覆盖率”
    2. 可以看到覆盖率、双击相应方法,可以看到哪些代码被覆盖,哪些没被覆盖
  3. 用命令行执行单元测试,并检查代码覆盖度。

    1. 添加环境变量。执行单元测试要用到vstest.console.exe,检查覆盖度要用到CodeCoverage.exe。它们都在visual studio的安装目录中,只是没有把路径加到系统环境变量中。因此,需要先把它们加到环境变量的Path里。
      1. vstest.console.exe:VisualStudio安装目录\Team Tools\Dynamic Code Coverage Tools;
      2. CodeCoverage.exe:VisualStudio安装目录\Common7\IDE\Extensions\TestPlatform;
    2. 运行命令:
      1. 收集:CodeCoverage collect /output:D:\1\Files\3.coverage vstest.console.exe D:\1\UT\UT\bin\Debug\UT.dll
        参数:
        1. 3.coverage 是要生成的覆盖度文件,2进制文件,需要用vs查看。
        2. UT.dll 是测试项目编译后的文件
      2. 分析:CodeCoverage analyze /output:D:\1\Files\3.xml D:\1\Files\3.coverage
        该命令会把3.coverage转成可读的文件3.xml。

        xml version="1.0" encoding="UTF-8" ?>
        <results>
          <modules>
            <module name="ut.dll" path="ut.dll" id="1F16FE5964AEE54C9413C7FB6B33EDAC01000000" block_coverage="100.00" line_coverage="100.00" blocks_covered="8" blocks_not_covered="0" lines_covered="10" lines_partially_covered="0" lines_not_covered="0">
              <functions>
                <function id="8272" token="0x6000001" name="Add_Test()" namespace="UT" type_name="CalcTest" block_coverage="100.00" line_coverage="100.00" blocks_covered="4" blocks_not_covered="0" lines_covered="5" lines_partially_covered="0" lines_not_covered="0">
                  <ranges>
                    <range source_id="0" covered="yes" start_line="12" start_column="9" end_line="12" end_column="10" />
                    <range source_id="0" covered="yes" start_line="13" start_column="13" end_line="13" end_column="36" />
                    <range source_id="0" covered="yes" start_line="14" start_column="13" end_line="14" end_column="39" />
                    <range source_id="0" covered="yes" start_line="15" start_column="13" end_line="15" end_column="35" />
                    <range source_id="0" covered="yes" start_line="16" start_column="9" end_line="16" end_column="10" />
                  ranges>
                function>
                <function id="8312" token="0x6000002" name="AbsAdd_Test()" namespace="UT" type_name="CalcTest" block_coverage="100.00" line_coverage="100.00" blocks_covered="4" blocks_not_covered="0" lines_covered="5" lines_partially_covered="0" lines_not_covered="0">
                  <ranges>
                    <range source_id="0" covered="yes" start_line="19" start_column="9" end_line="19" end_column="10" />
                    <range source_id="0" covered="yes" start_line="20" start_column="13" end_line="20" end_column="36" />
                    <range source_id="0" covered="yes" start_line="21" start_column="13" end_line="21" end_column="34" />
                    <range source_id="0" covered="yes" start_line="22" start_column="13" end_line="22" end_column="35" />
                    <range source_id="0" covered="yes" start_line="23" start_column="9" end_line="23" end_column="10" />
                  ranges>
                function>
              functions>
              <source_files>
                <source_file id="0" path="D:\1\UT\UT\UnitTest1.cs">
                source_file>
              source_files>
            module>
            <module name="bizlogic.dll" path="bizlogic.dll" id="3CCBE3B7A33CB441A19683A78A499F7901000000" block_coverage="75.00" line_coverage="80.00" blocks_covered="6" blocks_not_covered="2" lines_covered="8" lines_partially_covered="0" lines_not_covered="2">
              <functions>
                <function id="8272" token="0x6000001" name="AbsAdd(int, int)" namespace="BizLogic" type_name="Calc" block_coverage="75.00" line_coverage="80.00" blocks_covered="3" blocks_not_covered="1" lines_covered="4" lines_partially_covered="0" lines_not_covered="1">
                  <ranges>
                    <range source_id="0" covered="yes" start_line="12" start_column="9" end_line="12" end_column="10" />
                    <range source_id="0" covered="yes" start_line="13" start_column="13" end_line="13" end_column="23" />
                    <range source_id="0" covered="yes" start_line="14" start_column="17" end_line="14" end_column="30" />
                    <range source_id="0" covered="no" start_line="16" start_column="17" end_line="16" end_column="37" />
                    <range source_id="0" covered="yes" start_line="17" start_column="9" end_line="17" end_column="10" />
                  ranges>
                function>
                <function id="8312" token="0x6000002" name="Abs(int)" namespace="BizLogic" type_name="Calc" block_coverage="75.00" line_coverage="80.00" blocks_covered="3" blocks_not_covered="1" lines_covered="4" lines_partially_covered="0" lines_not_covered="1">
                  <ranges>
                    <range source_id="0" covered="yes" start_line="19" start_column="9" end_line="19" end_column="10" />
                    <range source_id="0" covered="yes" start_line="20" start_column="13" end_line="20" end_column="23" />
                    <range source_id="0" covered="no" start_line="21" start_column="17" end_line="21" end_column="26" />
                    <range source_id="0" covered="yes" start_line="22" start_column="13" end_line="22" end_column="27" />
                    <range source_id="0" covered="yes" start_line="23" start_column="9" end_line="23" end_column="10" />
                  ranges>
                function>
              functions>
              <source_files>
                <source_file id="0" path="D:\1\UT\BizLogic\Calc.cs">
                source_file>
              source_files>
            module>
          modules>
        results>
  4. 只计算指定dll的覆盖率:

    默认情况下,vstest会把指定dll目录下的所有有pdb文件的dll都计算一遍覆盖率。

    这样就会掺杂进去我们不关心的基础dll、第三方dll,导致计算时间长、总覆盖率不准确。

    因此,我们需要指定我们想要计算覆盖率的dll,或者排除掉我们不关心的dll。

    方法是,在CodeCoverage命令上,指定config文件,在config文件中,排除掉指定dll、或只计算特定dll。支持正则表达式。。

    排除包含Foundation的dll

    <ModulePaths>
      <Exclude>
        <ModulePath>.*Foundation.*ModulePath>
      Exclude>
    ModulePaths>

    配置文件的模板,不要用微软官方给的,要到CodeCoverage.exe目录下找配置,复制过来。目录: C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Team Tools\Dynamic Code Coverage Tools

  5. 常见问题:

    1. 有的VisualStudio 2017 环境下,命令行生成的报告显示“生成了空结果: 未检测任何二进制文件。请确保测试已运行,所需二进制文件已加载,具有匹配的符号文件,并且未通过自定义设置排除”。

      这个问题是由于VisualStudio2017的bug,不要纠结。升级你的VS版本就好。
      https://developercommunity.visualstudio.com/content/problem/97029/code-coverage-not-working-in-153.html?childToView=124999#comment-124999

你可能感兴趣的:(.NET 的单元测试之路 之三:计算代码覆盖率)