繁体   English   中英

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

[英]How do I use Assert to verify that an exception has been thrown with MSTest?

如何使用Assert (或其他测试类)验证在使用 MSTest/Microsoft.VisualStudio.TestTools.UnitTesting 时是否抛出了异常?

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

通常你的测试框架会有一个答案。 但如果它不够灵活,你总是可以这样做:

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();
}

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

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

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

如果您使用的 MSTest 最初没有ExpectedException属性,则可以执行以下操作:

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

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

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

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

小心使用 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/

在我正在处理的一个项目中,我们有另一个解决方案可以做到这一点。

首先,我不喜欢 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());            
}

辅助方法

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

整洁,不是吗;)

您可以使用简单的一行来实现这一点。

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

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

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

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

它是测试方法的一个属性……您不使用断言。 看起来像这样:

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

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

我不建议使用 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 块。 但是,我不仅要考虑对每个测试使用哪种技术,而且随着需求的变化将代码从一种技术更改为另一种技术并非易事。 使用一种一致的方法可以节省脑力劳动。

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

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

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

上面@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))
                    );
            }
        }
    }
}

由于您提到使用其他测试类,因此比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 行中抛出的。

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

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

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

有一个很棒的库,叫做NFluent ,它可以加快和简化你编写断言的方式

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

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

在使用NUnit 的情况下,试试这个:

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

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

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

[ExpectedException(typeof(ArgumentException))]

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

[ExpectedException( typeof( ArgumentException ) )]

尽管这是一个老问题,但我想在讨论中添加一个新的想法。 我已将安排、执行、断言模式扩展为预期、安排、执行、断言。 您可以创建一个预期的异常指针,然后断言它已分配给。 这感觉比在 catch 块中执行断言更干净,将 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);
}

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

//Arrange

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

//Assert
Assert.Throws<Exception>(LocalFunction);

这适用于Visual Studio Team Test (又名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"));
   }

FluentAssertions 示例

为那些使用该库的人添加一个使用FluentAssertions的例子。

// act
Action result = () => {
    sut.DoSomething();
};

// assert
result.Should().Throw<Exception>();

异步示例

// act
Func<Task> result = async () => {
    await sut.DoSomethingAsync();
};

// assert
await result.Should().ThrowAsync<Exception>();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM