아무것도 반환하지 않는 순수한 메소드에 대한 테스트를 어떻게 작성해야합니까?


13

가치 검증을 다루는 많은 클래스가 있습니다. 예를 들어, RangeValidator클래스는 값이 지정된 범위 내에 있는지 확인합니다.

모든 유효성 검사기 클래스에는 값 is_valid(value)을 반환 True하거나 False값에 따라 달라지며 ensure_valid(value)지정된 값을 확인하고 값이 유효한 경우 아무 것도 수행하지 않거나 값이 미리 정의 된 규칙과 일치하지 않으면 특정 예외가 발생합니다.

현재이 방법과 관련된 두 가지 단위 테스트가 있습니다.

  • 유효하지 않은 값을 전달하고 예외가 발생했는지 확인합니다.

    def test_outside_range(self):
        with self.assertRaises(demo.ValidationException):
            demo.RangeValidator(0, 100).ensure_valid(-5)
    
  • 유효한 값을 전달하는 것입니다.

    def test_in_range(self):
        demo.RangeValidator(0, 100).ensure_valid(25)
    

두 번째 테스트는 예외를 처리하면 실패하고 ensure_valid아무 것도 처리하지 않으면 성공합니다 assert. 내부에 s 가 없다는 사실이 이상하게 보입니다. 그러한 코드를 읽는 사람은 즉시 아무것도하지 않는 것처럼 보이는 테스트가 왜 있는지 스스로에게 묻습니다 .

값을 반환하지 않고 부작용이없는 방법을 테스트 할 때 이것이 현재 관행입니까? 아니면 다른 방법으로 테스트를 다시 작성해야합니까? 아니면 내가하고있는 일을 설명하는 의견을 적어보십시오.



11
Pedantic point, 그러나 인수를 사용하지 않고 ( self참조를 위해 저장 ) 결과를 반환하지 않는 함수가 있으면 순수한 함수가 아닙니다.
David Arno

10
@DavidArno : 이것은 현명한 요점이 아니며 문제의 핵심으로갑니다.이 방법은 부정확 하기 때문에 정확하게 테스트하기가 어렵습니다 .
Jörg W Mittag

@DavidArno 더 많은 논점은, "아무것도하지 않는"방법을 가질 수 있다는 것입니다 ( "결과를 반환하지 않는다"는 가정 void은 많은 언어에서 호출 되고 어리석은 규칙이있는 "단위 유형을 반환합니다"라고 해석됩니다 ). 루프 ( "결과를 반환하지 않는 경우에도 작동합니다"는 실제로 결과를 반환하지 않음을 의미합니다.
Derek Elkins가 SE를

종류의 순수 기능 unit -> unit유닛 마술 특수 맡았다 표준 데이터 형식 대신에 뭔가를 고려 모든 언어는. 불행히도, 그것은 단지 단위를 반환하고 다른 것은 수행하지 않습니다.
Phoshi

답변:


21

대부분의 테스트 프레임 워크에는 "Does n't throw"에 대한 명시 적 주장이 있습니다 (예 : Jasmine has expect(() => {}).not.toThrow();및 nUnit 및 friends도 있습니다).


1
그리고 테스트 프레임 워크에 그러한 주장이 없다면, 항상 똑같은 일을하는 로컬 메소드를 작성하여 코드를 자명하게 만들 수 있습니다. 좋은 생각.
Arseni Mourzenko

7

이것은 사용되는 언어와 프레임 워크에 크게 의존합니다. 로 말하면 NUnit, Assert.Throws(...)방법 이 있습니다. 람다 메소드를 전달할 수 있습니다.

Assert.Throws(() => rangeValidator.EnsureValid(-5))

에서 실행됩니다 Assert.Throws. 람다에 대한 호출은 대부분 try { } catch { }블록에 의해 랩핑되며 예외가 발생하면 어설 션이 실패합니다.

프레임 워크에서 이러한 방법을 제공하지 않으면 호출을 직접 래핑하여 해결할 수 있습니다 (C #으로 작성 중).

// assert that the method does not fail
try
{
    rangeValidator.EnsureValid(50);
}
catch(Exception e)
{
    Assert.IsTrue(false, $"Exception: {e.Message}");
}

// assert that the method does fail
try
{
    rangeValidator.EnsureValid(50);
    Assert.IsTrue(false, $"Method is expected to throw an exception");
}
catch(Exception e)
{
}

이것은 의도를보다 명확하게하지만 코드를 어느 정도 혼란스럽게 만듭니다. (물론이 모든 것을 방법으로 감쌀 수 있습니다.) 결국 그것은 당신에게 달려 있습니다.

편집하다

Doc Brown이 의견에서 지적했듯이 문제는 메소드가 throw되는 것이 아니라 throw되지 않음을 나타내는 것이 었습니다. NUnit에는 그에 대한 주장도 있습니다

Assert.DoesNotThrow(() => rangeValidator.EnsureValid(-5))

1

어설 션이 필요하지 않은 이유와 왜 잊지 않았는지 명확하게 설명을 추가하십시오.

다른 답변에서 볼 수 있듯이 다른 코드는 코드를 더 복잡하고 어수선하게 만듭니다. 이 의견을 통해 다른 프로그래머는 테스트의 의도를 알 수 있습니다.

그러나 이런 종류의 테스트는 예외가되어야합니다 (말장난 의도는 없음). 당신이 그런 것을 정기적으로 쓰는 것을 발견한다면, 아마도 테스트가 디자인이 최적이 아니라고 알려줄 것입니다.


1

일부 메소드가 올바르게 호출 (또는 호출되지 않음) 될 수도 있습니다.

예를 들어 :

public void SendEmail(User user)
     ... construct email ...
     _emailSender.Send(email);

테스트에서 :

emailSenderMock.VerifyIgnoreArgs(service =>
    service.Send(It.IsAny<Email>()),
    Times.Once
);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.