【问题标题】:How do I enforce exception message with ExpectedException attribute如何使用 ExpectedException 属性强制执行异常消息
【发布时间】:2011-06-10 06:07:46
【问题描述】:

我认为这两个测试的行为应该相同,事实上我已经在我的项目中使用 MS Test 编写了测试,只是现在发现它不像 NUnit 那样尊重预期的消息。

NUnit(失败):

[Test, ExpectedException(typeof(System.FormatException), ExpectedMessage = "blah")]
public void Validate()
{
    int.Parse("dfd");
}

MS 测试(通过):

[TestMethod, ExpectedException(typeof(System.FormatException), "blah")]
public void Validate()
{
    int.Parse("dfd");
}

无论我给ms测试什么消息,它都会通过。

如果消息不正确,有什么方法可以让 ms 测试失败?我什至可以创建自己的异常属性吗?我宁愿不必为发生这种情况的每个测试编写一个 try catch 块。

【问题讨论】:

    标签: c# unit-testing nunit mstest expected-exception


    【解决方案1】:

    @rcravens 是正确的 - 第二个参数是测试失败时打印的消息。我为解决这个问题所做的工作是用不同的方式制作我的测试。诚然,我不喜欢这种方法,但它确实有效。

    [TestMethod]
    public void Validate()
    {
        try
        {
            int.Parse("dfd");
        
            // Test fails if it makes it this far
            Assert.Fail("Expected exception was not thrown.");
        }
        catch (ArgumentNullException ex) // <-- Expected Exception class
        {
            Assert.AreEqual("blah", ex.Message);
        }
        catch (Exception ex) // All the other exceptions
        {
            Assert.Fail("Thrown exception was of the wrong type");
        }
    }
    

    【讨论】:

    • 如果使用 mstest,我现在是这种方法的粉丝
    • 谢谢,这将很快完成我想要它做的事情,验证我的消息
    • 这实际上是一个非常狡猾的解决方法.. 我喜欢它!
    • 这只会验证消息。如果您还想验证异常类型,则 catch 必须类似于 catch (ArgumentNullException ex){ Assert.AreEqual("blah", ex.Message); } catch (Exception ex){ Assert.Fail("抛出的异常类型错误"); }
    【解决方案2】:

    试试这种现代方式:

    [TestMethod]
    public async Task DoSmthAsync_WithWrongParam_ThrowHttpRequestException()
    {
        var smbdTask = service.DoSmthAsync(/* call params */);
    
        var exception = await Assert.ThrowsExceptionAsync<HttpRequestException>(() => smbdTask);
        Assert.AreEqual(HttpStatusCode.NotFound, exception.StatusCode);
    }
    

    【讨论】:

    • 是的,这个问题现在很古老。这些天我在测试方法代码中使用流利的断言,并且没有与您的答案相似的属性。
    【解决方案3】:

    我们到处都使用这个属性,我们显然误解了第二个参数(对我们感到羞耻)。

    但是,我们肯定用它来检查异常消息。以下是我们在此页面的提示中使用的内容。它不处理全球化或继承的异常类型,但它可以满足我们的需要。同样,目标是简单地 RR 'ExpectedException' 并将其与此类交换。 (Bummer ExpectedException 已密封。)

    public class ExpectedExceptionWithMessageAttribute : ExpectedExceptionBaseAttribute
    {
        public Type ExceptionType { get; set; }
    
        public string ExpectedMessage { get; set; }
    
        public ExpectedExceptionWithMessageAttribute(Type exceptionType)
        {
            this.ExceptionType = exceptionType;
        }
    
        public ExpectedExceptionWithMessageAttribute(Type exceptionType, string expectedMessage)
        {
            this.ExceptionType = exceptionType;
            this.ExpectedMessage = expectedMessage;
        }
    
        protected override void Verify(Exception e)
        {
            if (e.GetType() != this.ExceptionType)
            {
                Assert.Fail($"ExpectedExceptionWithMessageAttribute failed. Expected exception type: {this.ExceptionType.FullName}. " +
                    $"Actual exception type: {e.GetType().FullName}. Exception message: {e.Message}");
            }
    
            var actualMessage = e.Message.Trim();
            if (this.ExpectedMessage != null)
            {
                Assert.AreEqual(this.ExpectedMessage, actualMessage);
            }
    
            Debug.WriteLine($"ExpectedExceptionWithMessageAttribute:{actualMessage}");
        }
    }
    

    【讨论】:

    • 我尝试使用 PostSharp 实现相同的目标,但继承现有属性更有意义!
    • 真的是我自己做了这门课。虽然我称它为PrintExpectedExceptionAttribute :)
    • 不错!正如他们所说,“伟大的思想者思维相似,同时做同样的工作”。我得说,我们一直都在使用这个属性。
    • 我想补充一点,我们现在基本上使用一个具有静态方法的 ProjectAssert('Project' 是我们的项目名称):ExpectException(Action action,Type exceptionType, string message = null);所以它可能看起来像 ProjectAssert.ExpectException(()=>{var x = 0; var y = 1/x;},typeof(DivideByZeroException))。将它内联很好,但基本上与上面的属性完全相同。
    【解决方案4】:

    我通过添加对包含、不区分大小写和 ResourcesType-ResourceName 组合的支持,为我们的项目扩展了 BlackjacketMack 的答案。

    使用示例:

    public class Foo
    {
        public void Bar(string mode)
        {
            if (string.IsNullOrEmpty(mode)) throw new InvalidOperationException(Resources.InvalidModeSpecified);
        }
    
        public void Bar(int port)
        {
            if (port < 0 || port > Int16.MaxValue) throw new ArgumentOutOfRangeException("port");
        }
    }
    
    [TestClass]
    class ExampleClass
    {
        [TestMethod]
        [ExpectedExceptionWithMessage(typeof(InvalidOperationException), typeof(Samples.Resources), "InvalidModeSpecified")]
        public void Raise_Exception_With_Message_Resource()
        {
            new Foo().Bar(null);
        }
    
        [TestMethod]
        [ExpectedExceptionWithMessage(typeof(ArgumentOutOfRangeException), "port", true)]
        public void Raise_Exeception_Containing_String()
        {
            new Foo().Bar(-123);
        }
    }
    

    这是更新后的类:

    public class ExpectedExceptionWithMessageAttribute : ExpectedExceptionBaseAttribute
    {
        public Type ExceptionType { get; set; }
    
        public Type ResourcesType { get; set; }
    
        public string ResourceName { get; set; }
    
        public string ExpectedMessage { get; set; }
    
        public bool Containing { get; set; }
    
        public bool IgnoreCase { get; set; }
    
        public ExpectedExceptionWithMessageAttribute(Type exceptionType)
            : this(exceptionType, null)
        {
        }
    
        public ExpectedExceptionWithMessageAttribute(Type exceptionType, string expectedMessage)
            : this(exceptionType, expectedMessage, false)
        {
        }
    
        public ExpectedExceptionWithMessageAttribute(Type exceptionType, string expectedMessage, bool containing)
        {
            this.ExceptionType = exceptionType;
            this.ExpectedMessage = expectedMessage;
            this.Containing = containing;
        }
    
        public ExpectedExceptionWithMessageAttribute(Type exceptionType, Type resourcesType, string resourceName)
            : this(exceptionType, resourcesType, resourceName, false)
        {
        }
    
        public ExpectedExceptionWithMessageAttribute(Type exceptionType, Type resourcesType, string resourceName, bool containing)
        {
            this.ExceptionType = exceptionType;
            this.ExpectedMessage = ExpectedMessage;
            this.ResourcesType = resourcesType;
            this.ResourceName = resourceName;
            this.Containing = containing;
        }
    
        protected override void Verify(Exception e)
        {
            if (e.GetType() != this.ExceptionType)
            {
                Assert.Fail(String.Format(
                                "ExpectedExceptionWithMessageAttribute failed. Expected exception type: <{0}>. Actual exception type: <{1}>. Exception message: <{2}>",
                                this.ExceptionType.FullName,
                                e.GetType().FullName,
                                e.Message
                                )
                            );
            }
    
            var actualMessage = e.Message.Trim();
    
            var expectedMessage = this.ExpectedMessage;
    
            if (expectedMessage == null)
            {
                if (this.ResourcesType != null && this.ResourceName != null)
                {
                    PropertyInfo resourceProperty = this.ResourcesType.GetProperty(this.ResourceName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
                    if (resourceProperty != null)
                    {
                        string resourceValue = null;
    
                        try
                        {
                            resourceValue = resourceProperty.GetMethod.Invoke(null, null) as string;
                        }
                        finally
                        {
                            if (resourceValue != null)
                            {
                                expectedMessage = resourceValue;
                            }
                            else
                            {
                                Assert.Fail("ExpectedExceptionWithMessageAttribute failed. Could not get resource value. ResourceName: <{0}> ResourcesType<{1}>.",
                                this.ResourceName,
                                this.ResourcesType.FullName);
                            }
                        }
                    }
                    else
                    {
                        Assert.Fail("ExpectedExceptionWithMessageAttribute failed. Could not find static resource property on resources type. ResourceName: <{0}> ResourcesType<{1}>.",
                            this.ResourceName,
                            this.ResourcesType.FullName);
                    }
                }
                else
                {
                    Assert.Fail("ExpectedExceptionWithMessageAttribute failed. Both ResourcesType and ResourceName must be specified.");
                }
            }
    
            if (expectedMessage != null)
            {
                StringComparison stringComparison = this.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
                if (this.Containing)
                {
                    if (actualMessage == null || actualMessage.IndexOf(expectedMessage, stringComparison) == -1)
                    {
                        Assert.Fail(String.Format(
                                        "ExpectedExceptionWithMessageAttribute failed. Expected message: <{0}>. Actual message: <{1}>. Exception type: <{2}>",
                                        expectedMessage,
                                        e.Message,
                                        e.GetType().FullName
                                        )
                                    );
                    }
                }
                else
                {
                    if (!string.Equals(expectedMessage, actualMessage, stringComparison))
                    {
                        Assert.Fail(String.Format(
                                        "ExpectedExceptionWithMessageAttribute failed. Expected message to contain: <{0}>. Actual message: <{1}>. Exception type: <{2}>",
                                        expectedMessage,
                                        e.Message,
                                        e.GetType().FullName
                                        )
                                    );
                    }
                }
            }
        }
    }
    

    【讨论】:

    • 正如此处stackoverflow.com/a/53730206/955444 所解释的,这在版本 11 (VS2012) 中无法按预期工作,因为它将始终显示“调用目标已引发异常”。至少从版本 14 开始已修复。
    【解决方案5】:

    这是我不久前写的,所以也许在 VS2012 中有更好的方法。

    http://dripcode.blogspot.com.au/search?q=exception

    【讨论】:

      【解决方案6】:

      试试这个。在此示例中,我有一条问候消息,显示给定名称的问候消息。当 name 参数为空或 null 时,系统会抛出带有消息的异常。 ExceptionAssert.Throws 在 MsTest 中验证两者。

      [TestMethod]
      public void Does_Application_Display_Correct_Exception_Message_For_Empty_String()
      {
          // Arrange
          var oHelloWorld = new HelloWorld();
      
          // Act
      
          // Asset
      
          ExceptionAssert.Throws<ArgumentException>(() => 
                  oHelloWorld.GreetingMessge(""),"Invalid Name, Name can't be empty");
      }
      
      [TestMethod]
      public void Does_Application_Display_Correct_Exception_Message_For_Null_String()
      {
          // Arrange
          var oHelloWorld = new HelloWorld();
      
          // Act
      
          // Asset
      
          ExceptionAssert.Throws<ArgumentNullException>(() => 
              oHelloWorld.GreetingMessge(null), "Invalid Name, Name can't be null");
      }
      

      【讨论】:

      • ExceptionAssert 似乎没有捆绑在 Microsoft UnitTesting 中,您使用的是第三方插件吗?
      【解决方案7】:

      您可以使用this article 中解释的this project 中的代码创建一个ExpectedExceptionMessage 属性,该属性将与MS Test 一起使用以获得所需的结果。

      毫秒测试(失败):

      [TestClass]
      public class Tests : MsTestExtensionsTestFixture
      {
          [TestMethod, ExpectedExceptionMessage(typeof(System.FormatException), "blah")]
          public void Validate()
          {
              int.Parse("dfd");
          }
      }
      

      【讨论】:

        【解决方案8】:

        那个mstest第二个参数是测试失败时打印出来的消息。如果抛出格式异常,则 mstest 将成功。我发现这篇文章可能有用

        http://blogs.msdn.com/b/csell/archive/2006/01/13/expectedexception-might-not-be-what-you-ve-expected.aspx

        【讨论】:

        • 谢谢,这是一些很棒的信息。你知道一种使用 ms test 编写与 nunit 行为相同的测试的方法吗?
        • 我可能会考虑创建一个像 nunit 一样的自定义属性。
        • 链接已失效。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多