【问题标题】:How do I use Assert to verify that an exception has been thrown?如何使用 Assert 来验证是否引发了异常?
【发布时间】:2010-10-30 08:03:54
【问题描述】:

如何使用Assert(或其他测试类)来验证是否引发了异常?

【问题讨论】:

标签: c# unit-testing mstest assert vs-unit-testing-framework


【解决方案1】:

通常您的测试框架会对此给出答案。但如果不够灵活,您可以随时这样做:

try {
    somethingThatShouldThrowAnException();
    Assert.Fail(); // If it gets to this line, no exception was thrown
} catch (GoodException) { }

正如@Jonas 指出的那样,这不适用于捕获基本异常:

try {
    somethingThatShouldThrowAnException();
    Assert.Fail(); // raises AssertionException
} catch (Exception) {
    // Catches the assertion exception, and the test passes
}

如果您绝对必须捕获异常,则需要重新抛出 Assert.Fail()。但实际上,这表明你不应该手写这个;检查你的测试框架的选项,或者看看你是否可以抛出一个更有意义的异常来测试。

catch (AssertionException) { throw; }

您应该能够根据自己的喜好调整这种方法——包括指定要捕获的异常类型。如果您只期望某些类型,请使用以下命令完成 catch 块:

} catch (GoodException) {
} catch (Exception) {
    // not the right kind of exception
    Assert.Fail();
}

【讨论】:

  • +1,当我需要做出超出异常类型的断言时,我使用这种方式而不是属性。例如,如果需要检查异常实例中的某些字段是否设置为某些值。
  • 您不需要指定错误信息。这已经足够了:[ExpectedException(typeof(ArgumentException))]
  • 我认为这个解决方案是最好的。 [ExpectedException(typeof(ArgumentException))] 有它的用途,如果测试很简单,但在我看来这是一个懒惰的解决方案,并且适应可能会导致陷阱。此解决方案为您提供了特定控制以进行更正确的测试,此外您还可以测试 Writeline 到您的测试运行报告,该异常确实按预期抛出。
  • 要小心,因为 Assert.Fail() 会引发异常,如果捕获它,则测试通过!
  • @Vinnyq12 我的意思是上面例子中的第一个测试永远不会失败。如果抛出异常(而不是被 ExpectedExceptionAttribute “捕获”),则测试失败
【解决方案2】:

它是测试方法上的一个属性...你不使用 Assert。看起来像这样:

