문제

나는 모의 객체와 가짜 객체에 대해 기본적으로 이해하고 있지만 언제 어디서 모의 객체를 사용해야 하는지에 대한 느낌이 있는지 잘 모르겠습니다. 특히 이 시나리오에 적용할 때 그렇습니다. 여기.

도움이 되었습니까?

해결책

단위 테스트는 단일 메서드를 통해 단일 코드 경로를 테스트해야 합니다.메서드 실행이 해당 메서드 외부에서 다른 개체로 전달되고 다시 되돌리면 종속성이 발생합니다.

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

종속성에 버그가 있으면 테스트가 거짓 긍정을 반환하는 방식으로 영향을 받을 수 있습니다.예를 들어 종속성에 예기치 않은 null을 전달할 수 있으며 문서에 설명된 대로 종속성이 null을 발생시키지 않을 수 있습니다.테스트에서 예상한 null 인수 예외가 발생하지 않고 테스트가 통과됩니다.

또한 테스트 중에 종속 개체가 원하는 것을 정확하게 반환하도록 안정적으로 가져오는 것이 불가능하지는 않더라도 어려울 수 있습니다.여기에는 테스트 내에서 예상되는 예외를 발생시키는 것도 포함됩니다.

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

요약:단위 테스트가 접촉하는 모든 종속성을 모의하십시오.

다른 팁

모의 객체는 다음을 원할 때 유용합니다. 테스트 상호작용 테스트 중인 클래스와 특정 인터페이스 사이.

예를 들어, 우리는 그 방법을 테스트하고 싶습니다 sendInvitations(MailServer mailServer) 전화 MailServer.createMessage() 정확히 한 번만 호출하고 MailServer.sendMessage(m) 정확히 한 번만 실행되며 다른 메서드는 호출되지 않습니다. MailServer 상호 작용.이때 우리는 모의 객체를 사용할 수 있습니다.

실제 객체를 전달하는 대신 모의 객체를 사용하여 MailServerImpl, 또는 테스트 TestMailServer, 우리는의 모의 구현을 전달할 수 있습니다 MailServer 상호 작용.모의고사를 통과하기 전에 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() 마침 이때 전화가 옵니다.우리는 훈련 방법의 정확한 목록을 어떻게 생각해 냈습니까?우리는 단순히 테스트를 실행하고 테스트가 통과할 때까지 메서드를 계속 추가했습니다.우리는 메소드를 호출해야 하는 이유에 대한 단서 없이 메소드에 응답하도록 스텁을 훈련했으며, 테스트에서 문제가 되는 모든 것을 단순히 추가했다는 점에 주목하세요.우리는 행복합니다. 테스트가 통과되었습니다.

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

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

문제를 해결하는 방법은 무엇입니까?용이하게:

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

대체로 모의 객체에는 나름의 용도가 있지만 주의 깊게 사용하지 않으면 그들은 종종 나쁜 관행을 조장하고, 구현 세부 사항을 테스트하고, 리팩토링을 방해하고, 읽기 어렵고 유지 관리하기 어려운 테스트를 생성합니다..

모의 객체의 단점에 대한 자세한 내용은 다음을 참조하세요. 모의 객체:단점 및 사용 사례.

경험 법칙:

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

테스트하려는 코드 단위에 "그렇게" 되어야 하는 종속성이 있는 경우 개체를 모의해야 합니다.

예를 들어, 코드 단위에서 일부 논리를 테스트하려고 하지만 다른 개체에서 무언가를 가져와야 하고 이 종속성에서 반환된 내용이 테스트하려는 항목에 영향을 미칠 수 있는 경우 해당 개체를 모의합니다.

해당 주제에 관한 훌륭한 팟캐스트를 찾을 수 있습니다. 여기

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top