提问



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

最佳参考


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


此处文档中的示例:使用Visual Studio团队测试进行单元测试演练[37]


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

其它参考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
}


如果绝对必须捕获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());
}


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

其它参考3


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


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

其它参考4


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

其它参考5


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


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


和这里:


http://xunit.github.io/docs/comparisons.html[40]


如果你需要测试异常,那么就不那么讨厌了。您可以使用try {act/fail} catch {assert}方法,这对于不直接支持除ExpectedException之外的异常测试的框架非常有用。


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


你可以在github找到xUnit.NET:http://xunit.github.io/[41]

其它参考6


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


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


我用helper方法代替。


测试


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


整洁,不是吗;)

其它参考7


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


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

其它参考8


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

其它参考9


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


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

其它参考10


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


笔记


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


与其他人不同,我使用传播而不是抛出,因为我们只能测试异常是否从调用传播。我们不能直接测试抛出的异常。但我想你可以把图像抛出来表示:抛出而不是被抓住。


最后的想法


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


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

其它参考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))
                    );
            }
        }
    }
}

其它参考12


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


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

其它参考13


既然你提到使用其他测试类,比ExpectedException属性更好的选择就是使用Shoudly的Do.Throw。[43] [44]


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用于异步方法测试。

其它参考14


查看nUnit文档以获取以下示例:[45]


[ExpectedException( typeof( ArgumentException ) )]

其它参考15


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


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


[ExpectedException(typeof(ArgumentException))]

其它参考16


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


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

其它参考17


如果使用 NUnit ,请尝试以下操作:


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

其它参考18


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


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

其它参考19


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