모의 객체의 목적은 무엇입니까?


167

나는 단위 테스트를 처음 접했고, '모의 객체'라는 단어를 많이 들었습니다. 평신도의 관점에서 누군가 모의 객체가 무엇인지, 단위 테스트를 작성할 때 일반적으로 사용되는 것을 설명 할 수 있습니까?


12
이 도구는 당면한 문제에 필요하지 않은 유연성으로 대규모 오버 엔지니어링을위한 도구입니다.
dsimcha

답변:


360

유닛 테스트에 익숙하지 않고 "레이맨의 용어"로 모의 객체를 요청 했으므로 평신도의 예를 들어 보겠습니다.

단위 테스트

이 시스템에 대한 단위 테스트를 상상해보십시오.

cook <- waiter <- customer

일반적으로 cook다음 과 같은 저수준 구성 요소를 테스트하는 것이 쉽습니다 .

cook <- test driver

테스트 드라이버는 단순히 다른 요리를 주문하고 요리사가 각 주문에 맞는 올바른 요리를 반환하는지 확인합니다.

다른 구성 요소의 동작을 활용하는 웨이터와 같은 중간 구성 요소를 테스트하기가 더 어렵습니다. 순진한 테스터는 cook 컴포넌트를 테스트 한 것과 동일한 방식으로 웨이터 컴포넌트를 테스트 할 수 있습니다.

cook <- waiter <- test driver

시험 운전사는 다른 접시를 주문하고 웨이터가 올바른 접시를 반환하는지 확인합니다. 불행히도, 웨이터 구성 요소에 대한이 테스트는 쿡 구성 요소의 올바른 동작에 따라 달라질 수 있습니다. 요리사 구성 요소가 비 결정적 행동 (메뉴에 요리사의 놀라움이 포함 된 메뉴), 많은 의존성 (요리사가 전체 직원없이 요리하지 않음)과 같은 테스트에 익숙하지 않은 특성이있는 경우 이러한 종속성은 더욱 악화됩니다. 자원 (일부 요리는 고가의 재료가 필요하거나 요리하는 데 1 시간이 걸립니다).

이것은 웨이터 테스트이므로 이상적으로는 요리사가 아닌 웨이터 만 테스트하고 싶습니다. 특히, 웨이터가 고객의 주문을 요리사에게 정확하게 전달하고 요리사의 음식을 고객에게 올바르게 전달하도록합니다.

단위 테스트는 단위를 독립적으로 테스트하는 것을 의미하므로 Fowler가 테스트 배가 (더미, 스텁, 가짜, 모의)라고 부르는 것을 사용하여 테스트 대상 구성 요소 (웨이터)를 격리하는 것이 더 나은 방법입니다 .

    -----------------------
   |                       |
   v                       |
test cook <- waiter <- test driver

여기서 테스트 쿡은 테스트 드라이버와 함께 "cahoots"입니다. 테스트중인 시스템은 생산 코드를 변경하지 않고 (예 : 웨이터 코드를 변경하지 않고) 웨이터와 함께 작업하기 위해 테스트 쿡을 쉽게 대체 ( 주입 ) 할 수 있도록 설계 됩니다.

모의 객체

이제 테스트 쿡 (테스트 더블)은 다른 방식으로 구현 될 수 있습니다.

  • 가짜 요리사-냉동 저녁 식사와 전자 레인지를 사용하여 요리사 인 척하는 사람,
  • 스텁 쿡 (Stub Cook)-주문한 제품에 상관없이 항상 핫도그를 제공하는 핫도그 공급 업체
  • 모의 쿡-찌르기 작업에서 쿡인 척하는 대본을 따르는 비밀 경찰.

가짜 대 스터브 대 모의 대 인형에 대한 자세한 내용은 Fowler의 기사를 참조하십시오 . 그러나 지금은 모의 요리사에 초점을 맞 춥니 다.

    -----------------------
   |                       |
   v                       |
mock cook <- waiter <- test driver

웨이터 구성 요소를 테스트하는 단위의 상당 부분은 웨이터가 요리 구성 요소와 상호 작용하는 방식에 중점을 둡니다. 모의 기반 접근 방식은 올바른 상호 작용이 무엇인지 완전히 지정하고 잘못되었을 때 감지하는 데 중점을 둡니다.

