2011-04-18 4 views
6

나중에 메서드를 호출했는지 확인하면 콜백에 어설 션을 수행 할 수 있습니까? 이것은 내 모의 모형이 예상 된 매개 변수를 전달하도록하는 선호 된 방법인가요? 아니면 콜백에 지역 변수를 설정하고 해당 인스턴스에 대한 주장을해야합니까?Mock으로 전달되는 매개 변수를 확인하는 올바른 방법은 예상대로 설정됩니다.

필자는 입력을 기반으로 값을 유도하고 Creator 클래스에 전달하는 Presenter 클래스에 일부 논리가있는 상황이 있습니다. Presenter 클래스의 논리를 테스트하려면 Creator가 호출 될 때 적절한 파생 값이 관찰되는지 확인해야합니다. 그 작동 아래의 예를 함께했다,하지만 난이 방법 좋아 있는지 확실하지 않습니다 :

[TestFixture] 
public class WidgetCreatorPresenterTester 
{ 
    [Test] 
    public void Properly_Generates_DerivedName() 
    { 
     var widgetCreator = new Mock<IWidgetCreator>(); 
     widgetCreator.Setup(a => a.Create(It.IsAny<Widget>())) 
        .Callback((Widget widget) => 
        Assert.AreEqual("Derived.Name", widget.DerivedName)); 

     var presenter = new WidgetCreatorPresenter(widgetCreator.Object); 
     presenter.Save("Name"); 

     widgetCreator.Verify(a => a.Create(It.IsAny<Widget>()), Times.Once()); 
    } 
} 

나는 때문에 마지막에 Verify 호출하지 않고 걱정, 보장은 없습니다를 그 어설에서 콜백이 호출됩니다. 또 다른 방법은 콜백에서 로컬 변수를 설정하는 것입니다 :

[Test] 
public void Properly_Generates_DerivedName() 
{ 
    var widgetCreator = new Mock<IWidgetCreator>(); 
    Widget localWidget = null; 
    widgetCreator.Setup(a => a.Create(It.IsAny<Widget>())) 
     .Callback((Widget widget) => localWidget = widget); 

    var presenter = new WidgetCreatorPresenter(widgetCreator.Object); 
    presenter.Save("Name"); 

    widgetCreator.Verify(a => a.Create(It.IsAny<Widget>()), Times.Once()); 
    Assert.IsNotNull(localWidget); 
    Assert.AreEqual("Derived.Name", localWidget.DerivedName); 
} 

나는 더 명시 때문에이 방법을 적은 오류가 발생하기 쉬운이라고 생각하고는 Assert 문이 호출되는 것을 볼 쉽다. 한 접근법이 다른 접근보다 바람직합니까? 누락 된 모의 객체에 전달 된 입력 매개 변수를 테스트하는 간단한 방법이 있습니까?

public class Widget 
{ 
    public string Name { get; set; } 
    public string DerivedName { get; set; } 
} 

public class WidgetCreatorPresenter 
{ 
    private readonly IWidgetCreator _creator; 

    public WidgetCreatorPresenter(IWidgetCreator creator) 
    { 
     _creator = creator; 
    } 

    public void Save(string name) 
    { 
     _creator.Create(
      new Widget { Name = name, DerivedName = GetDerivedName(name) }); 
    } 

    //This is the method I want to test 
    private static string GetDerivedName(string name) 
    { 
     return string.Format("Derived.{0}", name); 
    } 
} 

public interface IWidgetCreator 
{ 
    void Create(Widget widget); 
} 

편집 내가 사용하기 쉽게 질문에 설명 된 두 번째 방법을 할 수있는 코드를 업데이트
: 그것은 도움이 될 것입니다 경우

, 여기에이 예제 코드의 나머지 부분입니다 . Setup/Verify에서 사용 된 표현식을 별도의 변수로 생성하여 한 번만 정의하면됩니다. 나는이 방법이 내가 가장 편안하다고 느낀다. 설치하기 쉽고 좋은 오류 메시지로 실패한다.

[Test] 
public void Properly_Generates_DerivedName() 
{ 
    var widgetCreator = new Mock<IWidgetCreator>(); 
    Widget localWidget = null; 

    Expression<Action<IWidgetCreator>> expressionCreate = 
     (w => w.Create(It.IsAny<Widget>())); 
    widgetCreator.Setup(expressionCreate) 
     .Callback((Widget widget) => localWidget = widget); 

    var presenter = new WidgetCreatorPresenter(widgetCreator.Object); 
    presenter.Save("Name"); 

    widgetCreator.Verify(expressionCreate, Times.Once()); 
    Assert.IsNotNull(localWidget); 
    Assert.AreEqual("Derived.Name", localWidget.DerivedName); 
} 

답변

3

코드가 구조화되어 있기 때문에 하나의 단위 테스트에서 두 가지를 테스트해야합니다. A) 발표자가 삽입 된 WidgetCreator의 create 메소드를 호출하고 B) 올바른 이름이 새 위젯에 설정되어 있는지 테스트하고 있습니다. 가능하다면 어떻게 든이 두 가지를 두 가지 별도의 테스트로 만들 수 있다면 더 좋을 것입니다. 그러나이 경우에는 실제로 그렇게 할 방법이 없습니다.

이 모든 것을 감안할 때 두 번째 접근 방식이 더 깨끗하다고 ​​생각합니다. 그것은 당신이 기대하는 것에 관해서는 더 명백합니다. 그리고 그것이 실패한다면, 그것은 왜 그리고 어디서 실패하고 있는지를 완벽하게 이해할 것입니다.

+0

