2013-02-14 6 views
2

모의 객체는 일부 프로그램 단위의 심층 행동 테스트를 수행 할 수있는 좋은 방법을 제공합니다. 테스트 된 유닛에 조롱 된 종속성을 전달하고 종속성이 제대로 작동하는지 확인해야합니다.유지 관리 가능한 머쉰

public class A 
{ 
    private B b; 

    public A(B b) 
    { 
     this.b = b; 
    } 

    public void DoSomething() 
    { 
     b.PerformSomeAction(); 
     if(b.State == some special value) 
     { 
      b.PerformAnotherAction(); 
     } 
    } 
} 


public class B 
{ 
    public BState State { get; private set; } 

    public void PerformSomeAction() 
    { 
     //some actions 

     State = some special value; 
    } 

    public void PerformAnotherAction() 
    { 
     if(State != some special value) 
     { 
      fail(); //for example throw new InvalidOperationException(); 
     } 
    } 
} 

가 클래스 B는 단위 테스트를 TestB 테스트되고 상상 :

당신은이 클래스 A와 B를 보자.

단위 테스트 클래스 A에 B를 생성자에 전달하거나 (상태 기반 테스트 수행) B의 모의을 전달하여 (행동 기반 테스트 수행).

두 번째 접근법 (예 : A 상태를 직접 확인할 수없고 간접적으로 수행 할 수 없음)을 선택하고 UnitA 테스트 TestA (B에 대한 참조를 포함하지 않음)를 생성했다고 가정 해 보겠습니다.

그래서 우리는 인터페이스 IDependency을 소개하고 클래스가 다음과 같이 표시됩니다 확인

[TestClass] 
public class TestB 
{ 
    [TestMethod] 
    public void ShouldPerformAnotherActionWhenDependencyReturnsSomeSpecialValue() 
    { 
     var d = CreateDependencyMockSuchThatItReturnsSomeSpecialValue(); 
     var a = CreateA(d.Object); 

     a.DoSomething(); 

     AssertSomeActionWasPerformedForDependency(d); 
    } 

    [TestMethod] 
    public void ShouldNotPerformAnotherActionWhenDependencyReturnsSomeNormalValue() 
    { 
     var d = CreateDependencyMockSuchThatItReturnsSomeNormalValue(); 
     var a = CreateA(d.Object); 

     a.DoSomething(); 

     AssertSomeActionWasNotPerformedForDependency(d); 
    } 
} 

:

public interface IDependency 
{ 
    void PerformSomeAction(); 
    void PerformAnotherAction(); 
} 

public class A 
{ 
    private IDependency d; 

    public A(IDependency d) 
    { 
     this.d = d; 
    } 

    public void DoSomething() 
    { 
     d.PerformSomeAction(); 
     if(d.State == some special value) 
     { 
      d.PerformAnotherAction(); 
     } 
    } 
} 


public class B : IDependency 
{ 
    public BState State { get; private set; } 

    public void PerformSomeAction() 
    { 
     //some actions 

     State = some special value; 
    } 

    public void PerformAnotherAction() 
    { 
     if(State != some special value) 
     { 
      fail(); //for example throw new InvalidOperationException(); 
     } 
    } 
} 

및 단위 테스트를 TestB 비슷한입니다. 개발자에게 행복한 순간입니다. 모든 것이 테스트되고 모든 테스트는 녹색입니다. 모든 좋은.

하지만!

누군가가 클래스 B의 논리를 수정하면 (예 : (State! = some special value)에서 if (State! = 다른 값)로 수정) TestB 만 실패합니다.

이 사람은이 테스트를 수정하고 모든 것이 잘 돌아 간다고 생각합니다.

A의 생성자에 B를 전달하려고하면 A.DoSomething이 실패합니다.

근본 원인은 우리의 모의 객체입니다. B 객체의 이전 동작을 수정했습니다. B가 행동을 바꿨을 때 조롱에 반영되지 않았습니다.

내 질문은 B의 모의를 만드는 방법은 B의 동작 변경을 따르십니까?

답변

1

이것은 관점의 문제입니다. 일반적으로 구체적인 클래스가 아닌 인터페이스을 모의합니다. 귀하의 예제에서 B의 모의는 IDependency의 구현입니다. B의 모의은 IDependency의 동작이 변경 될 때마다 변경되어야하며 IDependency의 정의 된 동작을 변경할 때 IDependency의 모든 구현을 살펴 봄으로써이를 보장 할 수 있습니다.

클래스가 인터페이스를 구현
  1. , 그것은 수정 후 인터페이스의 모든 정의 된 동작을 수행해야합니다

    그래서, 시행은 코드베이스에 따라야한다고 2 개 간단한 규칙을 통해입니다.

  2. 인터페이스를 변경하면 새 인터페이스를 수행하기 위해 모든 구현자를 조정해야합니다.

이상적으로, 당신은 B와 BMock하고,이 규칙의 캐치 위반에 모두 적용 IDependency의 정의 행동에 대해 테스트 장소에서 단위 테스트를해야합니다.

+0

귀하의 답변은 대부분 내가 생각하는 것과 내가 스스로 해결하고자하는 것입니다. Mocks가 인터페이스를 예외로 구현하는지 확인하는 일련의 테스트를 작성하는 것이 좋습니다. 좋은 습관입니까? 일부 유지 관리 문제가 있습니까? 우리는 단위 테스트 테스트 유틸리티 클래스도 필요합니다. 모의 동작을 수정하는 대신 단위 테스트 (각 인터페이스 구현에 대해 오버라이드되어야하는 추상적 인 기본 단위 테스트 클래스, 소위 계약 테스트)에서 수정해야합니다. 내가 맞습니까? – gerichhome

0

실제 구현과 (손으로 만든) 모의를 계약 테스트 집합 (역할/인터페이스의 동작을 지정)으로 옹호하는 것처럼 보이는 다른 대답과 다릅니다. 나는 모의 훈련을 한 적이 한번도 보지 못했다.

일반적으로 당신은 mocking 프레임 워크를 사용하지 않고 mock을 수작업으로 만들지 않습니다. 그래서 w.r.t. 귀하의 예를 들어, 내 클라이언트 테스트는 변경으로

new Mock<IDependency>().Setup(d => d.Method(params) 
         .Returns(expectedValue) 

계약 변경, 어떻게 클라이언트 테스트에서 인라인 기대도 업데이트 (또는 플래그)되는 것을 보장 할 때 귀하의 질문은 인라인 문을 것 의존?

컴파일러가 여기 도움이되지 않습니다. 테스트도 마찬가지입니다. 당신이 가지고있는 것은 클라이언트와 의존 사이의 공유 된 동의의 부족이다. 수동으로 찾기 및 바꾸기 (또는 IDE 도구를 사용하여 인터페이스 메소드에 대한 모든 참조를 찾아야 함)를 수정해야합니다.

탈출구는 세분화 된 IDependency 인터페이스를 무분별하게 정의하지 않는 것입니다. 대부분의 문제는 분명히 정의 된 비 휘발성 동작을 가진 역할 (인터페이스로 실현 됨)이 가장 적은 chunky 역할로 해결 될 수 있습니다. 역할 수준 변경을 최소화 할 수 있습니다. 처음에는 이것이 나와도 달라 붙는 지점이었습니다. 그러나 discussions with interaction-test experts과 실제적인 경험으로 저를 이겨낼 수있었습니다. 이것이 너무 자주 발생한다면, 변덕스러운 인터페이스의 원인에 대한 빠른 회고가 더 나은 결과를 가져와야합니다.