모의 객체는 테스트 중에 발생해야 할 사항 (예 : 호출을 호출 할 메소드 등)을 미리 알고 있으며 모의 객체는 어떻게 반응해야 하는지를 알고 있습니다 (예 : 제공 할 반환 값). 모의는 실제로 일어나는 일이 일어날 일과 다른지 여부를 나타냅니다. 테스트 케이스마다 예상되는 동작을 실행하기 위해 각 테스트 케이스에 대해 사용자 정의 모의 객체를 처음부터 만들 수 있지만, 모의 프레임 워크는 이러한 동작 사양을 테스트 케이스에 직접 명확하고 쉽게 표시 할 수 있도록 노력합니다.

모의 테스트를 둘러싼 대화는 다음과 같습니다.

요리사조롱하는 테스트 드라이버 : 핫도그 주문을 기대하고 그 에게이 더미 핫도그를 제공하십시오

웨이터 에게 테스트 드라이버 (고객으로 배치) : 나는 핫도그가
웨이터 에게 조롱 하고 싶습니다 : 1 핫도그웨이터에
조롱 하십시오 : 주문 : 1 개의 핫도그 준비 (더미 핫도그에게 웨이터에게 제공) 웨이터테스트 드라이버에게 : 여기 핫도그가 있습니다 (드라이버에게 더미 핫도그 제공)

테스트 드라이버 : 테스트 성공!

그러나 우리 웨이터가 새롭기 때문에 이런 일이 일어날 수 있습니다.

요리사조롱하는 테스트 드라이버 : 핫도그 주문을 기대하고 그 에게이 더미 핫도그를 제공하십시오

웨이터 에게 테스트 드라이버 (고객으로 배치) : 나는 핫도그가 요리조롱하기 위해
웨이터부탁합니다 : 1 햄버거 제발 모의 요리사 가 테스트를 중지하십시오 : 나는 핫도그 주문을 기대한다고 들었습니다!

테스트 드라이버 가 문제를 확인했습니다 : 테스트 실패! -웨이터가 주문을 변경했습니다

또는

요리사조롱하는 테스트 드라이버 : 핫도그 주문을 기대하고 그 에게이 더미 핫도그를 제공하십시오

웨이터 에게 테스트 드라이버 (고객으로 배치) : 나는 핫도그가
웨이터모의 쿡으로 만들고 싶습니다 : 1 핫도그웨이터로
모의합니다 : 주문 : 1 핫도그 준비 (더미 핫도그에게 웨이터에게 제공) 웨이터드라이버테스트합니다. : 여기 감자 튀김이 있습니다 (프렌치 프라이는 다른 순서로 드라이버를 테스트합니다)

테스트 드라이버 는 예상치 못한 감자 튀김을 테스트합니다 : 테스트 실패! 웨이터가 그릇된 그릇을 주었다

대조적 인 스터브 기반 예제가 없으면 모의 객체와 스터브의 차이점을 명확하게 파악하기 어려울 수 있지만이 답변은 이미 너무 길었습니다 :-)

또한 이것은 매우 단순한 예이며, 모의 프레임 워크는 구성 요소에서 포괄적 인 테스트를 지원하기 위해 예상되는 동작의 매우 정교한 사양을 허용합니다. 더 많은 정보를 위해 모의 객체 및 모의 프레임 워크에 대한 많은 자료가 있습니다.


12
이것은 훌륭한 설명이지만 웨이터의 구현을 어느 정도 테스트하지 않습니까? 귀하의 경우 올바른 API를 사용하고 있는지 확인하고 있기 때문에 괜찮을 것입니다.하지만 다른 방법이 있고 웨이터가 둘 중 하나를 선택할 수 있습니까? 단위 테스트의 요점은 구현이 아니라 API를 테스트하는 것이라고 생각했습니다. (이것은 항상 조롱에 대해 읽을 때 묻는 질문입니다.)
davidtbernal

8
감사. 웨이터의 사양을 보거나 정의하지 않고 "구현"을 테스트하고 있는지 말할 수 없습니다. 웨이터가 직접 요리를 요리하거나 길을 주문할 수 있다고 가정 할 수도 있지만 웨이터의 사양에는 의도 된 요리사의 사용이 포함된다고 가정합니다. 결국 생산 요리사는 값 비싼 미식가 요리사이며 우리는 d는 웨이터가 그를 사용하는 것을 선호합니다. 그 스펙이 없으면, 나는 "당신이 옳다고 결론을 내릴 것 같다"-웨이터는 "올바른"순서대로 주문을 채울 수있다. OTOH, 스펙이 없으면 테스트는 의미가 없다. [계속 ...]
Bert F

