Assert를 사용하여 예외가 발생했는지 어떻게 확인합니까?


830

Assert예외가 발생했는지 확인 하려면 어떻게해야합니까 (또는 다른 테스트 클래스?)?


어떤 단위 테스트 프레임 워크를 사용하고 있습니까?
— Kevin Pullin

3
Visual Studio 통합
— Alex

4
ExpectedException 속성이 도움이되지 않습니까? 참조 : msdn.microsoft.com/en-us/library/…
— shahkalpesh

2
웃긴, 나는 이것에 대한 답을 찾기를 끝내고 stackoverflow.com/questions/741029/testing-exceptions 에서 찾았습니다 .
— dfjacobs

답변:


978

"Visual Studio Team Test"의 경우 ExpectedException 특성을 테스트 방법에 적용합니다.

여기 문서의 샘플 : 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");
}

25
위의 ExpectedException 속성은 NUnit에서도 작동합니다 (그러나 [TestMethod]는 [Test] 여야 함).
— dbkk 2016 년

5
@dbkk : NUnit에서 정확히 동일하게 작동하지 않습니다. 메시지는 예외 메시지를 matcvh해야하는 문자열로 취급됩니다 (그리고 IU가 더 의미가 있다고 생각합니다)
— Ruben Bartelink

29
이 속성은 작업을 수행하며 C # 프로그래머를위한 기본 제공 기능이지만 충분히 유연하지 않기 때문에 사용하지 않는 것이 좋습니다. 테스트 설정 코드에 의해 예외 유형이 발생하면 어떻게되는지 생각해보십시오 : 테스트는 통과했지만 실제로 예상 한 것은 수행하지 않았습니다. 또는 예외 객체의 상태를 테스트하려면 어떻게해야합니까? 일반적으로 전체 메시지를 테스트하는 대신 StringAssert.Contains (e.Message ...)를 사용하고 싶습니다. 다른 답변에서 설명한대로 어설 션 방법을 사용하십시오.
— steve

3
NUnit에서는 ExpectedException을 사용하지 마십시오. NUnit 3.0에서는 삭제됩니다. 내가 사용 Assert.Throws () <SpecificException> 선호
— 테렌스

5
MsTest 내에서 Assert.ThrowsException <T> 및 Assert.ThrowsExceptionAsync <T>를 사용할 수 있습니다.
— 고팔 크리슈 난

257

일반적으로 테스트 프레임 워크에 이에 대한 답변이 있습니다. 그러나 융통성이 충분하지 않으면 언제든지 다음을 수행 할 수 있습니다.

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
}

Exception을 반드시 잡아야하는 경우 Assert.Fail ()을 다시 발생시켜야합니다. 그러나 실제로, 이것은 당신이 이것을 손으로 쓰면 안된다는 신호입니다. 테스트 프레임 워크에서 옵션을 확인하거나 테스트를 위해 더 의미있는 예외를 던질 수 있는지 확인하십시오.

catch (AssertionException) { throw; }

어떤 종류의 예외를 잡아야 하는지를 포함하여 원하는 방식으로이 방법을 적용 할 수 있어야합니다. 특정 유형 만 예상하면 다음 catch과 같이 블록을 마무리하십시오 .

} catch (GoodException) {
} catch (Exception) {
    // not the right kind of exception
    Assert.Fail();
}

20
+1, 예외 유형 이외의 어설 션을 만들어야하는 경우 속성 대신이 방법을 사용합니다. 예를 들어, 예외 인스턴스의 특정 필드가 특정 값으로 설정되어 있는지 확인해야하는 경우
— Pavel Repin 2016 년

2
오류 메시지를 지정할 필요는 없습니다. 이것으로 충분합니다 : [ExpectedException (typeof (ArgumentException))]
— mibollma

5
이 솔루션이 최고라고 생각합니다. 테스트가 간단하다면 [ExpectedException (typeof (ArgumentException))]에 사용되지만, 제 생각에는 게으른 솔루션이며 편안하게 함정에 빠질 수 있습니다. 이 솔루션을 사용하면보다 정확한 테스트를 수행 할 수있는 특정 제어 기능을 제공 할 수있을뿐만 아니라 테스트 실행 보고서에 테스트 Writeline을 작성하여 예외가 예상대로 처리 될 수 있습니다.
— evilfish

12
Assert.Fail ()이 예외를 발생 시키므로주의하십시오.
— 조나스

4
@ Vinnyq12 의미하는 것은 위 예제의 첫 번째 테스트는 절대 실패하지 않을 것입니다. 예외가 발생하면 (ExpectedExceptionAttribute에 의해 "catch"가 아닌) 테스트 실패
— Jonas

