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

时间:2009-04-12 00:12:03

标签: c# .net unit-testing

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

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

Assert.IsNotNull(exception);

我正在使用MS Test。

10 个答案:

答案 0 :(得分:125)

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

第一种情况:

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

第二种情况:

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

答案 1 :(得分:24)

现在,2017年,您可以使用新的MSTest V2 Framework

轻松完成
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7]
[2, 3, 4, 5, 6, 7, 8]

答案 2 :(得分:17)

我是新来的,没有评论或贬低的声誉,但想在Andy White's reply中的例子中指出一个缺陷:

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

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

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

答案 3 :(得分:15)

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

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

Assert.Throws<NullReferenceException>(() => someNullObject.ToString());

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

Assert.Catch<Exception>(() => someNullObject.ToString());

顺便说一句,在调试抛出异常的单元测试时,您可能希望阻止VS breaking on the exception

修改

仅举一个Matthew下面的评论示例,通用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);

答案 4 :(得分: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.");
    }
}

使用示例:

AssertException.Throws<ArgumentNullException>(() => classUnderTest.GetCustomer(null));

答案 5 :(得分: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);

很好又干净吧?

我的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);
    }
}

答案 6 :(得分:4)

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

答案 7 :(得分:3)

对于大多数.net单元测试框架,您可以在测试方法上放置[ExpectedException]属性。但是,这不能告诉您异常发生在您期望的那一刻。这就是xunit.net可以提供帮助的地方。

使用xunit,你有Assert.Throws,所以你可以这样做:

    [Fact]
    public void CantDecrementBasketLineQuantityBelowZero()
    {
        var o = new Basket();
        var p = new Product {Id = 1, NetPrice = 23.45m};
        o.AddProduct(p, 1);
        Assert.Throws<BusinessException>(() => o.SetProductQuantity(p, -3));
    }

[Fact]是[TestMethod]

的xunit等价物

答案 8 :(得分:0)

建议使用NUnit的干净委托语法。

测试ArgumentNullExeption的示例:

[Test]
[TestCase(null)]
public void FooCalculation_InvalidInput_ShouldThrowArgumentNullExeption(string text)
{
    var foo = new Foo();
    Assert.That(() => foo.Calculate(text), Throws.ArgumentNullExeption);

    //Or:
    Assert.That(() => foo.Calculate(text), Throws.Exception.TypeOf<ArgumentNullExeption>);
}

答案 9 :(得分:0)

以下是我为测试调用确保成功状态代码扩展方法时抛出的 HttpRequestException 所做的操作(.NET Core 3.1 MS 测试):

var result = await Assert.ThrowsExceptionAsync<HttpRequestException>(async ()=>
{
    await myService.SomeMethodAsync("test value");
}

Assert.AreEqual("Response status code does not indicate success: 401 (Unauthorized).", result);

以上测试方法 SomeMethodAsync 在这种情况下是否抛出 T 类型的异常 HttpRequestException 然后我可以做更多的断言,例如测试它不是 null,属于 HttpRequestException 类型并且异常消息与上面示例中的 401 Unauthorised 字符串匹配(HttpRequestException 基于 Exception 类所以你可以访问它的所有属性和方法)。