8
절대로, 당신은 화이트 박스 대 블랙 박스 단위 테스트의 환상적인 주제로 이어지는 훌륭한 지적을합니다. 단위 테스트가 화이트 박스 대신 블랙 박스 여야한다는 업계 컨센서스가 없다고 생각합니다 ( "구현이 아닌 API 테스트"). 코드 취재 및 테스트 케이스 완성도와 테스트 취성의 균형을 맞추기 위해서는 최상의 단위 테스트가 두 가지를 조합해야한다고 생각합니다.
Bert F

1
이 답변은 기술적으로 충분하지 않다고 말합니다. 실제 객체를 사용할 수 있는데 왜 모의 객체를 사용해야하는지 알고 싶습니다.
Niklas R.

1
대단한 설명 !! 감사합니다!! @BertF
Bharath Murali

28

모의 객체는 실제 객체를 대체하는 객체입니다. 객체 지향 프로그래밍에서 모의 ​​객체는 실제 객체의 동작을 제어 된 방식으로 모방하는 시뮬레이션 된 객체입니다.

컴퓨터 프로그래머는 일반적으로 자동차 디자이너가 충돌 테스트 더미를 사용하여 차량 충격에서 사람의 동적 행동을 시뮬레이션하는 것과 거의 같은 방식으로 다른 객체의 행동을 테스트하기 위해 모의 객체를 만듭니다.

http://en.wikipedia.org/wiki/Mock_object

모의 객체를 사용하면 데이터베이스와 같이 크고 다루기 힘든 리소스를 가지지 않고도 테스트 시나리오를 설정할 수 있습니다. 테스트를 위해 데이터베이스를 호출하는 대신 단위 테스트에서 모의 ​​객체를 사용하여 데이터베이스를 시뮬레이션 할 수 있습니다. 따라서 클래스에서 단일 메소드를 테스트하기 위해 실제 데이터베이스를 설정 및 해제해야하는 부담에서 벗어날 수 있습니다.

"Mock"이라는 단어가 "Stub"과 상호 교환 적으로 잘못 사용되는 경우가 있습니다. 두 단어의 차이점은 여기설명되어 있습니다. 본질적으로, 모의는 테스트 대상 객체 / 방법의 올바른 동작에 대한 기대치 (예 : "어설 션")를 포함하는 스텁 객체입니다.

예를 들면 다음과 같습니다.

class OrderInteractionTester...
  public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    Mock warehouse = mock(Warehouse.class);
    Mock mailer = mock(MailService.class);
    order.setMailer((MailService) mailer.proxy());

    mailer.expects(once()).method("send");
    warehouse.expects(once()).method("hasInventory")
      .withAnyArguments()
      .will(returnValue(false));

    order.fill((Warehouse) warehouse.proxy());
  }
}

것을 공지 사항 warehousemailer모의 객체가 예상 된 결과로 프로그램되어있다.


2
당신이 제공 한 정의는 "스텁 객체"와 다르지 않으며, 따라서 모의 객체가 무엇인지 설명하지 않습니다.
브렌트 아리아스

또 다른 수정 " '모의 (Mock)'라는 단어는 '스텁 (stub)'과 상호 교환 적으로 잘못 사용되는 경우 있습니다."
브렌트 아리아스

@Myst : 두 단어의 사용법은 보편적이지 않습니다. 저자마다 다릅니다. Fowler는 그렇게 말하고 Wikipedia 기사는 그렇게 말합니다. 그러나 변경 사항을 편집하고 다운 보트를 제거하십시오. :)
Robert Harvey

1
Robert에 동의합니다. "mock"이라는 단어의 사용은 업계에 따라 다르지만 일반적으로 실제 테스트 대상이 아니라 실제로 사용하는 경우 테스트를 용이하게하는 것 외에는 내 경험에 따라 정해진 정의 가 없습니다. 객체 또는 객체의 모든 부분은 매우 불편하고 거의 영향을 미치지 않습니다.
mkelley33

15

모의 객체는 실제 객체의 동작을 모방 한 시뮬레이션 된 객체입니다. 일반적으로 다음과 같은 경우 모의 객체를 작성합니다.

  • 실제 개체는 단위 테스트에 통합하기에는 너무 복잡합니다 (예 : 네트워킹 통신의 경우 다른 피어와 같은 모의 개체를 가질 수 있음)
  • 객체의 결과는 결정적이지 않습니다.
  • 실제 개체는 아직 사용할 수 없습니다