113

이것을 구현하기 위해 내가 선호하는 방법은 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());
}

나머지 단위 테스트 구문과 훨씬 비슷하게 보이고 동작합니다.


1
bool 플래그를 제거하고 더 작은 구현을 위해 호출 직후에 줄을 던지십시오.
— gt

11
이를 개선하는 유일한 방법은 예외의 속성과 같은 것이 올바른지 계속 주장 할 수 있도록 함수가 포착 된 예외를 리턴하도록하는 것입니다.
— Mark Hildreth

2
감사! 하나의 방법으로 여러 예외를 테스트하는 짧은 방법이기 때문에 이것은 나에게 가장 좋은 접근법 인 것 같습니다. 또한 훨씬 더 읽기 쉽습니다.
— David Sherret

2
@MickeyPerlstein 속성은 테스트에 대한 AAA 규칙을 위반합니다. 특히, 법에 도달하기 전에 어레인지가 예외를 던지면 테스트는 ... eek!
— freedomn-m 2016

2
V2 지원 - 마이크로 소프트는 마침내 MSTEST 업데이트에 라운드를 가지고 Assert.ThrowsException<T>와 Assert.ThrowsExceptionAsync<T>- 볼 blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/...
— Quango

62

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


60

원래 ExpectedException속성 이 없었던 MSTest를 사용하는 경우 다음을 수행 할 수 있습니다.

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

3
이것은 작동하지만 논리가 지나치게 복잡하기 때문에 일반적으로 권장하지 않습니다. 복잡한 말은 아니지만 여러 테스트 (10, 100, 테스트)에 대해이 코드 블록을 작성하는지 고려하십시오. 이 논리는 잘 설계된 어설 션 방법으로 구성해야합니다. 다른 답변을 참조하십시오.
— steve

35

다음과 같이 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을 사용하는 것입니다. xUnit.NET은 다른 모든 실수에서 배워 개선 된 매우 현대적이고 미래 지향적이며 확장 가능한 단위 테스트 프레임 워크입니다. 이러한 개선 사항 중 하나는 Assert.Throws이며 예외를 주장하는 데 훨씬 더 좋은 구문을 제공합니다.

github에서 xUnit.NET을 찾을 수 있습니다 : http://xunit.github.io/


4
NUnit 2.5는 이제 Assert.Throws 스타일 구문도 지원합니다. nunit.com/index.php?p=releaseNotes&r=2.5
— Alconja

ExpectedException을 사용할 때 예외에 대해 알리기 위해 단위 테스트가 중지되는 방식은 나를 미치게합니다. MS가 자동 테스트에서 수동 단계를 수행하는 것이 좋은 생각이라고 생각한 이유는 무엇입니까? 링크 주셔서 감사합니다.
— 개미

@Ant : MS는 NUnit을 복사했습니다 ... 그래서 실제 질문은 NUnit이 왜 그것이 좋은 생각이라고 생각 했습니까?
— jrista

28

MSTest (v2)에는 이제 다음과 같이 사용할 수있는 Assert.ThrowsException 함수가 있습니다.

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

너겟으로 설치할 수 있습니다 : Install-Package MSTest.TestFramework


2018 년에는 테스트중인 장치 만 던지고 다른 코드는 검사하지 않기 때문에 모범 사례로 간주됩니다.
— CM

24

내가 작업중 인 프로젝트에서 우리는 이것을하는 또 다른 솔루션을 가지고 있습니다.

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

깔끔하지 않습니까?)


14

테스트 방법의 속성입니다. Assert를 사용하지 않습니다. 다음과 같습니다 :

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

13

nget / xUnit 스타일의 Assert.Throws () 구문을 MsTest에 추가하는 PM> Install-Package MSTestExtensions 를 사용하여 Nuget에서 패키지를 다운로드 할 수 있습니다 .

고급 지침 : 어셈블리를 다운로드하고 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


예를 주셔서 감사합니다. Assert.DoesNotThrow () 또는 이와 동등한 것을 테스트하는 방법에 대한 예가 있습니까?
— Lane Goolsby 2016 년

10

간단한 한 줄로이를 달성 할 수 있습니다.

작업 foo.bar()이 비동기 인 경우 :

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

foo.bar()비동기가 아닌 경우

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

