使用Assert测试异常的最佳方法,以确保它们将被抛出

Han*_*sir 87 .net c# unit-testing

您认为这是测试异常的好方法吗?有什么建议?

Exception exception = null;
try{
    //I m sure that an exeption will happen here
}
catch (Exception ex){
    exception = ex;
}

Assert.IsNotNull(exception);
Run Code Online (Sandbox Code Playgroud)

我正在使用MS Test.

tva*_*son 129

我使用了几种不同的模式.ExpectedException当预期出现异常时,我大部分时间都使用该属性.这在大多数情况下都是足够的,但是,在某些情况下,这还不够.异常可能无法捕获 - 因为它是由反射调用的方法引发的 - 或者我只是想检查其他条件是否成立,比如事务被回滚或者某些值仍然被设置.在这些情况下,我将它包装在一个try/catch需要确切异常的块中,Assert.Fail如果代码成功则执行,并捕获通用异常以确保不会抛出不同的异常.

第一种情况:

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void MethodTest()
{
     var obj = new ClassRequiringNonNullParameter( null );
}
Run Code Online (Sandbox Code Playgroud)

第二种情况:

[TestMethod]
public void MethodTest()
{
    try
    {
        var obj = new ClassRequiringNonNullParameter( null );
        Assert.Fail("An exception should have been thrown");
    }
    catch (ArgumentNullException ae)
    {
        Assert.AreEqual( "Parameter cannot be null or empty.", ae.Message );
    }
    catch (Exception e)
    {
        Assert.Fail(
             string.Format( "Unexpected exception of type {0} caught: {1}",
                            e.GetType(), e.Message )
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 许多单元测试框架将断言失败实现为异常.因此,第二种情况下的Assert.Fail()将被catch(异常)块捕获,这将隐藏异常消息.你需要添加一个catch(NUnit.Framework.AssertionException){throw;}或者类似的东西 - 请参阅我的回答. (15认同)
  • 尽管您的代码在功能上是健全的,但我不建议使用 ExpectedException 属性(因为它太受限制且容易出错)或在每个测试中编写 try/catch 块(因为它太复杂且容易出错)。使用设计良好的断言方法——由您的测试框架提供或您自己编写。您可以实现更好的代码,并且不必在不同的技术之间进行选择,也不必随着测试的变化而从一种技术更改为另一种技术。请参阅http://stackoverflow.com/a/25084462/2166177 (2认同)

Ica*_*ato 36

现在,2017年,您可以使用新的MSTest V2 Framework轻松完成:

Assert.ThrowsException<Exception>(() => myClass.MyMethodWithError());

//async version
await Assert.ThrowsExceptionAsync<SomeException>(
  () => myObject.SomeMethodAsync()
);
Run Code Online (Sandbox Code Playgroud)

  • 需要注意的重要一点是,`Assert.ThrowsException&lt;MyException&gt;` 的使用将仅针对所提供的异常类型进行测试,而不是针对其派生的任何异常类型进行测试。在我的示例中,如果被测试的 `Sub` 是要 `Throw` 一个 `MyInheritedException`(从基类 `MyException` 派生的类型),那么测试将 **失败**。 (3认同)
  • 仅当抛出“System.Exception”时,此操作才会成功。任何其他异常,例如“System.ArgumentException”,都将导致测试失败。 (2认同)
  • 如果您期望其他类型的异常,则应该对其进行测试...在您的示例中,您应该这样做:Assert.ThrowsException &lt;ArgumentException&gt;(()=&gt; myClass.MyMethodWithError()); (2认同)

小智 17

我是新来的,并没有评论或downvote的声誉,但想在安迪怀特的回复中指出这个例子中的一个缺陷:

try
{
    SomethingThatCausesAnException();
    Assert.Fail("Should have exceptioned above!");
}
catch (Exception ex)
{
    // whatever logging code
}
Run Code Online (Sandbox Code Playgroud)

在我熟悉的所有单元测试框架中,Assert.Fail通过抛出异常来工作,因此泛型catch实际上会掩盖测试的失败.如果SomethingThatCausesAnException()没有抛出,Assert.Fail意志,但永远不会冒出来给测试运行器指示失败.

如果需要捕获预期的异常(即,断言某些细节,例如异常上的消息/属性),捕获特定的预期类型而不是基本的Exception类很重要.这将允许Assert.Fail异常冒泡(假设您没有抛出与单元测试框架相同的异常类型),但仍允许对您的SomethingThatCausesAnException()方法抛出的异常进行验证.


Stu*_*tLC 15

从v 2.5开始,NUnit具有以下Assert用于测试异常的方法级别:

Assert.Throws,它将测试一个确切的异常类型:

Assert.Throws<NullReferenceException>(() => someNullObject.ToString());
Run Code Online (Sandbox Code Playgroud)

并且Assert.Catch,它将测试给定类型的异常,或者从此类型派生的异常类型:

Assert.Catch<Exception>(() => someNullObject.ToString());
Run Code Online (Sandbox Code Playgroud)

另外,在调试抛出异常的单元测试时,您可能希望阻止VS 在异常上中断.

编辑

只是举一个下面马修评论的例子,泛型的返回 Assert.ThrowsAssert.Catch异常类型的异常,然后您可以检查以进一步检查:

// The type of ex is that of the generic type parameter (SqlException)
var ex = Assert.Throws<SqlException>(() => MethodWhichDeadlocks());
Assert.AreEqual(1205, ex.Number);
Run Code Online (Sandbox Code Playgroud)

  • Roy Osherove在"单元测试艺术"第二版第2.6.2节中推荐了这一点. (2认同)
  • 我喜欢`Assert.Throws`,另外它返回异常,因此你可以在异常本身上写下更多的断言. (2认同)

byt*_*dev 11

不幸的是,MSTest STILL只有ExpectedException属性(只显示MS对MSTest的关注程度)IMO非常糟糕,因为它打破了Arrange/Act/Assert模式,并且它不允许您准确指定您期望异常的代码行发生在.

当我使用(/被客户端强制)使用MSTest时,我总是使用这个助手类:

public static class AssertException
{
    public static void Throws<TException>(Action action) where TException : Exception
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            Assert.IsTrue(ex.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
            return;
        }
        Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
    }

    public static void Throws<TException>(Action action, string expectedMessage) where TException : Exception
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            Assert.IsTrue(ex.GetType() == typeof(TException), "Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
            Assert.AreEqual(expectedMessage, ex.Message, "Expected exception with a message of '" + expectedMessage + "' but exception with message of '" + ex.Message + "' was thrown instead.");
            return;
        }
        Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

AssertException.Throws<ArgumentNullException>(() => classUnderTest.GetCustomer(null));
Run Code Online (Sandbox Code Playgroud)


Gra*_*amS 9

作为使用ExpectedException属性的替代方法,我有时为我的测试类定义了两个有用的方法:

AssertThrowsException() 接受一个委托并声明它会抛出预期的异常和预期的消息.

AssertDoesNotThrowException() 采用相同的委托并断言它不会抛出异常.

当您想要测试在一种情况下抛出异常而不是另一种情况时抛出异常时,此配对非常有用.

使用它们我的单元测试代码可能如下所示:

ExceptionThrower callStartOp = delegate(){ testObj.StartOperation(); };

// Check exception is thrown correctly...
AssertThrowsException(callStartOp, typeof(InvalidOperationException), "StartOperation() called when not ready.");

testObj.Ready = true;

// Check exception is now not thrown...
AssertDoesNotThrowException(callStartOp);
Run Code Online (Sandbox Code Playgroud)

好又整洁吧?

AssertThrowsException()AssertDoesNotThrowException()方法在一个公共基类上定义如下:

protected delegate void ExceptionThrower();

/// <summary>
/// Asserts that calling a method results in an exception of the stated type with the stated message.
/// </summary>
/// <param name="exceptionThrowingFunc">Delegate that calls the method to be tested.</param>
/// <param name="expectedExceptionType">The expected type of the exception, e.g. typeof(FormatException).</param>
/// <param name="expectedExceptionMessage">The expected exception message (or fragment of the whole message)</param>
protected void AssertThrowsException(ExceptionThrower exceptionThrowingFunc, Type expectedExceptionType, string expectedExceptionMessage)
{
    try
    {
        exceptionThrowingFunc();
        Assert.Fail("Call did not raise any exception, but one was expected.");
    }
    catch (NUnit.Framework.AssertionException)
    {
        // Ignore and rethrow NUnit exception
        throw;
    }
    catch (Exception ex)
    {
        Assert.IsInstanceOfType(expectedExceptionType, ex, "Exception raised was not the expected type.");
        Assert.IsTrue(ex.Message.Contains(expectedExceptionMessage), "Exception raised did not contain expected message. Expected=\"" + expectedExceptionMessage + "\", got \"" + ex.Message + "\"");
    }
}

/// <summary>
/// Asserts that calling a method does not throw an exception.
/// </summary>
/// <remarks>
/// This is typically only used in conjunction with <see cref="AssertThrowsException"/>. (e.g. once you have tested that an ExceptionThrower
/// method throws an exception then your test may fix the cause of the exception and then call this to make sure it is now fixed).
/// </remarks>
/// <param name="exceptionThrowingFunc">Delegate that calls the method to be tested.</param>
protected void AssertDoesNotThrowException(ExceptionThrower exceptionThrowingFunc)
{
    try
    {
        exceptionThrowingFunc();
    }
    catch (NUnit.Framework.AssertionException)
    {
        // Ignore and rethrow any NUnit exception
        throw;
    }
    catch (Exception ex)
    {
        Assert.Fail("Call raised an unexpected exception: " + ex.Message);
    }
}
Run Code Online (Sandbox Code Playgroud)


ito*_*son 5

使用 ExpectedExceptionAttribute 标记测试(这是 NUnit 或 MSTest 中的术语;其他单元测试框架的用户可能需要翻译)。