'개발/Framework'에 해당되는 글 3건

  1. 2011.05.24 Rhino Mocks 기본편
  2. 2009.09.21 PostSharp AOP Framework 정리
  3. 2009.08.06 Microsot Sync Framework 시작
이전에 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
,
 

1) PostSharp Aspect Lifetime

·         PostSharp 핵심은 바로 Post-Compiler입니다. Post-Compiler 컴파일이 이후의 어셈블리를 수정하는 것을 의미합니다.

 

http://www.codeproject.com/KB/cs/ps-custom-attributes-2.aspx


·         PostSharp에서 Aspect 컴파일 타임에 생성(PostSharp Laos 의해서) 됩니다. 또한 런타임에 수정된 어셈블리를 로드하여 사용하는 복잡한(?!) Aspect Lifetime 가지고 있습니다. 이러한 과정을 자세히 알아보겠습니다.

 

·          컴파일 타임 (숫자는 실질적인 시간의 순서를 나타냅니다.)

1. Aspect 인스턴스화 : 해당 Aspect 적용될 모든 클래스(Target) Aspect 적용할 있는 코드를 만듭니다.

2. 컴파일 타임 초기화 : Aspect 인스턴스 안에 타입, 필드, 메소드를 있도록 초기화 해줍니다. 여기서 초기화 하는 타입, 필드, 메소드는 런타임에서도 사용할 있습니다.

3. 직렬화 : 새롭게 만들어진 어셈블리 리소스에 저장시키기 위해 직렬화해줍니다.

 

·          런타임

4. 1. 역직렬화 : Aspect 처음으로 사용하는 시점에 어셈블리 리소스에 저장된 정보들을 역직렬화합니다.

4. 2. 런타임 초기화 : 타입, 필드, 메소드의 속성을 초기화합니다. 여기서는 역직렬화된 필드를 사용하여 역직렬화된 타입, 필드, 메소드를 저장합니다. (다시 말해서 런타임에서는 직렬화된 정보를 역직렬화 하기 때문에 초기화 메서드에서 역직렬화된 필드를 사용하여야 컴파일 타임에 직렬화되었던 정보를 사용할 있는 것입니다.)

5. 인터셉트 메소드 호출 : Aspect 종류에 따라서 Aspect 실행되어야 시점을 인터셉트해서 호출합니다.

 

 

2) Aspect Kind

종류

내용

인터셉트 메소드

비고

On Method Boundary

메소드의 안에서 일어날 수 있는 시작점과 종료점 등과 같은 인터셉트 메소드를 제공해줌으로써 해당 시점을 인터셉트해 줄 수 있습니다. Trace 트랜잭션를 사용할 경우에 유용합니다.

OnEntry, OnSuccess, OnException, OnExit

 

On Exception

예외가 발생하는 경우에 처리해야 할 내용을 Aspect 만들 경우에 사용합니다.

OnException

 

On Field Access

모든 필드에 대한 인터셉트를 제공해줍니다.

OnGetValue, SetValue

 

On Method Invocation

On Method Boundary와 비슷합니다. 차이점은 다른 어셈블리에 있는 메소드도 인터셉트 할 수 있습니다.

OnInvocation

 

Composition

많은 Sub-Aspect 복합적으로 사용할 경우에 유용합니다.

클래스(Type)에 인터페이스를 구현해줍니다.

GetPublicInterface

CreateImplementationObject

 

Compound

많은 Sub Aspect를 복합적으로 사용할 경우에 유용합니다.

ProvideAspects

 

Implement Method

abstract or extern 메소드를 인터셉트 해줍니다.

OnExecution

 

Custom Attribute Injector

타켓에 속성  어트리뷰트를  추가할 경우에 사용합니다.

 

 

 

3) PostSharp + Enterprise Library(이하 EL)

·         EL에서도 AOP의 기능을 하는 Policy Injection Application Block(이하 PIAB)이 있습니다. PIAB는 런타임 위빙방식으로 설정파일에 의해서 Policy(정책)에 의해서 다른 Application Block를 사용합니다. PIAB MarshalByRefObject를 상속받는 개체를 요구하거나 인터페이스를 상속받는 개체를 요구합니다. (실제로 PIAB 예제를 사용해봤을 경우에는 MarshalByRefObject를 상속받지 않고서도 실행이 잘되었는데 좀 더 확인을 해봐야 할 것 같습니다.)
또한 정책에 의해서 다른 AB를 주입해주면서 Proxy, Factory 개체들을 생성하는데 바로 PostSharp4EntLib에서는 이러한 Proxy Factory를 제거하는 것을 목적으로 CodePlex에서 EL의 확장팩 정도의 개념으로 오픈소스로 공개되어 있습니다.

·         PostSharp4EntLib에서는 PostSharp의 컴파일 타임에 정책에 해당하는 객체들을 만들어놓고 런타임에 Proxy Factory 없이 사용하는 방식이라고 할 수 있습니다. 참고자료 2번에 링크로 가보시면 사용하는 방법이 3가지 나옵니다. 두 가지 아쉬운 점은 3가지 방법중에 마지막 방법이 제대로 동작하지 않는 것과 모든 방식이 정책 부분은 컴파일 타임에 만들어지지만 실질적으로 사용되는 AB의 설정은 App.config에서 불러옵니다. 예를 들어서 정책으로 Logging AB를 사용하면 정책은 컴파일 타임에 만들어지지만 Logging AB에 대한 정보는 App.config에 있어야 한다는 것입니다. 그래서 이 부분을 수정하여 현재는 Logging AB에 대해서만 App.config가 아닌 컴파일 타임에 정책이 만들어지면서 .config의 정보를 어셈블리 리소스에 저장하고 런타임에 불러와 사용하는 방식으로 변경해서 사용하고 있습니다.

