如何在Visual Studio Test中使用资源文件中的特定异常消息测试预期的异常?

时间:2020-03-06 14:31:33  来源:igfitidea点击:

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()

编译器将给出以下错误:

An attribute argument must be a
  constant expression, typeof expression
  or array creation expression of an
  attribute

有人知道如何测试资源文件中包含消息的异常吗?

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

"Do create and throw custom exceptions
  if you have an error condition that
  can be programmatically handled in a
  different way than any other existing
  exception. Otherwise, throw one of the
  existing exceptions." Source

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

解决方案

我认为我们可以只在测试代码中进行显式的try-catch,而不是依靠ExpectedException属性为我们完成此操作。然后,我们可以提出一些帮助程序方法,该方法将读取资源文件并将错误消息与捕获到的异常附带的错误消息进行比较。 (当然,如果没有异常,则应将测试用例视为失败)

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

[Fact]
public void TestException()
{
   Exception ex = Record.Exception(() => myClass.DoSomethingExceptional());
   // Assert whatever you like about the exception here.
}

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

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

请注意,第一个选项应允许我们测试多种语言,并具有在语言环境中运行的能力。

至于多个异常,我来自C ++领域,在这里,可以在大的heirachies中创建大量负载(每个" throw"语句一个)!(如果不常见),但是.Net的元数据系统可能不支持不喜欢那样,所以那个建议。

我想知道NUnit是否正在朝着远离简单性的方向发展……但是,我们就来了。

对ExpectedException属性的新增强功能(2.4.3及更高版本?)使我们可以通过Handler方法对要对预期的Exception执行的检查进行更多控制。在页面末尾的NUnit官方文档页面上有更多详细信息。

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

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

注意:这里没有什么感觉。为什么将异常消息国际化。我们是否将异常用于需要处理或者通知用户的事情。除非我们有一群跨文化的开发人员来修复错误..我们不应该需要这个。英文或者通用语言的例外就足够了。但是,如果我们必须拥有它..它可能是:)

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

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

这还有一个好处,就是它可以验证异常是否在我们期望将要抛出的行上而不是测试方法中的任何位置上抛出。

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

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

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

我最初是在自己的项目中完成此操作的,这样我就可以在应用程序和单元测试中抛出的错误消息之间保持一致。即,只需要在一个地方定义我的异常消息,在那个时候,资源文件似乎是一个明智的选择,因为我已经将它用于各种标签和字符串(并且添加引用很有意义)在我的测试代码中对其进行验证,以验证那些相同的标签是否显示在适当的位置)。

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

最后,我选择的解决方案是在资源库中创建一个静态类,并将异常消息存储在其中。这样,就无需对其进行国际化(我同意这是没有道理的),并且由于资源字符串位于同一名称空间中,因此只要资源字符串可访问,它们就可以随时访问。 (这符合我的愿望,即不使验证异常文本成为一个复杂的过程。)

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

[Test, 
    ExpectedException(typeof(System.ArgumentException),
    ExpectedException=ProductExceptionMessages.DuplicateProductName)]
public void TestCreateDuplicateProduct()
{
    _repository.CreateProduct("TestCreateDuplicateProduct");
    _repository.CreateProduct("TestCreateDuplicateProduct");
}