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

Ale*_*lex 783 c# unit-testing assert mstest vs-unit-testing-framework

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

Kev*_*lin 931

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

此处文档中的示例:使用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");
}
Run Code Online (Sandbox Code Playgroud)

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

ojr*_*rac 243

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

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

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

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

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

catch (AssertionException) { throw; }
Run Code Online (Sandbox Code Playgroud)

您应该能够将此方法适用于您喜欢的任何方式 - 包括指定要捕获的异常类型.如果您只想要某些类型,请catch使用以下命令关闭块:

} catch (GoodException) {
} catch (Exception) {
    // not the right kind of exception
    Assert.Fail();
}
Run Code Online (Sandbox Code Playgroud)

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

小智 111

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

这意味着您的单元测试如下所示:

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

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

  • 使这更好的唯一方法是让函数返回捕获的异常,以便您可以继续声明异常上的属性之类的东西是正确的. (11认同)
  • 微软终于开始更新 MSTest - v2 支持 `Assert.ThrowsException&lt;T&gt;` 和 `Assert.ThrowsExceptionAsync&lt;T&gt;` - 见 https://blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/ mstest-v2-now-and-ahead/ (3认同)
  • 摆脱 bool 标志并将 throw 直接放在调用之后,以获得更紧凑的实现。 (2认同)
  • 谢谢!这对我来说似乎是最好的方法,因为它是在一种方法中测试多个异常的简短方法.它也更具可读性. (2认同)
  • @MickeyPerlstein属性打破了AAA测试规则.具体来说,如果您的安排恰好在您达到法案之前抛出异常,那么您的测试通过......呃! (2认同)

Jon*_*jap 60

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

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

  • 这确实有效,但我不建议这样做,因为逻辑过于复杂.不是说它很复杂,但考虑一下你是否为多次测试编写了这段代码 - 10s,100s的测试.需要将这种逻辑归结为精心设计的断言方法.看到其他答案. (3认同)

dam*_*mir 53

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

Assert.Throws<ExpectedException>(() => methodToTest());
Run Code Online (Sandbox Code Playgroud)


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

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

请参阅:http://nunit.org/docs/2.5/exceptionAsserts.html

  • 没有错,但问题具体是关于 MSTest/Microsoft.VisualStudio.TestTools.UnitTesting ——而不是 NUnit (2认同)

jri*_*sta 35

警惕使用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样式语法 - http://nunit.com/index.php?p=releaseNotes&r=2.5 (4认同)

Gle*_*enn 24

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

首先,我不喜欢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());            
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
Run Code Online (Sandbox Code Playgroud)

整洁,不是吗;)


Mar*_*eby 19

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

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

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

  • 在 2018 年,这被认为是最佳实践,因为它仅检查被测单元是否抛出异常,而不检查其他代码。 (6认同)

byt*_*der 14

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

[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()
Run Code Online (Sandbox Code Playgroud)


Bra*_*ite 13

您可以使用以下命令从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)));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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


Cfr*_*rim 9

您可以通过简单的单行实现此目的.

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

await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());
Run Code Online (Sandbox Code Playgroud)

如果foo.bar()不是异步

Assert.ThrowsException<Exception>(() => foo.bar());
Run Code Online (Sandbox Code Playgroud)

  • 还有很多其他答案,对我来说,我正在寻找一种速记方法来仅通过异常类型测试已知的失败条件,这是最容易阅读的测试用例。注意:异常类型与标准 try-catch 等继承的异常类不匹配,因此上面的示例不会捕获例如“ArgumentException”。如果您有要测试的高级标准,旧的 Try Catch and test the exception response 仍然是首选,但对于我的许多情况,这有很大帮助! (2认同)

Bas*_*hir 6

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

//Arrange

//Act
void LocalFunction() => mr.ActualMethod(params);

//Assert
Assert.Throws<Exception>(LocalFunction);
Run Code Online (Sandbox Code Playgroud)


ste*_*eve 5

我不建议使用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));
    }
}
Run Code Online (Sandbox Code Playgroud)

示例用途:

    [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)
    }
Run Code Online (Sandbox Code Playgroud)

笔记

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

与其他人不同,我使用'propagates'而不是'throws',因为我们只能测试异常是否从调用传播.我们无法直接测试抛出异常.但是我想你可以将投射图像意味着:抛出而不是被抓住.

最后的想法

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

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


TTT*_*TTT 5

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

[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
    //...
}
Run Code Online (Sandbox Code Playgroud)