[Testing] Moq.NET (T/B Driven Development)

Agile Development 2009. 6. 29. 12:36 Posted by POWERUMC

 

Moq.NET

Moq 는 "Mock-you" 또는 "Mock" 로 부른다고 합니다. Moq.NET 3.0 은 C# 3.0 과 .NET Framework 3.5 를 통해 Linq Expression Tree 와 Lambda Expression(람대 표현식) 으로 직관적이고 생산적이라고 합니다.

이전에 봤던 웹 사이트 로그인 사용자 스토리를 다시 봅시다. 단, 이 예제에서는 복잡성을 만족하는 항목을 삭제합니다.

웹 사이트의 로그인 사용자 스토리

  • 사용자 아이디는 영문만 입력 가능하고 한글은 입력할 수 없다
  • 사용자 아이디는 최소 3자리, 최대 10자리까지 입력가능하고 초과시 경고 메시지를 보여준다
  • 사용자 비밀번호는 최소 5자리, 최대 20자리까지 입력 가능하고 초과시 경고 메시지를 보여준다

   

위의 사용자 스토리를 Mocking Framework 인 Moq.NET 을 이용하여 TDD와 BDD 형태로 테스트를 작성해 나갈 수 있습니다.

아래의 소스 코드는 Login 인터페이스를 정의한 코드입니다.

public interface ILogin
{

LoginResult Login(string id, string password);

LoginInputResult Valide(string id, string password);

}

   

public enum LoginResult

{

Authentication,

NoAuthentication

}

   

public enum LoginInputResult

{

Success,

MinLengthId,

MaxLengthId,

MinLengthPassword,

MaxLengthPassword

}

   

이제 Login 의 Behavior(행위) 측면에서 주도적인 개발을 하고자 합니다. BDD 이용하여 [그림1] 의 Clean up code 를 얻기 위한 목적이 아닙니다. 좀 더 TDD 에 가깝고, 디자인과 설계에 초점을 맞추고자 합니다.

class Program
{

static void Main(string[] args)

{

var id = "powerumc";

var pwd = "aaaa";

var mock = new Mock<ILogin>();

}

   

private static string minString(int length)

{

return Match<string>.Create( o => o.Length <= length );

}

   

private static string maxString(int length)

{

return Match<string>.Create( o => o.Length >= length );

}

}

   

위의 코드를 통해 Mocking Object 를 생성하도록 Mock<T> 생성자를 볼 수 있습니다. 바로 Mock<T> 를 통해 Mocking Object 를 생성합니다.

아래의 코드는 위의 로그인 사용자 스토리에서 입력되는 아이디/비밀번호의 유효성을 검사하도록 Mocking Object 를 설정합니다.

// Validate

mock.Setup( o => o.Valide(minString(3), It.IsAny<string>()))

.Returns(LoginInputResult.MinLengthId)

.Callback(()=>Console.WriteLine("ERROR> MinLengthId"));

mock.Setup( o => o.Valide(maxString(10), It.IsAny<string>()))

.Returns(LoginInputResult.MaxLengthId)

.Callback(()=>Console.WriteLine("ERROR> MaxLengthId"));

mock.Setup( o => o.Valide(It.IsAny<string>(), minString(3)))

.Returns(LoginInputResult.MinLengthPassword)

.Callback(()=>Console.WriteLine("ERROR> MinLengthPassword"));

mock.Setup( o => o.Valide(It.IsAny<string>(), maxString(20)))

.Returns(LoginInputResult.MaxLengthPassword)

.Callback(()=>Console.WriteLine("ERROR> MaxLengthPassword"));

   

아래의 코드는 유효성을 통과한 아이디/비밀번호로 로그인을 시도하고 결과값을 리턴하는 Mocking Object 입니다.

// Login

mock.Setup( o => o.Login(It.IsAny<string>(), It.IsAny<string>()))

.Returns(LoginResult.NoAuthentication)

