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


97

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

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

Assert.IsNotNull(exception);

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

답변:


137

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

16
많은 단위 테스트 프레임 워크는 어설 션 실패를 예외로 구현합니다. 따라서 두 번째 경우의 Assert.Fail ()은 catch (Exception) 블록에 의해 포착되어 예외 메시지를 숨 깁니다. catch (NUnit.Framework.AssertionException) {throw;} 또는 이와 유사한 것을 추가해야합니다. 내 대답을 참조하십시오.
GrahamS 2009

@Graham-나는 이것을 내 머리 꼭대기에 입력했습니다. 일반적으로 유형 외에도 예외 메시지를 인쇄합니다. 요점은 두 번째 핸들러가 어설 션 실패를 포착하고 오류에 대한 정보를 "refail"하기 때문에 테스트가 실패한다는 것입니다.
tvanfosson

1
귀하의 코드는 기능적으로 건전하지만 ExpectedException 속성을 사용하거나 (너무 제한적이고 오류가 발생하기 쉬우므로) 각 테스트에서 try / catch 블록을 작성하지 않는 것이 좋습니다 (너무 복잡하고 오류가 발생하기 쉬우므로). 테스트 프레임 워크에서 제공하거나 직접 작성하여 잘 설계된 assert 메서드를 사용하십시오. 더 나은 코드를 얻을 수 있으며 테스트가 변경됨에 따라 다른 기술 중에서 선택하거나 다른 기술을 변경할 필요가 없습니다. stackoverflow.com/a/25084462/2166177
steve

참고로-저는이 Assert.Throws두 경우를 모두 다루는 강력한 형식의 메서드 를 사용하는 xUnit을 사용했습니다 .
tvanfosson

ExpectedException 속성은 예외가 발생하는지 여부를 테스트하기위한 불쾌하고 오래된 방식입니다. 아래 내 전체 답변을 참조하십시오.
bytedev

45

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

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

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

System.Exception던진 경우에만 성공합니다 . 같은 다른 사람 System.ArgumentException은 테스트에 실패합니다.
sschoof

2
다른 유형의 예외가 예상되는 경우 테스트해야합니다. 예제에서 다음을 수행합니다. Assert.ThrowsException <ArgumentException> (() => myClass.MyMethodWithError ());
Icaro Bombonato

2
주목해야 할 중요한 점은를 사용 Assert.ThrowsException<MyException>하면 파생 된 예외 유형이 아니라 제공된 예외 유형에 대해서만 테스트된다는 것입니다. 내 예제에서 테스트하는 경우 Sub에 있었다 (기본 클래스에서 파생 형 ), 다음 테스트는 것이다 실패 . ThrowMyInheritedExceptionMyException
Ama 2011

테스트를 확장하고 예외 유형과 파생 된 유형을 허용하려면 Try { SubToTest(); Assert.Fail("...") } Catch (AssertFailedException e) {throw;} Catch (MyException e) {...}. 의 최상의 중요성 참고 Catch (AssertFailedException e) {throw;}(allgeek에서 참조 코멘트)
아마노

16

나는 여기에 새로 왔고 댓글을 달거나 반대표를 던질 평판이 없지만 예제의 결함을 지적하고 싶었습니다. 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()메서드 에서 발생한 예외에 대한 유효성 검사는 계속 허용합니다 .


15

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

2
Roy Osherove는 The Art of Unit Testing, second ed, section 2.6.2에서 이것을 권장합니다.
아비

2
나는 Assert.Throws또한 예외를 반환하므로 예외 자체에 대한 추가 주장을 작성할 수 있습니다.
Matthew

질문은 NUnit이 아닌 MSTest에 대한 것이 었습니다.
bytedev

@nashwan OP의 원래 질문에는 해당 자격이 없었으며 태그 지정은 여전히 ​​MS-Test에 적합하지 않습니다. 현재로서는 C #, .Net, 단위 테스트 질문입니다.
StuartLC

11

불행히도 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));

10

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

4

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


MSTest (고용주가 자주 강요하는)를 사용해야하는 경우 아래 내 대답을 참조하십시오.
bytedev

4

ExpectedExceptionAttribute로 테스트를 표시합니다 (이는 NUnit 또는 MSTest의 용어입니다. 다른 단위 테스트 프레임 워크의 사용자는 번역해야 할 수 있음).


ExpectedExceptionAttribute (아래 내 게시물에 제공된 이유)를 사용하지 마십시오. NUnit에는 Assert.Throws <YourException> ()이 있으며 MSTest의 경우 아래 AssertException 클래스와 같은 것을 사용하십시오.
bytedev

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>);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.