답변:
단위 테스트는 단일 방법을 통해 단일 코드 경로를 테스트해야합니다. 메소드 실행이 해당 메소드 외부에서 다른 오브젝트로 전달되고 다시 다시 전달되면 종속성이 있습니다.
실제 종속성으로 해당 코드 경로를 테스트 할 때는 단위 테스트가 아닙니다. 당신은 통합 테스트입니다. 그것은 좋고 필요하지만 단위 테스트는 아닙니다.
의존성이 버그 인 경우, 테스트는 오 탐지를 반환하는 방식으로 영향을받을 수 있습니다. 예를 들어, 종속성을 예기치 않은 널 (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
첫 번째 테스트는 테스트의 전체 지점 인 올바르게 정렬하는지 확인하는 데 큰 도움이됩니다. 코드를 안전하게 변경할 수 있습니다. 반면에 후자의 테스트는 항상 중단되며 적극적으로 유해합니다. 리팩토링을 방해합니다.
모의 프레임 워크는 종종 덜 엄격한 사용법을 허용하는데, 여기서 우리는 메소드를 몇 번이나 호출해야하는지, 어떤 매개 변수가 예상되는지를 정확하게 지정할 필요가 없습니다. 스텁 으로 사용되는 모의 객체를 만들 수 있습니다.
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 도 참조하십시오 .