4) 참고자료

1. PostSharp Doc : http://doc.postsharp.org/

2. PostSharp4EntLib : http://www.codeplex.com/entlibcontrib/Wiki/View.aspx?title=PostSharp4EntLib&referringTitle=Home

3. PIAB : http://msdn.microsoft.com/en-us/library/cc511729.aspx

 

Posted by resisa
,

아래는 'Microsoft Sync Framework에 대한 고찰'이라는 제목의 글입니다.

http://blog.powerumc.kr/214

 

여기서는 MSF의 동기화 알고리즘과 위의 글에서 '당신이 원하는 것은 뭐야?'에 대해서 살펴보도록 하겠습니다.

 


이해를 돕기 위해서 위의 그림을 실제 코드로 나타내면 아래와 같습니다.

SyncOperationStatistics stats;

SyncOrchestrator agent = new SyncOrchestrator();

agent.Direction = direction;

agent.LocalProvider = providerNameA;

agent.RemoteProvider = providerNameB;

stats = agent.Synchronize();

=> MSF에서 제공해주는 SyncOrchestrator클래스를 생성하고 Direction를 지정해주는데 Direction에서는 Upload, Download, UploadAndDownload, DownloadAndUpload가 있습니다. 그리고 로컬과 원격 Provider를 지정해주고 Synchronize 메소드를 호출해줌으로써 동기화를 수행합니다.

Source는 파일, DB 등이 될 수 있습니다. 대상 소스, 원본 소스라고도 표현을 하는데 이 표현이 헷갈려 좀 더 일반적인(?) Client Server라고 표현했습니다.

MSF에서 제공해주는 Provider를 상속받아서 Client Server Provider를 구현해주고 위의 코드와 같이 호출만 해주면 동기화를 수행하는 것입니다.

MSF에서 제공해주는 Provider의 중요 메소드들은 아래와 같습니다.

메소드명

역할

Source

BeginSession

항목과 항목에 대한 메타데이터를 가져옵니다.

Client(2),
Server(1)

GetChangeBatch

Sever측의 항목 변경에 대한 메타데이터를 Client Provider에게 제공합니다.

Server(3)

ProcessChangeBatch

각 항목 변경에 대해서 SaveItemChange()를 호출합니다.

Client(4)

SaveItemChange

변경된 항목에 대해서 자신의 메타데이터에 기록합니다.

Client(5)

EndSession

변경된 항목을 저장해주고 메타데이터를 닫아줍니다.

Client(6),
Server(7)

=> Direction Download로 주고 Synchronize()메소드를 실행하면 위의 표에서()안에 넣은 순서대로 Provider의 메소드가 실행이 됩니다. MSF에서는 위의 과정을 통해서 단방향 동기화됩니다. 설명의 편의상 Client Server로 표현했을 뿐 만약에 Direction의 방향이 Upload로 변경된다면 Client->Server가 되고 Server->Client가 되어 동기화 과정을 수행하게 됩니다. 마지막으로 Direction DownloadAndUpload UploadAndDownload로 처리하였을 경우에는 위의 과정이 두 번 실행되는 것을 의미합니다.

 

그럼 이제 '당신이 원하는 것이 뭐야?'에 대해서 알아보도록 하겠습니다. 

여기서 설명하는 모든 과정은 아래의 예제에 해당하는 것입니다.

Sync101 RCA with Metadata Store(WCF)

http://code.msdn.microsoft.com/Release/ProjectReleases.aspx?ProjectName=sync&ReleaseId=1713

 

위의 예제의 아키텍처를 간단히 그려본 것입니다. 'Microsoft Sync Framework에 대한 고찰'이라는 글에서도 나오지만 파일로 동기화를 하려면 원격 서버의 폴더를 공유해야하고 노출된 폴더의 사용자 권한 문제 라던지 공유가 되더라도 파일이 10,000개 까지만 공유되는 문제도 있다고 합니다. 이러한 문제들이 WCF를 사용하여 해결할 수 있습니다.

 

먼저 Server측에서는 기존에 만들어 놓은 Provider를 멤버로 하여 메소드들을 WCF의 오퍼레이션(메소드)으로 노출시켜주는 것입니다. 그러면 동기화를 수행할 때마다 위의 표에서 나온 메소드들이 순차적으로 실행이 됩니다. Client측에서는 RemoteProviderProxy라는 프록시 클래스를 만들어서 WCF를 호출해줍니다. 이 과정을 조금 더 자세히 설명하면 WCF에 노출된 클래스의 Client 클래스가 만들어지는데 System.ServiceModel.ClientBase<T>를 상속받는 클래스입니다. 이 클래스를 MSF Provider에 넣을 수 없기 때문에 RemoteProviderProxy라는 클래스를 만들어 MSF에서 제공해주는 Provider를 상속받고 이 안에서 WCF Client클래스를 멤버로 하여 메소드들을 노출해주는 것입니다.

 

Posted by resisa
,