9

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

[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 个未覆盖的块。

4

4 回答 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))
于 2009-10-22T11:32:51.560 回答
3

@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");
}
于 2014-09-17T08:14:33.197 回答
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% :-)

于 2015-12-08T14:12:46.373 回答
0

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

于 2009-10-22T11:00:52.777 回答