.Callback(()=>Console.WriteLine("No Authentication"));

mock.Setup( o => o.Login(id, pwd))

.Returns(LoginResult.Authentication)

.Callback(()=>Console.WriteLine("Success Login"));

   

자, 그럼 가상의 Mocking Object 를 통해 인터페이스만으로 로그인 시도를 해보도록 하겠습니다.

var obj = mock.Object;

var loginInputResult = obj.Valide(id,pwd);

   

if( loginInputResult != LoginInputResult.Success ) return;

   

obj.Login(id,pwd);

   

입력 변수 값에 따라 비록 Mocking Object 지만, 테스트 시나리오를 충분히 검증할 수 있습니다.

Id="aaa", pwd="aaa" 일 경우는 아래와 같은 결과가 나타나겠죠~?

 

Id="powerumc", pwd="aaaa" 일 경우는 아래와 같이 모든 테스트 시나리오를 통과한 결과입니다.

 

그럼, Microsoft Research 프로젝트의 일환인 Pex 를 이용하여 Mocking Object 를 Testing 해보겠습니다. Pex 테스트 프로젝트를 생성하고 아래의 PexMethod 를 만들었습니다.

아래는 테스트 결과입니다. 총 31개의 테스트 케이스 중에 통과되는 1개의 테스트를 확인할 수 있습니다.

자 어떻습니다~? 전혀 구현 코드를 구현하지 않았음에도 인터페이스를 통해 TDD-테스트 주도 개발이 가능합니다. BDD-행위 주도 개발을 통해 번거로운 TDD 의 Red, Green, Refector 의 유한반복 사이클 없이도, 실제 인터페이스의 디자인과 설계에 초점을 맞추어 테스트를 진행하였습니다.

단순히 BDD 가 최고야~ 라는 말은 아닙니다. 우리가 BDD 를 통해 TDD 를 좀 더 쉽고, 근접하게 접근할 수 있습니다. 그리고 코드의 구현이 전혀 없이 오직 디자인과 설계에 초점이 맞추어져 있다는 사실을 알면 됩니다. 바로 Moq.NET 은 Mocking Object 를 통해 TDD 와 BDD 개발을 통해 좀 더 품질 좋은 WhiteBox Testing 의 기대효과를 누릴 수 있습니다.

데이터베이스의 데이터를 조회하여 테스트한다고 할 때에도, 반드시 데이터베이스가 존재하지 않아도 됩니다. 가상의 데이터베이스 커넥션 객체를 만들어서 쓰면 됩니다. 쇼핑몰 사이트에서 결재 프로세스를 테스트해야 하나요? 그렇다면 가상의 객체를 통해 설계된 결재 프로세스가 합당한지 Mocking Object 를 통해 테스트를 하면 됩니다. 그 이후에는 잘 설계된 인터페이스를 구현하기만 하면 되겠죠?

   

이 외에도 WikiPedia 에 Mock Library 의 종류가 있네요. 아무튼 테스트와 Moq.NET 에 대한 내용은 여기까지 하는 것으로 마치도록 하렵니다.

 

참고 문헌

http://en.wikipedia.org/wiki/Mock-object
http://behaviour-driven.org/

그렇다면 BDD (Behavior-Driven Development) !

TDD 는 그렇다고 치고, 이제는 BDD(Behavior-Driven Development-행위 주도 개발) 가 왠말이냐 -_-; 저 또한 Moq 에 생소한 나머지 여기까지 추적하게 되었습니다. 모두가 TDD 가 좋은 줄은 압니다. 종속적인 기능이나 코드가 정상적임을 증명하고 점진적으로 테스트 코드를 만듦으로써 자연스럽게 세부 설계를 생각하게 할 수 있습니다.

나에게 "TDD" 를 요구한다면 나에게 "시간"을 달라

