39

Visual Studio Test 可以使用 ExpectedException 属性检查预期的异常。你可以像这样传入一个异常:

[TestMethod]
[ExpectedException(typeof(CriticalException))]
public void GetOrganisation_MultipleOrganisations_ThrowsException()

您还可以像这样检查 ExpectedException 中包含的消息:

[TestMethod]
[ExpectedException(typeof(CriticalException), "An error occured")]
public void GetOrganisation_MultipleOrganisations_ThrowsException()

但是在测试 I18N 应用程序时,我会使用资源文件来获取该错误消息(如果我愿意,任何人甚至可以决定测试错误消息的不同本地化,但 Visual Studio 不会让我这样做:

[TestMethod]
[ExpectedException(typeof(CriticalException), MyRes.MultipleOrganisationsNotAllowed)]
public void GetOrganisation_MultipleOrganisations_ThrowsException()

编译器将给出以下错误:

属性参数必须是属性的常量表达式、typeof 表达式或数组创建表达式

有人知道如何测试具有来自资源文件的消息的异常吗?


我考虑过的一种选择是使用自定义异常类,但基于经常听到的建议,例如:

“如果您的错误条件可以以与任何其他现有异常不同的方式以编程方式处理,请创建并抛出自定义异常。否则,抛出现有异常之一。” 来源

我不希望在正常流程中以不同的方式处理异常(这是一个严重的异常,所以无论如何我都会进入恐慌模式),我不认为为每个测试用例创建一个异常是正确的做法。有什么意见吗?

4

7 回答 7

64

我建议使用辅助方法而不是属性。像这样的东西:

public static class ExceptionAssert
{
  public static T Throws<T>(Action action) where T : Exception
  {
    try
    {
      action();
    }
    catch (T ex)
    {
      return ex;
    }
    Assert.Fail("Exception of type {0} should be thrown.", typeof(T));

    //  The compiler doesn't know that Assert.Fail
    //  will always throw an exception
    return null;
  }
}

然后你可以写你的测试是这样的:

[TestMethod]
public void GetOrganisation_MultipleOrganisations_ThrowsException()
{
  OrganizationList organizations = new Organizations();
  organizations.Add(new Organization());
  organizations.Add(new Organization());

  var ex = ExceptionAssert.Throws<CriticalException>(
              () => organizations.GetOrganization());
  Assert.AreEqual(MyRes.MultipleOrganisationsNotAllowed, ex.Message);
}

这还有一个好处,它可以验证异常是在您期望它被抛出的行上抛出的,而不是在测试方法中的任何地方抛出的。

于 2008-09-22T07:26:43.303 回答
14

ExpectedException Message 参数与异常消息不匹配。相反,如果预期的异常实际上没有发生,则这是在测试结果中打印的消息。

于 2009-01-16T05:40:59.507 回答
7

只是一个意见,但我会说错误文本:

  • 是测试的一部分,在这种情况下,从资源中获取它是“错误的”(否则您最终可能会得到一个一致损坏的资源),因此只需在更改资源时更新测试(或测试失败)
  • 不是测试的一部分,您应该只关心它是否引发异常。

请注意,第一个选项应该允许您测试多种语言,因为它能够使用语言环境运行。

至于多个异常,我来自 C++ 领域,在那里创建大量异常(到每个“抛出”语句一个!)在大继承中是可以接受的(如果不常见的话),但 .Net 的元数据系统可能不会'不喜欢那样,因此建议。

于 2008-09-22T06:28:54.457 回答
4

我认为你可以在你的测试代码中做一个显式的 try-catch,而不是依赖 ExpectedException 属性来为你做这件事。然后你可以想出一些帮助方法来读取资源文件并将错误消息与捕获的异常所带来的错误消息进行比较。(当然,如果没有异常,那么测试用例应该被认为是失败的)

于 2008-09-22T06:15:31.510 回答
3

如果您切换到使用非常好的xUnit.Net测试库,您可以将 [ExpectedException] 替换为如下内容:

[Fact]
public void TestException()
{
   Exception ex = Record.Exception(() => myClass.DoSomethingExceptional());
   // Assert whatever you like about the exception here.
}
于 2008-09-22T06:15:57.560 回答
1

我想知道 NUnit 是否正在远离简单的道路……但你去吧。

ExpectedException 属性的新增强(2.4.3 及更高版本?)允许您通过 Handler 方法更好地控制对预期 Exception 执行的检查。更多详细信息在官方 NUnit 文档页面.. 到页面末尾。

[ExpectedException( Handler="HandlerMethod" )]
public void TestMethod()
{
...
}

public void HandlerMethod( System.Exception ex )
{
...
}

注意:这里感觉有些不对劲。为什么您的异常消息国际化了。您是否将异常用于需要处理或通知给用户的事情。除非你有一群文化不同的开发人员修复错误......你不应该需要这个。英语或通用接受语言的例外情况就足够了。但万一你必须有这个..它可能:)

于 2008-09-22T06:46:01.983 回答
0

我在尝试自己解决类似问题时遇到了这个问题。(我将详细说明我在下面确定的解决方案。)

我必须同意 Gishu 关于将异常消息国际化为代码气味的评论。

我最初是在我自己的项目中这样做的,以便我可以在我的应用程序抛出的错误消息和我的单元测试中保持一致。即,当时只需在一个地方定义我的异常消息,资源文件似乎是一个明智的地方,因为我已经将它用于各种标签和字符串(并且因为添加参考是有意义的在我的测试代码中验证这些相同的标签是否显示在适当的位置)。

有一次,我曾考虑(并测试过)使用 try/catch 块来避免 ExpectedException 属性对常量的要求,但是如果大规模应用,这似乎会导致相当多的额外代码。

最后,我确定的解决方案是在我的资源库中创建一个静态类并将我的异常消息存储在其中。这样就不需要将它们国际化(我同意这没有意义),并且在资源字符串可以访问的任何时候都可以访问它们,因为它们在同一个命名空间中。(这符合我不希望验证异常文本成为一个复杂过程的愿望。)

然后我的测试代码简单地归结为(请原谅我的修改......):

[Test, 
    ExpectedException(typeof(System.ArgumentException),
    ExpectedException=ProductExceptionMessages.DuplicateProductName)]
public void TestCreateDuplicateProduct()
{
    _repository.CreateProduct("TestCreateDuplicateProduct");
    _repository.CreateProduct("TestCreateDuplicateProduct");
} 
于 2009-02-18T22:00:31.847 回答