언제 조롱해야합니까?


137

나는 가짜와 가짜 객체에 대한 기본적인 이해를 가지고 있지만, 나는 확실히 어디서 조롱 사용하는 경우 / 대한 느낌이 아니에요 - 그것은이 시나리오에 적용 할 특히 여기를 .


외부 프로세스 (SMTP 서버, 메시지 버스 등)와 상호 작용할 수있는 외부 프로세스와의 상호 작용 만 조롱하는 것이 좋습니다. 데이터베이스를 조롱하지 말고 구현 세부 사항입니다. 여기에 대한 자세한 내용 : enterprisecraftsmanship.com/posts/when-to-mock
Vladimir

답변:


121

단위 테스트는 단일 방법을 통해 단일 코드 경로를 테스트해야합니다. 메소드 실행이 해당 메소드 외부에서 다른 오브젝트로 전달되고 다시 다시 전달되면 종속성이 있습니다.

실제 종속성으로 해당 코드 경로를 테스트 할 때는 단위 테스트가 아닙니다. 당신은 통합 테스트입니다. 그것은 좋고 필요하지만 단위 테스트는 아닙니다.

의존성이 버그 인 경우, 테스트는 오 탐지를 반환하는 방식으로 영향을받을 수 있습니다. 예를 들어, 종속성을 예기치 않은 널 (null)로 전달할 수 있으며 문서화 된대로 종속성이 널 (null)에서 발생하지 않을 수 있습니다. 테스트에서 null 인수 예외가 발생하지 않았으며 테스트가 통과되었습니다.

또한 불가능하지는 않지만 종속 객체가 테스트 중에 원하는 것을 정확하게 반환하도록하는 것이 어렵다는 것을 알 수 있습니다. 여기에는 테스트 내에서 예상되는 예외 발생이 포함됩니다.

모의가 그 의존성을 대체합니다. 종속 객체에 대한 호출에 대한 기대치를 설정하고, 원하는 테스트를 수행하기 위해 제공해야하는 정확한 반환 값 및 / 또는 예외 처리 코드를 테스트 할 수 있도록 throw 할 예외를 설정합니다. 이런 식으로 문제의 장치를 쉽게 테스트 할 수 있습니다.

TL; DR : 단위 테스트가 다루는 모든 의존성을 조롱하십시오.


164
이 답변은 너무 급진적입니다. 단위 테스트는 모두 동일한 응집 단위에 속하는 한 단일 방법 이상을 수행 할 수 있으며 수행해야합니다. 그렇지 않으면 너무 많은 조롱 / 가짜가 필요하기 때문에 복잡하고 취약한 테스트가 발생합니다. 실제로 테스트 대상 장치에 속하지 않는 종속성 만 조롱을 통해 교체해야합니다.
Rogério

10
이 답변은 너무 낙관적입니다. @Jan의 모의 객체의 단점을 통합하면 더 좋을 것입니다.
Jeff Axelrod

1
구체적으로 모의보다는 테스트에 대한 의존성을 주입하는 데 더 많은 논쟁이 아닌가? 답변에서 "mock"을 "stub"으로 대체 할 수 있습니다. 나는 당신이 중요한 의존성을 조롱하거나 스터 빙해야한다는 것에 동의합니다. 기본적으로 조롱 된 객체의 일부를 다시 구현하는 모의 무거운 코드가 많이 있습니다. 모의는 확실히 은색 총알이 아닙니다.
Draemon

2
단위 테스트가 다루는 모든 의존성을 조롱하십시오. 이것은 모든 것을 설명합니다.
Teoman shipahi 2016 년

2
TL; DR : 단위 테스트가 다루는 모든 의존성을 조롱하십시오. -이것은 실제로 훌륭한 접근 방법은 아니지만 모의 자작 자체는 모든 것을 조롱하지 마십시오. (downvoted)
p_champ 2016 년

167

모의 객체는 테스트 중인 클래스와 특정 인터페이스 간의 상호 작용테스트 하려는 경우에 유용합니다 .

예를 들어, 메소드 sendInvitations(MailServer mailServer)호출을 MailServer.createMessage()정확히 한 번만 테스트하고 정확히 한 번만 호출 MailServer.sendMessage(m)하며 MailServer인터페이스 에서 다른 메소드가 호출되지 않도록 테스트하려고합니다 . 이때 모의 객체를 사용할 수 있습니다.

실제 객체 MailServerImpl또는 테스트 를 전달하는 대신 모의 객체를 사용 TestMailServer하여 MailServer인터페이스 의 모의 구현을 전달할 수 있습니다. 우리가 mock을 전달하기 전에 MailServer, 우리는 그것을 "트레이닝"하여 어떤 메소드 호출을 기대하고 어떤 리턴 값을 리턴 할지를 알 수 있습니다. 결국 모의 객체는 모든 예상 메소드가 예상대로 호출되었다고 주장합니다.