어째든, BDD 는 소프트웨어 품질을 향상하기 위해 개발자간에 협력할 수 있는 Agile Software Development 기법입니다. BDD 의 목표는 TDD 를 수행하기 위한 것이며 TDD 의 접근법을 전환한 것입니다. TDD 의 딱딱한 어휘를 정리하고 설계나 디자인에 초점이 맞추어진 패러다임의 전환이라고 합니다. 그리하여 TDD 를 수행한다는 본질은 변하지 않지만, TDD 를 수행하기 위해 BDD 를 통해 행위 자체는 변할 수 있다는 것입니다.

더 자세한 내용은 아래의 Behavior-Driven Development 공식 사이트를 참고하십시오.

Behaviour-Driven Development
http://behaviour-driven.org/

또한 Agile Software Development 에서 각 이터레이션(Iteration)에서 수행하게 될 사용자 스토리를 통해 기능이나 구현에 대한 스팩을 정의할 수 있습니다. 즉, 애자일의 사용자 스토리는 바로 테스트를 수행하는 테스트 시나리오로 이어지게 됩니다. 헌데, TDD 로만 수행되는 테스트 시나리오는 실제로 변화에 능동적으로 대처하지 못할 수 있는 경우도 존재합니다.

예를 들어, 설계자는 개발자에게 아래의 스팩이 만족하는 로그인 기능의 "사용자 스토리" 를 정의합니다.

웹 사이트의 로그인 사용자 스토리

  • 사용자 아이디는 영문만 입력 가능하고 한글은 입력할 수 없다
  • 사용자 아이디는 최소 3자리, 최대 10자리까지 입력가능하고 초과시 경고 메시지를 보여준다
  • 사용자 비밀번호는 최소 5자리, 최대 20자리까지 입력 가능하고 초과시 경고 메시지를 보여준다
  • 사용자 비밀번호는 복잡성 만족도를 우측에 색깔과 메시지로 보여준다

위의 사용자 스토리에 만족하도록 TDD 를 수행해야 하는데, TDD 를 수행하기 위해서는 반드시 스토리의 우선 순위대로 진행해야 다음의 테스트 코드를 작성할 수 있습니다. 그런데 여기에 엄청난 함정이 있을 수 도 있습니다.

  • 초/중반의 우선순위의 사용자 스토리의 규모가 한 이터레이션의 주기와 맞먹는 경우라면 TDD 를 어떻게 수행할건가요?
  • 또는, 로그인 시나리오(에피소드, 테마) 안에 고객의 쉽지 않은 요구사항이 추가되었다면 어떻게 할건가요?

예를 들면, 설계가 진행 도중 아래와 같은 요구 사항이 추가가 되었다고 가정해 봅시다.

  • 로그인은 SSO(Single Sign On) 을 통해 로그인하며, SSO 중앙 서버를 통해 인증해야 합니다.

 

기가 막히군요. 아직 SSO 서버는 구축이 되지 않은 상태이고, 단일 시스템간에 명확한 프로토콜도 정의되지 않은 시점입니다. TDD 를 수행하기 위해 SSO 중앙 서버와 프로포콜이 구축되지 않는다면 더 이상 로그인과 관련된 작업은 진행할 수 없게 됩니다.

자! 바로 이런 경우 행위 주도 개발-BDD 가 빛을 발할 때입니다. BDD 의 행위 주도 개발은 인터페이스와 구현을 분리하고 인터페이스에 집중할 수 있습니다. 즉, 어떠한 구현 코드가 없이도 BDD 를 통해 인터페이스만으로 테스트나 코드를 통해 설계 작업을 진행할 수 있게 되는 것입니다.

구현 코드가 없이 인터페이스만으로 테스트를 진행한다니요? 이거 말장난 아닙니까? 아닙니다. 객체의 구현은 전혀 알 필요 없습니다. 단, 내부적인 테스트 시나리오를 알고 있는 것만으로 테스트를 진행하게 됨으로써, 인터페이스의 디자인이나 설계에 집중하게 됩니다.