代码覆盖率与预期异常

发布于 2024-08-08 02:46:45 字数 668 浏览 13 评论 0原文

我对这种模式进行了多次单元测试:

[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);
}

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

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

编辑:呃...看起来上面的 ExpectException 方法也给我留下了 1 个未覆盖的块。

I've got several unittests of this pattern:

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

It turns out that code coverage markes the throwing line as half-run, so I get 1 block of uncovered code each time.

After thinking about this problem for a while, the best solution I could come up with was adding a try/catch. Since this is a repeated pattern, I'm going to create a helper method along the lines of

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

This would have the nice side benefit that I could add all exception tests to the non-throwing tests.

Is this a valid design, or did I miss something?

Edit: Ugs... seems like the above ExpectException method leaves me with 1 uncovered block as well.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

智商已欠费 2024-08-15 02:46:45

您的建议是有效的。除了代码覆盖率问题之外,我认为它比使用 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))

What you are suggesting is valid. Aside from you code coverage issue, I would argue it is better than using the ExpectedException attribute as it explicitly shows which line of the test is expected to throw the exception. Using ExpectedException means that any line of code in the test can throw the expected exception type and the test will still pass. If the error originates from another call that was not expected to throw, it can disguise the fact that the test should be failing because the line that should be throwing isn't.

What would be a useful modification to what you have proposed would be to return the caught exception:

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

This would enable the test code to further assert the exception if it desired (ie. to check a particular message was used).

NUnit (though it doesn't look like you are using it as you have a TestMethod attribute) has a built-in construct similar to what you have proposed:

Assert.Throws<ArgumentNullException>(() => Foo.DoStuff(null))
删除→记忆 2024-08-15 02:46:45

@adrianbanks 如果操作参数抛出另一个异常而不是预期的异常,则 ExpectException 不会按预期工作:

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

当我执行 TestMethod“my_test”时,我刚刚收到一条消息,指出测试方法引发了并且 System.ArgumentException:你好。在这种情况下,它应该显示“Expected 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");
}

@adrianbanks the ExpectException does not work as expected if the action parameter throws another exception than the expected exception:

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

When I execute the TestMethod "my_test" i just got a message saying that the test method raised and System.ArgumentException: hello. In this case it should says "Expected InvalidOperationException".
I propose a new version for the ExpectException method:

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");
}
荭秂 2024-08-15 02:46:45

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

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

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

I know this is an old topic, but I ran into the same problem.

Eventually I questioned myself: why do I need to know the coverage of the tests? I don't! - So let's rule them out, so the coverage is cleaner.

In my test project I've added an CodeCoverage.runsettings file and this is the content:

<?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>

After selecting this Test Settings file my Code Coverage is 100%

This way there is no need to 'hack' the unit test code coverage system, just in order to achieve 100% :-)

岁月流歌 2024-08-15 02:46:45

是的,这是相当标准的费用——我们的很多测试都是这样做的。同时,您必须想知道,如果这些半分支如此重要以至于值得付出努力,您是否对代码覆盖率给予了过高的重视。

Yep this is pretty standard fare - a lot of our tests do the same. At the same time, you have to wonder if you're not placing too high a value on code coverage if those half-branches weigh in so much for it to be worth the effort.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文