이론적으로는 좋은 것처럼 보이지만 단점도 있습니다.

모의 단점

모의 프레임 워크가있는 경우 테스트중인 클래스에 인터페이스를 전달해야 할 때마다 모의 객체를 사용하려고합니다 . 이렇게하면 필요하지 않은 경우에도 상호 작용 테스트 를 마칠 수 있습니다. 불행히도, 원하지 않는 (우연한) 상호 작용 테스트는 좋지 않습니다. 구현시 필요한 결과를 생성하는 대신 특정 요구 사항이 특정 방식으로 구현되는지 테스트하고 있기 때문입니다.

다음은 의사 코드의 예입니다. MySorter클래스를 만들고 테스트하고 싶다고 가정 해 봅시다 .

// the correct way of testing
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert testList equals [1, 2, 3, 7, 8]
}


// incorrect, testing implementation
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert that compare(1, 2) was called once 
    assert that compare(1, 3) was not called 
    assert that compare(2, 3) was called once 
    ....
}

(이 예에서는 테스트하려는 빠른 정렬과 같은 특정 정렬 알고리즘이 아니라고 가정합니다.이 경우 후자의 테스트는 실제로 유효합니다.)

그러한 극단적 인 예에서 후자의 예가 왜 틀린지 분명합니다. 의 구현을 변경할 때 MySorter첫 번째 테스트는 테스트의 전체 지점 인 올바르게 정렬하는지 확인하는 데 큰 도움이됩니다. 코드를 안전하게 변경할 수 있습니다. 반면에 후자의 테스트는 항상 중단되며 적극적으로 유해합니다. 리팩토링을 방해합니다.

스터브로 mocks

모의 프레임 워크는 종종 덜 엄격한 사용법을 허용하는데, 여기서 우리는 메소드를 몇 번이나 호출해야하는지, 어떤 매개 변수가 예상되는지를 정확하게 지정할 필요가 없습니다. 스텁 으로 사용되는 모의 객체를 만들 수 있습니다.

sendInvitations(PdfFormatter pdfFormatter, MailServer mailServer)테스트하려는 방법 이 있다고 가정 해 봅시다 . PdfFormatter객체는 초대장을 만들 수 있습니다. 테스트는 다음과 같습니다.

testInvitations() {
   // train as stub
   pdfFormatter = create mock of PdfFormatter
   let pdfFormatter.getCanvasWidth() returns 100
   let pdfFormatter.getCanvasHeight() returns 300
   let pdfFormatter.addText(x, y, text) returns true 
   let pdfFormatter.drawLine(line) does nothing

   // train as mock
   mailServer = create mock of MailServer
   expect mailServer.sendMail() called exactly once

   // do the test
   sendInvitations(pdfFormatter, mailServer)

   assert that all pdfFormatter expectations are met
   assert that all mailServer expectations are met
}

이 예제에서는 실제로 PdfFormatter객체에 신경 쓰지 않으므로 호출을 조용히 수락 sendInvitation()하고이 시점에서 호출되는 모든 메서드에 대해 합리적인 통조림 반환 값을 반환하도록 훈련 시킵니다. 우리는 훈련 할이 방법 목록을 정확히 어떻게 만들었습니까? 테스트를 통과하고 테스트가 통과 할 때까지 메소드를 계속 추가했습니다. 메소드를 호출해야하는 이유를 알지 못하고 메소드에 응답하도록 스텁을 학습 했으므로 테스트에 대해 불평하는 모든 것을 추가했습니다. 우리는 행복합니다, 시험은 통과합니다.

그러나 나중에 더 멋진 PDF를 만들기 위해 변경 sendInvitations()하거나 사용하는 다른 클래스를 sendInvitations()사용하면 어떻게됩니까? 더 많은 메소드 PdfFormatter가 호출되고 스텁이이를 예상하도록 훈련 하지 않았기 때문에 테스트가 갑자기 실패 합니다. 그리고 일반적으로 이와 같은 상황에서 실패하는 테스트는 하나뿐이 아니라 직접 또는 간접적으로 sendInvitations()메소드 를 사용하는 테스트입니다 . 더 많은 교육을 추가하여 모든 테스트를 수정해야합니다. 또한 어떤 메소드가 필요하지 않은지 알 수 없으므로 더 이상 필요하지 않은 메소드를 제거 할 수 없습니다. 다시, 그것은 리팩토링을 방해한다.

또한 테스트의 가독성은 끔찍하게 고통을 겪었습니다. 우리가 원했기 때문에 작성하지 않았지만해야했기 때문에 많은 코드가 있습니다. 그 코드를 원하는 사람은 우리가 아닙니다. 모의 객체를 사용하는 테스트는 매우 복잡해 보이며 종종 읽기가 어렵습니다. 테스트는 독자가 이해하는 데 도움이되며 테스트중인 클래스가 어떻게 사용되어야하는지 간단하고 간단해야합니다. 읽을 수 없다면 아무도 그것을 유지하지 않을 것입니다. 실제로 유지 관리하는 것보다 삭제하는 것이 더 쉽습니다.

