[unit-testing] 모의 객체의 목적은 무엇입니까?

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



답변

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

단위 테스트

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

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 핫도그 준비 (더미 핫도그에게 웨이터에게 제공) 웨이터드라이버테스트합니다. : 여기 감자 튀김이 있습니다 (프렌치 프라이는 다른 순서로 드라이버를 테스트합니다)

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

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

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


답변

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

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

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모의 객체가 예상 된 결과로 프로그램되어있다.


답변

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

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

답변

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());

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


답변

Martin Fowler의 모의 내용과 스텁과의 차이점을 설명 하는 훌륭한 기사를 강력히 추천합니다 .


답변

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

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

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입니다


답변

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

간단히 말해서, 당신은 그렇게 다른 개체와 모의 객체에 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()실패는 실패하지 않습니다.