[Unit-testing] 언제 내가 조롱해야합니까?


Answers

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

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

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

이론적으로는 좋지만 일부 단점도 있습니다.

가짜 단점

모의 프레임 워크가 있다면 모의 객체를 사용할 때마다 테스트를 통해 클래스에 인터페이스를 전달해야 할 필요가 있습니다. 이렇게하면 필요하지 않은 경우에도 상호 작용테스트하게됩니다 . 유감스럽게도 상호 작용에 대한 원치 않는 (우발적 인) 테스트는 좋지 않습니다. 특정 요구 사항이 특정 방식으로 구현되었는지 테스트했기 때문에 구현 결과가 필요한 결과를 산출하지 못하기 때문입니다.

다음은 의사 코드의 예입니다. MySorter 클래스를 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 의 구현을 변경하면 첫 번째 테스트는 테스트의 전체 지점 인 올바르게 정렬되는지 확인하는 작업을 훌륭하게 수행합니다. 코드를 안전하게 변경할 수 있습니다. 반면에, 후자의 테스트는 항상 중단되고 그것은 적극적으로 해를 끼칩니다. 그것은 리펙토링을 방해합니다.

스텁 같은 모의

모의 프레임 워크는 종종 덜 엄격한 사용을 허용합니다. 여기서 우리는 메소드가 호출되어야하는 횟수와 예상되는 매개 변수를 정확히 지정할 필요가 없습니다. stubs 으로 사용되는 mock 객체를 생성 할 수 있습니다.

테스트 할 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 객체를 신경 쓰지 않고 모든 호출을 조용히 PdfFormatter 시점에서 sendInvitation() 이 호출하는 모든 메소드에 대해 합리적인 준비된 반환 값을 반환하도록 교육합니다. 우리는 어떻게이 훈련 방법 목록을 정확히 제시 했습니까? 우리는 단순히 테스트를 실행하고 테스트가 통과 될 때까지 메소드를 계속 추가했습니다. 우리가 테스트 콜을 호출해야하는 이유를 알지 못하고 메소드에 응답하도록 스텁을 훈련 시켰습니다. 테스트에서 불평하는 모든 것을 간단히 추가했습니다. 시험에 합격하면 행복합니다.

그러나 나중에 sendInvitations() 또는 sendInvitations() 사용하는 다른 클래스를 변경하여 더욱 멋진 PDF를 만들면 어떻게됩니까? 우리의 테스트는 갑자기 실패합니다. 왜냐하면 PdfFormatter 메소드가 더 많이 호출되기 때문에 우리는 테스트를하지 않아서 기대할 수 없습니다. 그리고 보통 이런 상황에서 실패하는 것은 하나의 테스트 일뿐만 아니라 sendInvitations() 메소드를 직접 또는 간접적으로 사용하는 테스트입니다. 우리는 더 많은 교육을 추가하여 모든 테스트를 수정해야합니다. 또한 더 이상 필요하지 않은 방법을 알 수 없기 때문에 더 이상 필요하지 않은 방법을 제거 할 수 없다는 점에 유의하십시오. 다시 말하지만, 리팩터링을 방해합니다.

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

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

  • 가능한 경우 가짜 대신 실제 수업을 사용해보십시오. 실제 PdfFormatterImpl 사용하십시오. 가능하지 않으면 실제 수업을 변경하여 가능하게 만드십시오. 테스트에서 클래스를 사용할 수 없다는 것은 대개 클래스의 몇 가지 문제점을 지적합니다. 문제를 해결하는 것은 윈 - 윈 (win-win) 상황입니다. 클래스를 수정하면보다 간단한 테스트를 할 수 있습니다. 반면에 그것을 고치지 않고 모의 객체를 사용하는 것은 윈 - 윈 상황이 아닙니다. 실제 클래스를 고치지 않았고 더 복잡한 리팩토링을 방해하는 읽기 어려운 테스트가 있습니다.
  • 각 테스트에서 조롱하는 대신 인터페이스의 간단한 테스트 구현을 작성하고 모든 테스트에서이 테스트 클래스를 사용하십시오. 아무것도하지 않는 TestPdfFormatter 를 만듭니다. 그렇게하면 모든 테스트에 대해 한 번 변경할 수 있으며 스텁을 훈련 할 때 긴 설정으로 테스트가 복잡해지지 않습니다.

대체로 모의 객체는 사용되지만 신중하게 사용하지 않으면 나쁜 관행을 장려하고 구현 세부 사항을 테스트하며 리팩토링을 방해하고 읽기 어렵고 테스트를 유지하기 어렵게 만듭니다 .

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

Question

저는 가짜와 가짜 오브젝트에 대한 기본적인 이해를 가지고 있습니다 만, 언제 어디에서 조롱을 사용할 것인지에 대해 확신 할 수 없습니다. 특히 here 이 시나리오에 적용 할 것입니다.




테스트하려는 코드 단위에 종속성이있을 때 객체를 모방해야합니다.이 코드는 "그렇게"해야합니다.

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

화제에 중대한 podcast는 here 에서 찾아 낼 수있다