[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()

【讨论】:

    【解决方案3】:

    对于“Visual Studio Team Test”,您似乎将 ExpectedException 属性应用于测试的方法。

    此处的文档示例:A Unit Testing Walkthrough with Visual Studio Team Test

    [TestMethod]
    [ExpectedException(typeof(ArgumentException),
        "A userId of null was inappropriately allowed.")]
    public void NullUserIdInConstructor()
    {
       LogonInfo logonInfo = new LogonInfo(null, "P@ss0word");
    }
    

    【讨论】:

    • ExpectedException 属性同样适用于 NUnit(但 [TestMethod] 应该是 [Test])。
    • @dbkk:在 NUnit 中的工作方式并不完全相同 - 消息被视为需要匹配异常消息的字符串(IU 认为这更有意义)
    • 这个属性可以完成工作,是 c# 程序员的内置功能,但我不建议使用它,因为它不够灵活。考虑一下如果您的测试设置代码抛出异常类型会发生什么:测试通过,但实际上并没有达到您的预期。或者如果你想测试异常对象的状态怎么办。我通常想使用 StringAssert.Contains(e.Message...) 而不是测试整个消息。使用其他答案中描述的断言方法。
    • 避免在 NUnit 中使用 ExpectedException,因为它将在 NUnit 3.0 中被删除。我更喜欢使用 Assert.Throws()
    • 您可以在 MsTest 中使用 Assert.ThrowsException 和 Assert.ThrowsExceptionAsync
    【解决方案4】:

    这将取决于您使用的是什么测试框架?

    例如,在 MbUnit 中,您可以使用属性指定预期的异常,以确保获得您真正期望的异常。

    [ExpectedException(typeof(ArgumentException))]
    

    【讨论】:

      【解决方案5】:

      查看nUnit Docs 以获取有关以下内容的示例:

      [ExpectedException( typeof( ArgumentException ) )]
      

      【讨论】:

        【解决方案6】:

        如果您使用 MSTest,它最初没有 ExpectedException 属性,您可以这样做:

        try 
        {
            SomeExceptionThrowingMethod()
            Assert.Fail("no exception thrown");
        }
        catch (Exception ex)
        {
            Assert.IsTrue(ex is SpecificExceptionType);
        }
        

        【讨论】:

        • 这确实有效,但我一般不建议这样做,因为逻辑过于复杂。并不是说它很复杂,但考虑一下你是否为多个测试编写这段代码——10 次,100 次测试。这个逻辑需要外包给一个设计良好的断言方法。查看其他答案。
        • 也可以使用Assert.IsInstanceOfType(ex, typeof(SpecificExceptionType);
        【解决方案7】:

        小心使用 ExpectedException,因为它可能导致如下所示的几个陷阱:

        http://geekswithblogs.net/sdorman/archive/2009/01/17/unit-testing-and-expected-exceptions.aspx

        这里:

        http://xunit.github.io/docs/comparisons.html

        如果您需要测试异常情况,那么就没有那么多不受欢迎的方法了。您可以使用try{act/fail}catch{assert} 方法,该方法对于不直接支持ExpectedException 以外的异常测试的框架很有用。

        一个更好的选择是使用 xUnit.NET,它是一个非常现代、前瞻性和可扩展的单元测试框架,它从所有其他错误中吸取了教训,并进行了改进。其中一个改进是Assert.Throws,它为断言异常提供了更好的语法。

        你可以在 github 上找到 xUnit.NET:http://xunit.github.io/

        【讨论】:

        • 请注意,NUnit 2.5 现在也支持 Assert.Throws 样式语法 - nunit.com/index.php?p=releaseNotes&r=2.5
        • 在使用 ExpectedException 时单元测试停止让您知道异常的方式让我发疯。为什么 MS 认为在自动化测试中手动执行步骤是个好主意?感谢您的链接。
        • @Ant:MS 复制了 NUnit...所以真正的问题是,为什么 NUnit 认为这是个好主意?
        【解决方案8】:

        在我正在进行的一个项目中,我们有另一个解决方案。

        首先我不喜欢 ExpectedExceptionAttribute,因为它确实考虑了导致异常的方法调用。

        我使用辅助方法来代替。

        测试

        [TestMethod]
        public void AccountRepository_ThrowsExceptionIfFileisCorrupt()
        {
             var file = File.Create("Accounts.bin");
             file.WriteByte(1);
             file.Close();
        
             IAccountRepository repo = new FileAccountRepository();
             TestHelpers.AssertThrows<SerializationException>(()=>repo.GetAll());            
        }
        

        HelperMethod

        public static TException AssertThrows<TException>(Action action) where TException : Exception
            {
                try
                {
                    action();
                }
                catch (TException ex)
                {
                    return ex;
                }
                Assert.Fail("Expected exception was not thrown");
        
                return null;
            }
        

        整洁,不是吗;)

        【讨论】:

          【解决方案9】:

          我首选的实现方法是编写一个名为 Throws 的方法,并像使用任何其他 Assert 方法一样使用它。不幸的是,.NET 不允许您编写静态扩展方法,因此您不能使用此方法,就好像它实际上属于 Assert 类中的构建一样;只需制作另一个名为 MyAssert 或类似的东西。该类如下所示:

          using System;
          using Microsoft.VisualStudio.TestTools.UnitTesting;
          
          namespace YourProject.Tests
          {
              public static class MyAssert
              {
                  public static void Throws<T>( Action func ) where T : Exception
                  {
                      var exceptionThrown = false;
                      try
                      {
                          func.Invoke();
                      }
                      catch ( T )
                      {
                          exceptionThrown = true;
                      }
          
                      if ( !exceptionThrown )
                      {
                          throw new AssertFailedException(
                              String.Format("An exception of type {0} was expected, but not thrown", typeof(T))
                              );
                      }
                  }
              }
          }
          

          这意味着你的单元测试看起来像这样:

          [TestMethod()]
          public void ExceptionTest()
          {
              String testStr = null;
              MyAssert.Throws<NullReferenceException>(() => testStr.ToUpper());
          }
          

          它的外观和行为更像您的单元测试语法的其余部分。

          【讨论】:

          • 去掉 bool 标志,在调用后直接将 throw 放在行上,实现更紧凑。
          • 唯一能改善这一点的是让函数返回捕获的异常,这样您就可以继续断言异常上的属性之类的东西是正确的。
          • 谢谢!这对我来说似乎是最好的方法,因为它是一种用一种方法测试多个异常的捷径。它也更具可读性。
          • @MickeyPerlstein 属性打破了 AAA 测试规则。具体来说,如果您的 Arrange 碰巧在您到达 Act 之前抛出了异常,那么您的测试通过了……哎呀!
          • 微软终于开始更新 MSTest - v2 支持 Assert.ThrowsException&lt;T&gt;Assert.ThrowsExceptionAsync&lt;T&gt; - 请参阅 blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/…
          【解决方案10】:

          好吧,我将在这里总结一下其他人之前所说的话...无论如何,这是我根据好的答案构建的代码:) 剩下要做的就是复制和使用...

          /// <summary>
          /// Checks to make sure that the input delegate throws a exception of type TException.
          /// </summary>
          /// <typeparam name="TException">The type of exception expected.</typeparam>
          /// <param name="methodToExecute">The method to execute to generate the exception.</param>
          public static void AssertRaises<TException>(Action methodToExecute) where TException : System.Exception
          {
              try
              {
                  methodToExecute();
              }
              catch (TException) {
                  return;
              }  
              catch (System.Exception ex)
              {
                  Assert.Fail("Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
              }
              Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");  
          }
          

          【讨论】:

            【解决方案11】:

            上面@Richiban 提供的帮助器效果很好,只是它不能处理抛出异常的情况,但不是预期的类型。以下地址:

            using System;
            using Microsoft.VisualStudio.TestTools.UnitTesting;
            
            namespace YourProject.Tests
            {
                public static class MyAssert
                {
                    /// <summary>
                    /// Helper for Asserting that a function throws an exception of a particular type.
                    /// </summary>
                    public static void Throws<T>( Action func ) where T : Exception
                    {
                        Exception exceptionOther = null;
                        var exceptionThrown = false;
                        try
                        {
                            func.Invoke();
                        }
                        catch ( T )
                        {
                            exceptionThrown = true;
                        }
                        catch (Exception e) {
                            exceptionOther = e;
                        }
            
                        if ( !exceptionThrown )
                        {
                            if (exceptionOther != null) {
                                throw new AssertFailedException(
                                    String.Format("An exception of type {0} was expected, but not thrown. Instead, an exception of type {1} was thrown.", typeof(T), exceptionOther.GetType()),
                                    exceptionOther
                                    );
                            }
            
                            throw new AssertFailedException(
                                String.Format("An exception of type {0} was expected, but no exception was thrown.", typeof(T))
                                );
                        }
                    }
                }
            }
            

            【讨论】:

            • 嗯...我理解这个想法,但我不确定我是否同意它更好。仅仅因为我们想要确保引发特定异常并不意味着所有其他异常都应该被包装为断言失败。恕我直言,未知异常应该像在任何其他断言操作中一样在堆栈中冒泡。
            • @Martin 我会删除涉及 exceptionOther 的代码,然后从第二个 catch 子句中重新抛出
            【解决方案12】:

            如果你使用 NUNIT,你可以这样做:

            Assert.Throws<ExpectedException>(() => methodToTest());
            


            也可以存储抛出的异常以进一步验证它:

            ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
            Assert.AreEqual( "Expected message text.", ex.Message );
            Assert.AreEqual( 5, ex.SomeNumber);
            

            见:http://nunit.org/docs/2.5/exceptionAsserts.html

            【讨论】:

              【解决方案13】:

              我不建议使用 ExpectedException 属性(因为它太受约束且容易出错)或在每个测试中编写 try/catch 块(因为它太复杂且容易出错)。使用设计良好的断言方法——由您的测试框架提供或自己编写。这是我编写和使用的内容。

              public static class ExceptionAssert
              {
                  private static T GetException<T>(Action action, string message="") where T : Exception
                  {
                      try
                      {
                          action();
                      }
                      catch (T exception)
                      {
                          return exception;
                      }
                      throw new AssertFailedException("Expected exception " + typeof(T).FullName + ", but none was propagated.  " + message);
                  }
              
                  public static void Propagates<T>(Action action) where T : Exception
                  {
                      Propagates<T>(action, "");
                  }
              
                  public static void Propagates<T>(Action action, string message) where T : Exception
                  {
                      GetException<T>(action, message);
                  }
              
                  public static void Propagates<T>(Action action, Action<T> validation) where T : Exception
                  {
                      Propagates(action, validation, "");
                  }
              
                  public static void Propagates<T>(Action action, Action<T> validation, string message) where T : Exception
                  {
                      validation(GetException<T>(action, message));
                  }
              }
              

              使用示例:

                  [TestMethod]
                  public void Run_PropagatesWin32Exception_ForInvalidExeFile()
                  {
                      (test setup that might propagate Win32Exception)
                      ExceptionAssert.Propagates<Win32Exception>(
                          () => CommandExecutionUtil.Run(Assembly.GetExecutingAssembly().Location, new string[0]));
                      (more asserts or something)
                  }
              
                  [TestMethod]
                  public void Run_PropagatesFileNotFoundException_ForExecutableNotFound()
                  {
                      (test setup that might propagate FileNotFoundException)
                      ExceptionAssert.Propagates<FileNotFoundException>(
                          () => CommandExecutionUtil.Run("NotThere.exe", new string[0]),
                          e => StringAssert.Contains(e.Message, "NotThere.exe"));
                      (more asserts or something)
                  }
              

              注意事项

              返回异常而不支持验证回调是一个合理的想法,但这样做会使此断言的调用语法与我使用的其他断言非常不同。

              与其他人不同,我使用“传播”而不是“抛出”,因为我们只能测试异常是否从调用传播。我们无法直接测试是否引发了异常。但我想你可以想象 throws 的意思是:被抛出而不是被抓住。

              最后的想法

              在切换到这种方法之前,我考虑在测试仅验证异常类型时使用 ExpectedException 属性,并在需要更多验证时使用 try/catch 块。但是,我不仅要考虑每次测试使用哪种技术,而且随着需求的变化将代码从一种技术更改为另一种技术也不是一件容易的事。使用一种一致的方法可以节省脑力劳动。

              因此,总而言之,这种方法具有以下特点:易用性、灵活性和稳健性(很难做错)。

              【讨论】:

                【解决方案14】:

                您可以使用以下方法从 Nuget 下载一个包:PM> Install-Package MSTestExtensions,它将 nUnit/xUnit 样式的 Assert.Throws() 语法添加到 MsTest。

                高级指令:下载程序集并从 BaseTest 继承,您可以使用 Assert.Throws() 语法。

                Throws 实现的主要方法如下所示:

                public static void Throws<T>(Action task, string expectedMessage, ExceptionMessageCompareOptions options) where T : Exception
                {
                    try
                    {
                        task();
                    }
                    catch (Exception ex)
                    {
                        AssertExceptionType<T>(ex);
                        AssertExceptionMessage(ex, expectedMessage, options);
                        return;
                    }
                
                    if (typeof(T).Equals(new Exception().GetType()))
                    {
                        Assert.Fail("Expected exception but no exception was thrown.");
                    }
                    else
                    {
                        Assert.Fail(string.Format("Expected exception of type {0} but no exception was thrown.", typeof(T)));
                    }
                }
                

                披露:我把这个包放在一起。

                更多信息:http://www.bradoncode.com/blog/2012/01/asserting-exceptions-in-mstest-with.html

                【讨论】:

                • 感谢您的示例。你有一个如何测试 Assert.DoesNotThrow() 或等效的例子吗?
                【解决方案15】:

                尽管这是一个老问题,但我想在讨论中添加一个新想法。我已经将 Arrange, Act, Assert 模式扩展为 Expected, Arrange, Act, Assert。您可以创建一个预期的异常指针,然后断言它被分配给。这感觉比在 catch 块中执行 Asserts 更干净,将 Act 部分主要留给一行代码来调用被测方法。您也不必从代码中的多个点Assert.Fail();return。抛出的任何其他异常都将导致测试失败,因为它不会被捕获,并且如果抛出您预期类型的​​异常,但它不是您所期望的,则针对消息或其他属性断言异常有助于确保您的测试不会意外通过。

                [TestMethod]
                public void Bar_InvalidDependency_ThrowsInvalidOperationException()
                {
                    // Expectations
                    InvalidOperationException expectedException = null;
                    string expectedExceptionMessage = "Bar did something invalid.";
                
                    // Arrange
                    IDependency dependency = DependencyMocks.Create();
                    Foo foo = new Foo(dependency);
                
                    // Act
                    try
                    {
                        foo.Bar();
                    }
                    catch (InvalidOperationException ex)
                    {
                        expectedException = ex;
                    }
                
                    // Assert
                    Assert.IsNotNull(expectedException);
                    Assert.AreEqual(expectedExceptionMessage, expectedException.Message);
                }
                

                【讨论】:

                  【解决方案16】:

                  由于您提到使用其他测试类,比ExpectedException 属性更好的选择是使用ShoudlyShould.Throw

                  Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });
                  

                  假设我们要求客户必须有一个地址才能创建订单。如果不是,CreateOrderForCustomer 方法应该产生一个ArgumentException。然后我们可以写:

                  [TestMethod]
                  public void NullUserIdInConstructor()
                  {
                    var customer = new Customer(name := "Justin", address := null};
                  
                    Should.Throw<ArgumentException>(() => {
                      var order = CreateOrderForCustomer(customer) });
                  }
                  

                  这比使用ExpectedException 属性要好,因为我们要明确什么应该引发错误。这使得我们测试中的需求更加清晰,也使得测试失败时的诊断更加容易。

                  注意还有一个Should.ThrowAsync 用于异步方法测试。

                  【讨论】:

                    【解决方案17】:

                    MSTest (v2) 现在有一个 Assert.ThrowsException 函数,可以像这样使用:

                    Assert.ThrowsException<System.FormatException>(() =>
                                {
                                    Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
                                }); 
                    

                    你可以用 nuget 安装它:Install-Package MSTest.TestFramework

                    【讨论】:

                    • 在 2018 年,这被认为是最佳实践,因为它只检查被测单元正在抛出,而不是其他代码。
                    【解决方案18】:

                    作为替代方案,您可以尝试测试异常实际上是在测试中的下两行引发的。

                    var testDelegate = () => MyService.Method(params);
                    Assert.Throws<Exception>(testDelegate);
                    

                    【讨论】:

                      【解决方案19】:

                      如果使用 NUnit,试试这个:

                      Assert.That(() =>
                              {
                                  Your_Method_To_Test();
                              }, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));
                      

                      【讨论】:

                        【解决方案20】:

                        在VS内置单元测试中,如果你只是想验证是否抛出了“任何异常”,但你不知道类型,你可以使用catch all:

                        [TestMethod]
                        [ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
                        public void ThrowExceptionTest()
                        {
                            //...
                        }
                        

                        【讨论】:

                          【解决方案21】:

                          你可以通过简单的一行来实现这一点。

                          如果您的操作foo.bar() 是异步的:

                          await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());
                          

                          如果foo.bar() 不是异步的

                          Assert.ThrowsException<Exception>(() => foo.bar());
                          

                          【讨论】:

                          • 还有很多其他的答案,对我来说,我一直在寻找一种速记方法来仅按异常类型测试已知的失败条件,这使得最容易阅读的测试用例成为可能。注意:异常类型与标准 try-catch 等继承的异常类不匹配,因此上述示例不会捕获 ArgumentException。如果您有高级测试标准,旧的 Try Catch and test 异常响应仍然是首选,但对于我的许多情况,这很有帮助!
                          【解决方案22】:

                          有一个很棒的库叫做NFluent 加速并简化了你编写断言的方式

                          编写一个抛出异常的断言非常简单:

                              [Test]
                              public void given_when_then()
                              {
                                  Check.ThatCode(() => MethodToTest())
                                      .Throws<Exception>()
                                      .WithMessage("Process has been failed");
                              }
                          

                          【讨论】:

                            【解决方案23】:

                            我知道这个帖子很旧并且有很多很好的答案,但也许值得一提的是,本地函数可以以非常简单的方式提供帮助。

                            //Arrange
                            
                            //Act
                            void LocalFunction() => mr.ActualMethod(params);
                            
                            //Assert
                            Assert.Throws<Exception>(LocalFunction);
                            

                            【讨论】:

                              【解决方案24】:

                              这适用于 Visual Studio 团队测试(又名 MSTest
                              在处理数据库或 http 事务时。系统应该在某处抛出异常,使用 Assert.ThrowExceptionAsync() 将捕获您的 Throw 事件。 (在这些情况下,Assert.ThrowException() 不会捕获异常)。

                                 [TestMethod]
                                 public void Invalid_Input_UserName_Should_Throw_Exception()
                                 {
                                     await Assert.ThrowExceptionAsync<ExpectedExceptionType>(()=> new LogonInfo(InvalidInputInUserNameFormat,"P@ssword"));
                                 }
                              

                              【讨论】:

                                猜你喜欢
                                • 2013-02-05
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2013-07-07
                                • 2018-01-13
                                • 1970-01-01
                                • 2020-11-05
                                • 2013-03-27
                                相关资源
                                最近更新 更多