두 번째 방법이 더 바람직하다는 데 동의합니다. Verify를 사용하는 @ aqwert의 제안이 마음에 들지만, 실패 메시지는 사용하기가 너무 어렵습니다. 한 번 표현식을 선언해야하므로 두 번째 방법을 업데이트하는 원래 질문에 몇 가지 코드를 추가했습니다. 나는 그 접근 방식을 고수 할 것이라고 생각한다. – rsbarro

4

내가하는 일은 Verify과 일치하는 AAA를 유지하는 것입니다. 그리고 이것 때문에 설치가 필요하지 않습니다. 당신은 그것을 인라인 할 수 있지만 좀 더 깔끔하게 보이도록 분리했습니다.

[Test] 
public void Properly_Generates_DerivedName() 
{ 
    var widgetCreator = new Mock<IWidgetCreator>(); 

    var presenter = new WidgetCreatorPresenter(widgetCreator.Object); 
    presenter.Save("Name"); 

    widgetCreator.Verify(a => a.Create(MatchesWidget("Derived.Name")); 
} 

private Widget MatchesWidget(string derivedName) 
{ 
    return It.Is<Widget>(m => m.DerivedName == derivedName); 
} 
+0

+1 감사합니다. 필자의 예제보다 읽기가 훨씬 쉽지만, 실패 메시지는 단위 테스트에서 조금 이상합니다. "expected, but was"메시지 대신에 "모의에서 적어도 한 번 이상 예상 된 호출이 있지만 수행 된 적이 없습니다"라는 메시지가 나타납니다. 일단 익숙해지면 다루기가 너무 힘들지 않지만, 그런 유형의 메시지를 보는데 익숙하지 않다면 조금 불분명 할 수도 있습니다. – rsbarro

+1

예, 조롱 프레임 워크 메시지가 조금 이상 할 수 있습니다. 도움이된다면 실패한 메시지를'Verify' 메소드에 추가 할 수 있습니다. – aqwert

+0

그래, 실패 메시지가 도움이됩니다. 다른 제안 사항이 있는지보기 위해이 질문을 잠시 열어 두겠습니다. 다시 한번 감사드립니다. – rsbarro

3

그냥 @의 rsbarro의 의견에 정교 - MOQ 실패 오류 메시지 :

이 모의에 호출에 한 번 이상 예상하지만,

을 수행하지 않았다 ... 도움 미만 복잡한 유형의 경우 정확히 which 상태가 실제로 실패했는지, 코드 또는 유닛 테스트에서 버그를 찾아 낼 때 결정됩니다.상기 방법은 int 또는 string 같은 프리미티브 아닌 특정 파라미터 값으로 호출되어 있어야 Verify의 조건의 다수를 확인 MOQ Verify 사용시

제가 종종 발생. Moq은 예외의 일부로 메서드에서 실제 "수행 된 호출"을 나열하므로 일반적으로 기본 형식에는 문제가 없습니다.

은 결과,이 경우에, 나는 (나에게 Moq의 작품을 복제 보인다)에 전달 된 매개 변수를 캡처하거나 Setup/Callbacks로 어설 인라인을 이동해야합니다.

검증 : 우리가 Arrange으로 Assert 인라인 퍼팅 때문에 콜백은 이 중를 호출하지만

widgetCreator.Verify(wc => wc.Create(
     It.Is<Widget>(w => w.DerivedName == "Derived.Name" 
        && w.SomeOtherCondition == true), 
     It.Is<AnotherParam>(ap => ap.AnotherCondition == true), 
    Times.Exactly(1)); 

는 (이 AAA 위반, 언뜻

widgetCreator.Setup(wc => wc.Create(It.IsAny<Widget>(), 
            It.IsAny<AnotherParam>()) 
      .Callback<Widget, AnotherParam>(
       (w, ap) => 
       { 
        Assert.AreEqual("Derived.Name", w.DerivedName); 
        Assert.IsTrue(w.SomeOtherCondition); 
        Assert.IsTrue(ap.AnotherCondition, "Oops"); 
       }); 

// *** Act => invoking the method on the CUT goes here 

// Assert + Verify - cater for rsbarro's concern that the Callback might not have happened at all 
widgetCreator.Verify(wc => wc.Create(It.IsAny<Widget>(), It.Is<AnotherParam>()), 
    Times.Exactly(1)); 

로 코딩 될 것이다 Act).하지만 적어도 문제의 최하점까지 도달 할 수 있습니다.

'추적'콜백 람다를 고유 한 명명 된 함수로 옮기는 아이디어를 보거나 C# 7에서 더 좋게도 단위 테스트 메서드의 하단에있는 Local Function으로 이동할 수 있으므로 AAA 레이아웃을 유지할 수 있습니다.

1

이 스레드의 StuartLC의 답 위에 올라가면, AAA을 위반하지 않고 제안하는 내용을 따르며, mock 개체의 Verify 메서드에 전달되는 "인라인"함수를 작성합니다. 그래서 예를 들면

: 대답에 대한

// Arrange 
widgetCreator 
    .Setup(wc => wc.Create(It.IsAny<Widget>(), It.IsAny<AnotherParam>()); 

// Act 
// Invoke action under test here... 

// Assert 
Func<Widget, bool> AssertWidget = request => 
{ 
    Assert.AreEqual("Derived.Name", w.DerivedName); 
    Assert.IsTrue(w.SomeOtherCondition); 
    Assert.IsTrue(ap.AnotherCondition, "Oops"); 
    return true; 
}; 

widgetCreator 
    .Verify(wc => wc.Create(It.Is<Widget>(w => AssertWidget(w)), It.Is<AnotherParam>()), Times.Exactly(1)); 
관련 문제