首页 文章

如何使用Assert验证是否抛出了异常?

提问于
浏览
700

如何使用Assert(或其他Test类?)来验证是否抛出了异常?

21 回答

  • 13

    对于“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");
    }
    
  • 227

    通常,您的测试框架将为此提供答案 . 但如果它不够灵活,你可以随时这样做:

    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
    }
    

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

    catch (AssertionException) { throw; }
    

    您应该能够根据自己喜欢的方式调整此方法 - 包括指定要捕获的异常类型 . 如果您只期望某些类型,请使用以下命令关闭 catch 块:

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

    我实现这个的首选方法是编写一个名为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());
    }
    

    其外观和行为更像是其他单元测试语法 .

  • 4

    如果您 ExpectedException 具有 ExpectedException 属性,则可以执行以下操作:

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

    如果您使用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

  • 3

    警惕使用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 方法,这对于没有直接支持除ExpectedException之外的异常测试的框架非常有用 .

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

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

  • 2

    在我正在研究的项目中,我们有另一个解决方案 .

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

    我用helper方法代替 .

    Test

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

    整洁,不是吗;)

  • 2

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

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

    您可以使用以下命令从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

  • 11

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

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

    我不建议使用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)
        }
    

    笔记

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

    与其他人不同,我使用'传播'而不是'投掷',因为我们只能测试异常是否从调用传播 . 我们无法直接测试抛出异常 . 但是我想你可以想象一下投掷意味着:抛出并没有被 grab .

    最后的想法

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

    总而言之,这种方法运动:易用性,灵活性和稳健性(很难做错) .

  • 59

    @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))
                        );
                }
            }
        }
    }
    
  • 41

    好吧,我几乎可以总结一下之前所有其他人所说的内容......无论如何,这里是我根据好的答案 Build 的代码:)剩下要做的就是复制和使用......

    /// <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.");  
    }
    
  • 100

    由于您提到使用其他测试类,比 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 .

  • 2

    有关以下示例,请查看nUnit Docs

    [ExpectedException( typeof( ArgumentException ) )]
    
  • 24

    这取决于您使用的测试框架?

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

    [ExpectedException(typeof(ArgumentException))]
    
  • 835

    作为替代方案,您可以尝试测试异常实际上是在测试中接下来的2行抛出 .

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

    如果使用 NUnit ,请尝试以下方法:

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

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

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

    您可以通过简单的单行实现这一目标 .

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

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

    如果 foo.bar() 不是异步

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

    虽然这是一个老问题,但我想在讨论中添加一个新思路 . 我已将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);
    }
    

相关问题