[c#] Assert로 예외를 테스트하여 예외가 발생하는지 확인하는 가장 좋은 방법

이것이 예외를 테스트하는 좋은 방법이라고 생각하십니까? 어떤 제안?

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

Assert.IsNotNull(exception);

MS Test를 사용하고 있습니다.



답변

제가 사용하는 몇 가지 다른 패턴이 있습니다. 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 )
        );
    }
}


답변

이제 2017 년에는 새로운 MSTest V2 프레임 워크를 사용하여 더 쉽게 할 수 있습니다 .

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

//async version
await Assert.ThrowsExceptionAsync<SomeException>(
  () => myObject.SomeMethodAsync()
);


답변

나는 여기에 새로 왔고 댓글을 달거나 반대표를 던질 평판이 없지만 예제의 결함을 지적하고 싶었습니다. Andy White의 답변 .

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

내가 익숙한 모든 단위 테스트 프레임 워크 Assert.Fail에서 예외를 throw하는 방식으로 작동하므로 일반적인 catch는 실제로 테스트 실패를가립니다. SomethingThatCausesAnException()던지지 않으면 Assert.Fail의지가 있지만 실패를 나타 내기 위해 테스트 러너에게 거품이 나오지 않습니다.

예상되는 예외를 포착해야하는 경우 (예 : 예외에 대한 메시지 / 속성과 같은 특정 세부 정보를 주장하려면) 기본 Exception 클래스가 아닌 특정 예상 유형을 포착하는 것이 중요합니다. 그러면 Assert.Fail예외가 버블 아웃 될 수 있지만 (단위 테스트 프레임 워크와 동일한 유형의 예외를 발생시키지 않는다고 가정) SomethingThatCausesAnException()메서드 에서 발생한 예외에 대한 유효성 검사는 계속 허용합니다 .


답변

v 2.5부터 NUnit 에는 Assert예외 테스트를위한 다음과 같은 메소드 수준이 있습니다 .

정확한 예외 유형을 테스트하는 Assert.Throws :

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

그리고 Assert.Catch는 주어진 유형의 예외 또는이 유형에서 파생 된 예외 유형을 테스트합니다.

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

제쳐두고, 예외를 던지는 단위 테스트를 디버깅 할 때 VS 가 예외깨뜨리는 것을 방지 할 수 있습니다. .

편집하다

아래에 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);


답변

불행히도 MSTest는 여전히 IMO가 Arrange / Act / Assert 패턴을 깨뜨리고 예외를 예상하는 코드 줄을 정확히 지정할 수 없기 때문에 IMO가 꽤 끔찍한 ExpectedException 속성 (MS가 MSTest에 얼마나 신경을 쓰는지 보여줍니다) 만 실제로 가지고 있습니다. 발생합니다.

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


답변

ExpectedException애트리뷰트 를 사용하는 대신 테스트 클래스에 유용한 두 가지 방법을 정의하는 경우가 있습니다.

AssertThrowsException() 대리자를 취하고 예상 된 메시지와 함께 예상되는 예외를 throw한다고 주장합니다.

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

멋지고 깔끔한 응?

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


답변

대부분의 .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입니다.