代码覆盖率 vs 预期异常

9

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

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

原来代码覆盖率标记抛出行为半运行状态,所以每次我都会得到一个未覆盖的代码块。

经过一番思考,我想到的最佳解决方案是添加try/catch。由于这是一个重复的模式,我打算创建一个类似以下的帮助方法

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

这将有一个很好的副作用,我可以将所有异常测试添加到非抛出测试中。
这是一个有效的设计吗?还是我漏掉了什么?
编辑:糟糕...似乎上面的ExpectException方法也给我留下了1个未覆盖的块。
4个回答

10
你的建议是有效的。除了你提到的代码覆盖率问题,我认为这种方法比使用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。好东西。这个问题困扰了我一段时间,但一直没有时间去解决它。 - magnus

3

@adrianbanks 如果 action 参数抛出的异常不是预期异常,则 ExpectException 不会按预期工作:

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

当我执行TestMethod“my_test”时,只会收到一条消息,说测试方法引发了System.ArgumentException:hello。在这种情况下,应该显示“预期的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");
}

2

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

最终,我问自己:为什么需要知道测试覆盖率呢?其实不需要!- 所以让我们排除它们,使覆盖率更加清晰。

在我的测试项目中,我添加了一个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>

在选择了这个名为测试设置的文件后,我的代码覆盖率达到了100%。
这样做就不需要“黑客”单元测试代码覆盖系统,只为了达到100% :-)。

谢谢你的这个想法!我目前无法自己尝试,但如果你确保测试本身是正确的,它似乎是一个非常好的解决方案。 - mafu

0

是的,这是相当标准的做法 - 我们的许多测试也是如此。同时,如果那些半分支在代码覆盖率方面占据了如此重要的地位,是否值得花费这么多的精力来实现,这也是值得思考的。


目前测试覆盖率仅为惊人的35%,因此这并没有太大作用。这更像是一个小型设计问题(可以节省几百行测试代码)。 - mafu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接