이전에 Mocking Framework로 Rhino Mocks에 대하여 링크로 간단한 소개글을 썼었습니다. 본 글에서는 Mocking Framework는 왜 필요한지와 Rhino Mocks에 대한 Practice에 대하여 설명하고자 합니다.

먼저 아래의 간단한 코드로 시작합니다.
[TestMethod]

public void GenerateMock_Vs_GenerateStub()

{

    // 1. GenerateStub

    var stubRepository = MockRepository.GenerateStub<IRepository>();

 

    stubRepository.Property = 1;

    Assert.AreEqual(stubRepository.Property, 1);

 

    // 2. GenerateMock

    var mockRepository = MockRepository.GenerateMock<IRepository>();

 

    mockRepository.Property = 1;

    Assert.AreEqual(mockRepository.Property, 0);

 

    mockRepository.Stub(t => t.Property).Return(1);

    Assert.AreEqual(mockRepository.Property, 1);

}

=> Rhino Mocks에서는 MockRepository라는 클래스를 사용하여 가짜 개체를 만들어 주는 것을 볼 수 있습니다. 가짜 개체를 만들 때에 대상이 되는 타입은 인터페이스입니다. 특정 타입이 아닌 인터페이스를 통해서 가짜 개체를 만들어주고 있습니다. 바로 여기서 Mocking Framework를 사용하는 한 가지 이유가 보입니다. 화면과 통신을 각각 담당하여 개발을 한다고 하면 화면을 담당하는 개발자는 통신을 담당하는 개발자가 모든 개발을 하기 전까지 화면에 통신 데이타를 보여주지 못할 것입니다. 하지만 이렇게 인터페이스와 Mocking Framework를 사용한다면 통신 개발이 완료되지 않았다고 하더라도 데이터를 보여줄 수 있습니다. 어떻게? (물론 실제 통신 클래스에서 가짜 데이터를 만들어서 넣어줘도 화면에 보여줄 수는 있습니다.) 바로 위의 코드에 답이 있습니다. StubRepository는 바로 Property라는 이름의 프로퍼티에 값을 할당하는 것으로 MockRepository는 Stub() 메서드를 사용하여 값을 할당하는 방법입니다. Stub과 Mock의 사용 방법에 대한 차이는 알겠는데 실질적으로 어떠한 상황에서 Stub을 또는 Mock를 사용해야 하는 걸까요?

[TestMethod]

public void The_Difference_Between_Stubs_And_Mocks()

{

    var repository = MockRepository.GenerateMock<IRepository>(); 

    // MockRepository.GenerateStub<IRepository>();

 

    var user = new User { Name = "Old User" };

 

    repository.Stub(t => t.GetUser("Old User")).Return(user);

 

    repository.Expect(t => t.Save(user));

 

    var controller = new LoginController(repository);

    controller.ChangeUserName("Old User");

 

    repository.VerifyAllExpectations();

}

=> 먼저 Mock(GenerateMock()메서드 사용)은 위의 Expect()메서드와 같이 실행이 예상되는 메서드를 지정하고 마지막 줄의 VerifyAllExpectatation()로 검증 작업을 합니다. 여기서 검증 대상이 되는 실질적인 클래스는 어떤 것일까요? 바로 LoginController입니다. 해당 코드 중에 new키워드를 사용하여 인스턴스화 한 클래스는 User와 LoginController입니다. Rhino Mocks에서는 바로 이렇게 실질적으로 인스턴스화 한 클래스에서 검증을 합니다. 해당 내용은 마지막 부분에서 Event와 관련된 코드에서 다시 한 번 살펴보도록 하겠습니다.
그러면 검증 대상이 된 LoginController는 어떻게 구현이 되어 있는지 살펴보겠습니다.
public class LoginController
{
    IRepository repository;

    public LoginController(IRepository repository)
    {
        this.repository = repository;
    }