1
다른 답변이 많이 있습니다. 저는 예외 유형으로 알려진 실패 조건을 테스트하는 간단한 방법을 찾고 있었으므로 가장 읽기 쉬운 테스트 사례가되었습니다. 참고 : 예외 유형은 표준 try-catch와 같은 상속 된 예외 클래스에서 일치하지 않으므로 위의 예는 예를 들어 트랩하지 않습니다 ArgumentException. 테스트 할 고급 기준이있는 경우 이전 Try Catch 및 예외 응답 테스트는 여전히 선호되지만, 많은 경우에 이것은 많은 도움이됩니다!
— Chris Schaller

5

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'대신 'propagates'를 사용합니다. 예외가 호출에서 전파되는지 여부 만 테스트 할 수 있기 때문입니다. 예외가 발생했는지 직접 테스트 할 수 없습니다. 그러나 이미지 던지기를 의미한다고 생각합니다.

최종 생각

이러한 종류의 접근 방식으로 전환하기 전에 테스트에서 예외 유형 만 확인했을 때 ExpectedException 특성을 사용하고 추가 유효성 검사가 필요한 경우 try / catch 블록을 사용하는 것을 고려했습니다. 그러나 각 테스트에 사용할 기술에 대해 생각해야 할뿐만 아니라 필요에 따라 코드를 한 기술에서 다른 기술로 변경하는 것이 쉬운 일이 아닙니다. 하나의 일관된 접근 방식을 사용하면 정신적 인 노력이 절약됩니다.

요약하면,이 접근 방식은 사용 편의성, 유연성 및 견고성 (잘못하기 어렵다)입니다.


4

위의 @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))
                    );
            }
        }
    }
}

2
흠 ... 나는 그 아이디어를 이해하지만 그것이 더 낫다는 것에 확신이 서지 않습니다. 특정 예외가 발생했는지 확인한다고해서 다른 모든 예외가 어설 션 오류로 마무리되어야하는 것은 아닙니다. 알 수없는 예외는 다른 어설 션 작업에서와 마찬가지로 스택을 버블 링해야합니다.
— Crono

@Martin 나는 exceptionOther와 관련된 코드를 제거하고 간단히 두 번째 catch 절
— Tom Lint

4

다른 테스트 클래스 사용을 언급 했으므로ExpectedException 속성 보다 더 나은 옵션 은 Shoudly 's should.Throw 를 사용하는 것 입니다.

Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });

고객 이 주문 을 작성하려면 주소 가 있어야 한다는 요구 사항이 있다고 가정 해 봅시다 . 그렇지 않은 경우 메소드는을 생성해야합니다 . 그런 다음 우리는 다음과 같이 쓸 수 있습니다.CreateOrderForCustomerArgumentException

[TestMethod]
public void NullUserIdInConstructor()
{
  var customer = new Customer(name := "Justin", address := null};

  Should.Throw<ArgumentException>(() => {
    var order = CreateOrderForCustomer(customer) });
}

이것은 사용하는 것보다 낫다 ExpectedException오류를 발생시킬 대상을 구체적으로 지정하기 때문에 속성을 . 이를 통해 테스트 요구 사항이보다 명확 해지고 테스트 실패시 진단이 쉬워집니다.

또한이 주 Should.ThrowAsync비동기 방식의 테스트를 위해.


4

대안으로 테스트를 시도 할 수 있습니다. 실제로 테스트에서 다음 두 줄로 예외가 발생합니다.

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

4

VS 내장 단위 테스트에서 단순히 "예외"가 발생했는지 확인하고 싶지만 유형을 모르는 경우 모두 catch를 사용할 수 있습니다.

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

3

글쎄, 나는 여기에 다른 사람들이 전에 말한 것을 요약 할 것입니다 ... 어쨌든, 여기에 좋은 답변에 따라 빌드 한 코드가 있습니다 :) 모든 것은 복사하고 사용하는 것입니다 ...

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


2

이것은 어떤 테스트 프레임 워크를 사용하고 있습니까?

예를 들어 MbUnit에서 예상 예외를 속성으로 지정하면 실제로 예상되는 예외를 얻을 수 있습니다.

[ExpectedException(typeof(ArgumentException))]

2

NUnit 을 사용하는 경우 다음을 시도하십시오.

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

2

NFluent 라는 멋진 라이브러리가있어 어설 션 작성 속도 가 빨라집니다 .

예외를 던지기위한 주장을 작성하는 것은 매우 간단합니다.

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

1

이것은 오래된 질문이지만 토론에 새로운 생각을 추가하고 싶습니다. Arrange, Act, Assert 패턴을 예상, Arrange, Act, Assert로 확장했습니다. 예상되는 예외 포인터를 만든 다음 지정된 예외 포인터를 지정할 수 있습니다. 이것은 catch 블록에서 Asserts를 수행하는 것보다 깨끗하게 느껴지므로 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);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.