【问题标题】:CodeCoverage vs ExpectedExceptionCodeCoverage 与 ExpectedException
【发布时间】:2009-10-22 10:51:50
【问题描述】:

我有几个这种模式的单元测试:

[TestMethod ()]
[ExpectedException (typeof (ArgumentNullException))]
public void DoStuffTest_Exception ()
{
    var foo = new Foo ();
    Foo.DoStuff (null);
}

事实证明,代码覆盖将投掷线标记为半运行,所以我每次都会得到 1 块未覆盖的代码。

在考虑了这个问题一段时间后,我能想出的最佳解决方案是添加一个 try/catch。由于这是一个重复的模式,我将按照以下方式创建一个辅助方法

public static void ExpectException<_T> (Action action) where _T: Exception
{
    try { action(); }
    catch (_T) { return; }
    Assert.Fail ("Expected " + _T);
}

这将有一个很好的附带好处,我可以将所有异常测试添加到非抛出测试中。

这是一个有效的设计,还是我错过了什么?

编辑: Ugs... 似乎上面的 ExpectException 方法也给我留下了 1 个未覆盖的块。

【问题讨论】:

    标签: c# unit-testing expected-exception


    【解决方案1】:

    您的建议是有效的。除了代码覆盖率问题之外,我认为它比使用 ExpectedException 属性更好,因为它明确显示测试的哪一行预计会引发异常。使用ExpectedException 意味着测试中的任何行代码都可以抛出预期的异常类型并且测试仍然会通过。如果错误源于另一个预期不会抛出的调用,它可以掩盖测试应该失败的事实,因为应该抛出的行没有。

    对您建议的有用修改是返回捕获的异常:

    public static _T ExpectException<_T> (Action action) where _T: Exception
    {
        try { action(); }
        catch (_T ex) { return ex; }
        Assert.Fail ("Expected " + typeof(_T));
        return null;
    }
    

    这将使测试代码能够在需要时进一步断言异常(即检查是否使用了特定消息)。

    NUnit(虽然它看起来不像你在使用它,因为你有一个 TestMethod 属性)有一个类似于你建议的内置结构:

    Assert.Throws<ArgumentNullException>(() => Foo.DoStuff(null))
    

    【讨论】:

    • +1。好东西。这一直困扰着我一段时间,但一直没有解决它。
    【解决方案2】:

    @adrianbanks 如果 action 参数抛出预期异常之外的另一个异常,ExpectException 不会按预期工作:

    [TestMethod]
    public void my_test()
    {
        ExpectException<InvalidOperationException>(delegate()
        {
            throw new ArgumentException("hello");
        });
    }
    

    当我执行 TestMethod "my_test" 时,我刚刚收到一条消息,指出测试方法已引发并且 System.ArgumentException: 你好。在这种情况下,它应该显示“预期的 InvalidOperationException”。 我为 ExpectException 方法提出了一个新版本:

    public static void VerifierException<T>(Action action) where T : Exception
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            Assert.IsInstanceOfType(ex, typeof(T));
            return;
        }
    
        Assert.Fail("Aucune exception n'a été déclenchée alors qu'une exception du type " + typeof(T).FullName + " était attendue");
    }
    

    【讨论】:

      【解决方案3】:

      我知道这是一个老话题,但我遇到了同样的问题。

      最后我问自己:为什么我需要知道测试的覆盖率? 我不知道! - 所以让我们排除它们,这样覆盖范围就更清晰了。

      在我的测试项目中,我添加了一个CodeCoverage.runsettings 文件,内容如下:

      <?xml version="1.0" encoding="utf-8" ?>
      <RunSettings>
        <DataCollectionRunSettings>
          <DataCollectors>
            <DataCollector friendlyName="Code Coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
              <Configuration>
                <CodeCoverage>
                  <ModulePaths>
                    <Exclude>
                      <ModulePath>.*tests.dll</ModulePath>
                      <ModulePath>.*Tests.dll</ModulePath>
                      <!-- Add more ModulePath nodes here. -->
                    </Exclude>
                  </ModulePaths>
                </CodeCoverage>
              </Configuration>
            </DataCollector>
          </DataCollectors>
        </DataCollectionRunSettings>
      </RunSettings>
      

      选择此 Test Settings 文件后,我的代码覆盖率为 100%

      这样就不需要“破解”单元测试代码覆盖系统,只是为了达到 100% :-)

      【讨论】:

      • 感谢您提出这个想法!我目前无法亲自尝试,但如果您确保测试本身正确,这似乎是一个非常好的解决方案。
      【解决方案4】:

      是的,这是相当标准的票价 - 我们的许多测试都是这样做的。同时,您不得不怀疑,如果这些半分支的重要性如此之大以至于值得付出努力,您是否没有对代码覆盖率赋予过高的价值。

      【讨论】:

      • 目前覆盖率高达 35%,因此不会增加太多。这更像是一个小设计问题(可能会节省几百行测试代码)。
      猜你喜欢
      • 2014-08-22
      • 1970-01-01
      • 2019-04-13
      • 1970-01-01
      • 1970-01-01
      • 2012-11-20
      • 1970-01-01
      • 2017-05-09
      • 1970-01-01
      相关资源
      最近更新 更多