12

Mock 객체는 Test Double 의 한 종류입니다 . mockobject를 사용하여 테스트중인 클래스의 프로토콜 / 상호 작용을 다른 클래스와 테스트하고 확인합니다.

일반적으로 일종의 '프로그램'또는 '레코드'기대 : 클래스가 기본 객체에 대해 수행 할 것으로 예상되는 메소드 호출입니다.

예를 들어 위젯에서 필드를 업데이트하기 위해 서비스 메소드를 테스트한다고 가정 해 봅시다. 그리고 아키텍처에는 데이터베이스를 다루는 WidgetDAO가 있습니다. 데이터베이스와의 대화 속도가 느리고 나중에 설정 및 정리가 복잡하므로 WidgetDao를 조롱합니다.

서비스가해야 할 일을 생각해 봅시다 : 데이터베이스에서 위젯을 가져 와서 무언가를하고 다시 저장해야합니다.

의사 모의 라이브러리가있는 의사 언어에서는 다음과 같은 것이 있습니다.

Widget sampleWidget = new Widget();
WidgetDao mock = createMock(WidgetDao.class);
WidgetService svc = new WidgetService(mock);

// record expected calls on the dao
expect(mock.getById(id)).andReturn(sampleWidget);   
expect(mock.save(sampleWidget);

// turn the dao in replay mode
replay(mock);

svc.updateWidgetPrice(id,newPrice);

verify(mock);    // verify the expected calls were made
assertEquals(newPrice,sampleWidget.getPrice());

이런 식으로 우리는 다른 클래스에 의존하는 클래스의 드라이브 개발을 쉽게 테스트 할 수 있습니다.



9

컴퓨터 프로그램의 일부를 단위 테스트 할 때 해당 특정 부분의 동작 만 테스트하는 것이 이상적입니다.

예를 들어, 다른 프로그램을 사용하여 인쇄를 호출하는 가상의 프로그램에서 아래의 의사 코드를보십시오.

If theUserIsFred then
    Call Printer(HelloFred)
Else
   Call Printer(YouAreNotFred)
End

이것을 테스트하는 경우 주로 사용자가 Fred인지 여부를 확인하는 부분을 테스트하려고합니다. 당신은 실제로 Printer사물 의 일부 를 테스트하고 싶지 않습니다 . 그것은 또 다른 시험이 될 것입니다.

이것은 Mock 객체가 들어오는 곳입니다. 그들은 다른 유형의 것으로 가장합니다. 이 경우 Mock을 사용하여 Printer실제 프린터처럼 작동하지만 인쇄와 같은 불편한 작업은 수행하지 않습니다.


Mocks가 아닌 사용할 수있는 다른 유형의 척 오브젝트가 있습니다. Mocks Mocks를 만드는 주된 이유는 동작과 예상으로 구성 할 수 있다는 것입니다.

예상 대로 Mock이 잘못 사용될 때 오류가 발생할 수 있습니다. 위의 예에서 "user is Fred"테스트 사례에서 HelloFred를 사용하여 프린터를 호출했는지 확인할 수 있습니다. 그것이 일어나지 않으면 모의는 당신에게 경고 할 수 있습니다.

Mocks의 동작은 예를 들어 코드에서 다음과 같은 작업을 수행했음을 의미합니다.

If Call Printer(HelloFred) Returned SaidHello Then
    Do Something
End

이제 프린터를 호출하고 SaidHello를 반환 할 때 코드의 기능을 테스트하려고하므로 HelloFred로 호출 할 때 SaidHello를 반환하도록 Mock을 설정할 수 있습니다.

이 문제를 해결하는 한 가지 좋은 자료는 Martin Fowlers post Mocks Are n't Stubs입니다


7

모의 객체와 스터브 객체는 단위 테스트에서 중요한 부분입니다. 실제로 이들은 단위 그룹 이 아닌 단위를 테스트하기 위해 먼 길을갑니다 .

간단히 말해서, 당신은 그렇게 다른 개체와 모의 객체에 SUT의 (시스템에서 테스트) 의존성을 깰 스텁을 사용 하고 SUT 종속성의 특정 메서드 / 속성이라는 것을 확인합니다. 이것은 단위 테스트의 기본 원칙으로 되돌아갑니다. 테스트는 쉽게 읽을 수 있고 빠르며 구성이 필요하지 않아야합니다. 이는 모든 실제 클래스를 사용하는 것이 좋습니다.

일반적으로 테스트에서 스텁을 두 개 이상 가질 수 있지만 모의는 하나만 있어야합니다. mock의 목적은 행동을 확인하는 것이며 테스트는 한 가지만 테스트해야하기 때문입니다.

C # 및 Moq를 사용한 간단한 시나리오 :

public interface IInput {
  object Read();
}
public interface IOutput {
  void Write(object data);
}

class SUT {
  IInput input;
  IOutput output;

  public SUT (IInput input, IOutput output) {
    this.input = input;
    this.output = output;
  }

  void ReadAndWrite() { 
    var data = input.Read();
    output.Write(data);
  }
}

[TestMethod]
public void ReadAndWriteShouldWriteSameObjectAsRead() {
  //we want to verify that SUT writes to the output interface
  //input is a stub, since we don't record any expectations
  Mock<IInput> input = new Mock<IInput>();
  //output is a mock, because we want to verify some behavior on it.
  Mock<IOutput> output = new Mock<IOutput>();

  var data = new object();
  input.Setup(i=>i.Read()).Returns(data);

  var sut = new SUT(input.Object, output.Object);
  //calling verify on a mock object makes the object a mock, with respect to method being verified.
  output.Verify(o=>o.Write(data));
}

위의 예제에서 Moq를 사용하여 스텁과 모의를 시연했습니다. Moq는 두 클래스에 동일한 클래스를 사용하므로 Mock<T>약간 혼란 스럽습니다. 그럼에도 불구하고 런타임에 output.Write데이터를로 호출 하지 않으면 테스트가 실패 parameter하지만 호출 input.Read()실패는 실패하지 않습니다.


4

" Mocks Are n't Stubs " 링크를 통해 제안 된 또 다른 답변으로 , 모의는 실제 객체 대신 사용할 "테스트 더블"의 한 형태입니다. 스터브 객체와 같은 다른 형태의 테스트 더블과 다른 점은 다른 테스트 더블은 상태 검증 (및 시뮬레이션)을 제공하는 반면, 모의는 행동 검증 (및 시뮬레이션)을 제공한다는 것입니다.

스텁을 사용하면 스텁에서 여러 가지 메소드를 임의의 순서로 (또는 반복적으로) 호출하고 스텁이 의도 한 값 또는 상태를 캡처했는지 여부를 판별 할 수 있습니다. 반대로 모의 객체는 매우 특정한 함수가 특정 순서로, 심지어 특정 횟수로 호출 될 것으로 예상합니다. 모의 객체에 대한 테스트는 단순히 테스트가 끝났을 때 모의 객체에 올바른 상태가 있더라도 메소드가 다른 순서 또는 개수로 호출 되었기 때문에 "실패한"것으로 간주됩니다!

이런 식으로 모의 객체는 종종 스텁 객체보다 SUT 코드에 더 밀접하게 결합 된 것으로 간주됩니다. 확인하려는 내용에 따라 좋거나 나쁠 수 있습니다.


3

모의 객체를 사용하는 요점 중 하나는 사양에 따라 실제로 구현할 필요가 없다는 것입니다. 그들은 단지 더미 응답을 줄 수 있습니다. 예를 들어 컴포넌트 A와 B를 구현하고 서로 "호출"(상호 작용)해야하는 경우 B가 구현 될 때까지 A를 테스트 할 수 없으며 그 반대도 마찬가지입니다. 테스트 중심 개발에서 이것은 문제입니다. 당신이 ( "더미")를 모의 만들 그래서 매우 간단 그 A와 B에 대한 객체,하지만 그들은 줄 몇 가지 그들이 상호 작용 할 때 응답의 종류. 이렇게하면 B에 대한 모의 객체를 사용하여 A를 구현하고 테스트 할 수 있습니다.


1

php와 phpunit에 대해서는 phpunit documentaion에 잘 설명되어 있습니다. 여기를 참조하십시오 phpunit documentation

간단한 단어 조롱 객체는 원래 객체의 더미 객체이며 반환 값을 반환합니다.이 반환 값은 테스트 클래스에서 사용할 수 있습니다


0

단위 테스트의 주요 관점 중 하나입니다. 예, 단일 코드 단위를 테스트하려고 시도하고 테스트 결과가 다른 Bean 또는 오브젝트 동작과 관련이 없어야합니다. 따라서 간단한 대응 응답으로 Mock 객체를 사용하여 조롱해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.