    public void ChangeUserName(string userName)
    {
        var user = repository.GetUser(userName);

        user.Name = "New User";
        repository.Save(user);
    }
}
=> 위의 테스트 코드를 실행시키면 Pass가 됩니다. 주석 처리된 부분을 Stub으로 변경하여도 Pass가 됩니다. 그렇다면 차이점이 없는 것일까요? LoginController에서 Save()메서드가 호출되는 부분을 주석처리 하고 Stub 테스트 코드를 실행하면 오류가 발생하지 않습니다. 정리하면 Expect()메서드로 호출될 것이라 예상된 메서드가 실행이 되든 되지 않든 Pass가 된다는 말입니다. Mock의 경우에는 Fail이 발생합니다. 이것으로 둘의 차이점과 언제 Mock을 언제 Stub을 사용해야 할지를 판단할 수 있습니다. Expect()메서드를 사용 유무로 또한 차이점이기는 하지만 해당 Expect()메서드는 Rhino Mocks에서 제공하는 메서드일 뿐입니다.(아래의 참고 링크를 보시면 Stub에서도 호출 여부를 판단할 수 있는 방법도 존재합니다.) Stub으로 만드는 개체의 경우에는 해당 개체의 행위 또는 값이 해당 테스트 코드와의 상관없거나 영향을 미치지 않는 개체일 경우에 사용하면 되는 것입니다. 예를 들면 어떤 기능을 구현하는 코드는 완성이 되었지만 해당 코드에 보안 관련 기능은 아직 구현이 안되어다고 한다면 보안 관련 기능은 Stub 개체로 만들어서 테스트 코드를 작성하면 되는 것입니다.

기본편에서 살펴볼 마지막은 Event와 관련된 코드입니다.
[TestMethod]

public void Event_Registration()

{

    var view = MockRepository.GenerateMock<Form>();

 

    var presenter = new Presenter(view);

 

    view.Raise(t => t.Load += nullthisEventArgs.Empty);

 

    Assert.IsTrue(presenter.OnLoadCalled);

}

 

public class Presenter

{

    public bool OnLoadCalled { getset; }

 

    private Form loginForm;

 

    public Presenter(Form loginForm)

    {

        this.loginForm = loginForm;

        this.loginForm.Load += new EventHandler(loginForm_Load);

    }

 

    void loginForm_Load(object sender, EventArgs e)

    {

        OnLoadCalled = true;

    }

}

=> 위의 코드에서는 Mock 개체를 인터페이스가 아닌 Form클래스를 대상으로 만들었습니다. Raise()메서드가 해당 코드의 핵심 코드이며 Load이벤트가 발생 여부를 확인의 대상이 되는 클래스는 Presenter 클래스입니다. 이전과 마찬가지고 실질적으로 new키워드를 사용하여 인스턴스화 된 클래스가 검증 대상 클래스가 되며 이러한 이유는 Mock 개체는 일반적으로 인터페이스를 개체화 하기 때문입니다. 즉, 실질적인 생성된 검증 대상 클래스가 있어야 로직(여기서는 Load이벤트)가 실질적으로 발생했는지를 알 수 있기 때문입니다.

본 글에서는 Rhino Mock를 사용하는 기본적인 방법을 살펴보고 해당 방법을 통하여 Mocking Framework가 필요한 이유와 실질적으로 어떠한 상황에서 사용하는지를 간단히 살펴보았습니다.(물론 저의 생각.. ㅋㅋ)

다음 편을 쓸지는 장담할 수 없지만 DB 대신의 저장소의 역할을 하는 예제와 UI와 관련된 테스트 코드 작성 등등 좀 더 실질적으로 사용할 수 방법을 공유하도록 하겠습니다.

참고 사이트
http://ayende.com/wiki/Rhino+Mocks+3.5.ashx
http://builds.hibernatingrhinos.com/builds/Rhino-Mocks
Posted by resisa