그것을 고치는 방법? 용이하게:

  • 가능하면 모의 대신 실제 클래스를 사용해보십시오. 진짜를 사용하십시오 PdfFormatterImpl. 가능하지 않으면 실제 클래스를 변경하여 가능하게하십시오. 테스트에서 클래스를 사용할 수 없다는 것은 일반적으로 클래스의 일부 문제를 나타냅니다. 문제를 해결하는 것은 상생의 상황입니다. 수업을 수정하고 더 간단한 테스트를합니다. 다른 한편으로, 그것을 고치지 않고 목을 사용하는 것은 승리의 상황이 아닙니다. 실제 클래스를 고치지 않았고 더 리팩토링을 방해하는 더 복잡하고 읽기 어려운 테스트가 있습니다.
  • 각 테스트에서 모의하는 대신 인터페이스의 간단한 테스트 구현을 작성하고이 테스트 클래스를 모든 테스트에서 사용하십시오. TestPdfFormatter아무것도하지 않는 창조 . 이렇게하면 모든 테스트에서 한 번만 변경할 수 있으며 스텁을 훈련시키는 긴 설정으로 인해 테스트가 복잡해지지 않습니다.

대체로 모의 객체는 사용하지만 신중하게 사용하지 않을 경우 종종 나쁜 관행, 구현 세부 사항 테스트, 리팩토링을 방해하고 읽기가 어렵고 테스트를 유지하기가 어려운 경우가 많습니다 .

모의 단점에 대한 자세한 내용은 Mock Objects : Shortcomings and Use Cases 도 참조하십시오 .


1
잘 생각 된 답변이며, 나는 대부분 동의합니다. 단위 테스트는 화이트 박스 테스트이므로 구현을 변경하여 더 멋진 PDF를 보내려면 테스트를 변경 해야하는 것은 부당하지 않을 수도 있습니다. 때로는 모의가 보일러 플레이트를 많이 사용하는 대신 스텁을 빠르게 구현하는 유용한 방법이 될 수 있습니다. 그러나 실제로는 이러한 간단한 경우에는 사용되지 않습니다.
Draemon

1
모의의 요점은 테스트가 일관성이 있다는 것입니다. 테스트를 실행할 때마다 다른 프로그래머에 의해 구현이 지속적으로 변경 될 수있는 객체를 조롱 할 필요가 없으며 일관성있는 테스트 결과를 얻을 필요가 있습니까?
PositiveGuy

1
매우 좋고 관련성 높은 점 (특히 테스트 취약성에 관한 것). 어릴 때 모의를 많이 사용했지만 이제는 모의에 크게 의존하는 단위 테스트를 잠재적으로 일회용으로 간주하고 통합 테스트 (실제 구성 요소 포함)에 더 중점을 둔 단위 테스트를 고려합니다.
Kemoda

6
"테스트에서 수업을 사용할 수 없다는 것은 보통 수업에 문제가 있음을 나타냅니다." 클래스가 서비스 인 경우 (예 : 데이터베이스에 액세스하거나 웹 서비스에 프록시) 외부
종속성

1
그러나 sendInvitations ()를 변경하면 나중에 어떻게됩니까? 테스트중인 코드가 수정되면 더 이상 이전 계약을 보장하지 않으므로 실패해야합니다. 그리고 일반적으로 이와 같은 상황에서 실패하는 테스트는 하나뿐이 아닙니다 . 이 경우 코드가 깨끗하게 구현되지 않은 것입니다. 의존성에 대한 메소드 호출의 검증은 한 번만 (적절한 단위 테스트에서) 테스트해야합니다. 다른 모든 클래스는 모의 인스턴스 만 사용합니다. 따라서 단위 테스트와 통합을 혼합하면 어떤 이점도 얻지 못합니다.
크리스토퍼 윌

55

경험 법칙 :

테스트중인 함수가 복잡한 객체를 매개 변수로 필요로하고이 객체를 인스턴스화하는 것 (예를 들어, TCP 연결을 설정하려는 경우)은 모의를 사용합니다.


4

테스트하려는 코드 단위에서 종속성이있을 때 "그냥"필요한 객체를 조롱해야합니다.

예를 들어 코드 단위에서 일부 로직을 테스트하려고하지만 다른 객체에서 무언가를 가져와야 할 때이 종속성에서 반환되는 것이 테스트하려는 대상에 영향을 줄 수 있습니다.

주제에 대한 훌륭한 팟 캐스트는 여기 에서 찾을 수 있습니다


링크는 이제 의도 한 에피소드가 아닌 현재 에피소드로 라우팅됩니다. 의도 된 팟 캐스트는이 hanselminutes.com/32/mock-objects 입니까?
C 퍼킨스
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.