[unit-testing] 언제 조롱해야합니까?

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



답변

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

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

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

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

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

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


답변

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

예를 들어, 메소드 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 도 참조하십시오 .


답변

경험 법칙 :

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


답변

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

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

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


답변