[2011 04 11]mock_object 소개

26
Mock Object 소소 2011. 04. 11 소소소

description

고품질 쾌속개발을위한 TDD 실천법과 도구 4장 Mock Object 정리 & C++ 예제로 수정

Transcript of [2011 04 11]mock_object 소개

Page 1: [2011 04 11]mock_object 소개

Mock Object 소개

2011. 04. 11원종필

Page 2: [2011 04 11]mock_object 소개

TDD? Mock?

Q : …… 한 경우에 TDD 를 어떻게 적용하면 좋을 까요 ?

A : Mock 을 쓰세요 . Mock 을 쓰면 해결 할 수 있어요 .

Mock 은 또 뭐 ???

Page 3: [2011 04 11]mock_object 소개

목차

• Mock Object ??

• Mock Object 사용 예

• 언제 Mock Object 를 만들 것 인가 ?

• Mock 분류

• Test Double 살펴보기

• Mock Framework 사용 유의사항

Page 4: [2011 04 11]mock_object 소개

Mock Object ??

조각하기 쉬운 재료 ( 나무 , 점토 ) 를 이용해 추후 만들어질 제품의 양을 흉내 낸 모조품

모듈의 겉모양이 실제 모듈과 비슷하게 보이도록 만든 가짜 객체

소프트웨어 개발

사진출처 : http://blog.naver.com/yes10001

Page 5: [2011 04 11]mock_object 소개

Mock Object 사용 예 (1/3)

class UserRegister{public:

UserRegister() {};~UserRegister() {};void SavePassword(const std::string& user_id, const

std::string& user_pwd){

user_pwd_table_[user_id] = user_pwd;}std::string GetPassword(const std::string& user_id){

std::map<std::string, std::string>::iterator it = user_pwd_table_.find(user_id);

assert(it != user_pwd_table_.end());return it->second;

}private:

std::map<std::string, std::string> user_pwd_table_;};TEST(PasswordTest, password_ciper_test){

UserRegister* user_register = new UserRegister();

std::string user_id = "aether";std::string user_pwd = "potato";

user_register->SavePassword(user_id, user_pwd);EXPECT_EQ(user_pwd, user_register-

>GetPassword(user_id));}

UserRegister 를 구현 , User 암호 저장 기능에 대한 테스트

테스트성공 !!

Page 6: [2011 04 11]mock_object 소개

Mock Object 사용 예 (2/3)

TEST(PasswordTest, password_ciper_test){

UserRegister* user_register = new UserRegister();// Ciper* ciper = ... 이거 만들어주면되는데 ..

std::string user_id = "aether";std::string user_pwd = "potato";

user_register->SavePassword(user_id, cipher->Encryption(user_pwd));std::string decripted_pwd = cipher->Decryption(user_register-

>GetPassword(user_id));EXPECT_EQ(user_pwd, decripted_pwd);

}

요구 사항 – 사용자 암호는 반드시 암호화한 다음에 저장해야 한다

테스트 코드를 통과시켜야 하는데 ............... 다른 개발자가 MD5 를 기반으로 Cipher 를 만들어 줄 때까지 기다려야 하는가 ?

class Cipher{public:

Cipher();~Cipher();virtual std::string Encryption(const

std::string& source)=0;virtual std::string Decryption(const

std::string& source)=0;};

MD5 기반으로다른 개발자가독립적으로 구현하기로 ..

Page 7: [2011 04 11]mock_object 소개

Mock Object 사용 예 (3/3)

class MockMD5Cipher : public Cipher{public:

MockMD5Cipher() {};~MockMD5Cipher() {};virtual std::string Encryption(const std::string& source) {

return "8ee2027983915ec78acc45027d874316“;}

virtual std::string Decryption(const std::string& source) { return "potato“; }

};TEST(PasswordTest, password_ciper_test){

UserRegister* user_register = new UserRegister();Cipher* cipher = new MockMD5Cipher();

std::string user_id = "aether";std::string user_pwd = "potato";

user_register->SavePassword(user_id, cipher->Encryption(user_pwd));std::string decripted_pwd = cipher->Decryption(user_register-

>GetPassword(user_id));EXPECT_EQ(user_pwd, decripted_pwd);

}

MD5Cipher 처럼 보이는 객체를 만들어서 사용하자 .

MockMD5Cipher 의 구현자체는 임시적이겠지만 , 정말 구현하려고 하는 SavePassword 기능을 테스트 케이스로 만들기에는 충분한 코드이다 .

Page 8: [2011 04 11]mock_object 소개

언제 Mock Object 를 만들 것인가 ?

모듈이 가진 의존성이 근본적인 원인

모듈이 필요로 하는 의존성은테스트 작성을 어렵게 만든다 .

Page 9: [2011 04 11]mock_object 소개

언제 Mock Object 를 만들 것인가 ?

1. 테스트 작성을 위한 환경 구축이 어려울 때• 환경 구축을 위한 작업시간이 많이 필요할 때• 사용하고 있는 DB 와 다른 DB 를 설치해야만 테스트가 가능한 경우

• 특정 모듈을 아직 갖고 있지 않아서 테스트 환경을 구축하지 못할 때• 아직 모듈 개발이 완료되지 않았거나 심각한 버그가 있는 경우

2. 테스트가 특정 경우나 순간에 의존적일 때• 네트워크 지연에 따른 예외처리를 하고자 하는 경우• 테스트 할 때마다 원하는 만큼의 네트워크 지연을 만들기 위해

수동적으로 환경을 조성하기에는 노력이 많이 든다 .

Page 10: [2011 04 11]mock_object 소개

Mock 에 대한 분류

테스트 대역 (Test Double)

• 오리지널 객체를 사용해서 테스트를 진행하기가 어려운 경우 이를 대신해서 테스트를 진행할 수 있도록 만들어 주는 객체

• xUnit Test Patterns 의 저자 제라드 메스자로스 (Gerard Meszaros) 가 용어를 만들고 , 분류하여 사용하기 시작함 .

• 많은 개발자들이 좀 더 포괄적이고 보편적인 개념으로 Mock 이라는 단어를 사용하고 있지만 , 분류법은 알아둘 만함 .

Page 11: [2011 04 11]mock_object 소개

Mock 에 대한 분류

테스트 대역 (Test Double) 의 종류

Test Double

Dummy Object

Test Stub

Test Spy Mock Object

Fake Object

제라드 메스자로스 (Gerard Meszaros)

Page 12: [2011 04 11]mock_object 소개

테스트 대역 (Test Double)하나씩 살펴보기

Page 13: [2011 04 11]mock_object 소개

예제 설명

class Actor{public:

Actor(const std::string actor_name) {};~Actor() {};void AddItem(Item* item)

{ item_list_.push_back(item); }int GetTotalItemCount() { return item_list_.size();

}private:

std::list<Item*> item_list_;};TEST(TestDouble, test_double_dummy_object){

Actor* actor = new Actor("Warrior");EXPECT_EQ(0, actor->GetTotalItemCount());

//Item* item =

actor->AddItem(item);EXPECT_EQ(1, actor->GetTotalItemCount());

}

class Item{public:

Item() {};virtual ~Item() {};virtual std::string GetName() =

0;virtual bool IsValid() = 0;virtual bool IsAppliable(Actor*

actor) = 0;virtual int GetPrice() = 0;virtual int GetDiscountPrice() =

0;};

Actor 에 Item 추가 기능 구현 - Item 은 인터페이스 논의만 된 상태

Page 14: [2011 04 11]mock_object 소개

더미 객체 (Dummy Object)

더미 객체는 말 그대로 모조품 , 단순한 껍데기에 해당한다class DummyItem : public Item{public:

DummyItem() {};~DummyItem() {};virtual std::string GetName() {

assert(false); return "";

}virtual bool IsValid() {

assert(false); return false;

}virtual bool IsAppliable(Actor*

actor) {

assert(false); return false;

}virtual int GetPrice() {

assert(false); return -1;

}virtual int GetDiscountPrice() {

assert(false); return -1;

}};

TEST(TestDouble, test_double_dummy_object){

Actor* actor = new Actor("Warrior");

EXPECT_EQ(0, actor->GetTotalItemCount());

Item* item = new DummyItem();

actor->AddItem(item);EXPECT_EQ(1, actor->GetTotalItemCount());

}

• 단지 인스턴스화 된 객체가 필요할 뿐 객체의 기능까지 필요하지 않은 경우 사용

• 더미 객체의 메소드가 호출 됐을 때의 동작은 보장하지 않는다 .

• 테스트 과정에서 메소드 호출이 필요한 경우 더미보다 좀더 발전된 객체를 사용해야 한다

Page 15: [2011 04 11]mock_object 소개

테스트 스텁 (Test Stub)

더미 객체가 실제로 동작하는 것처럼 보이게 만들어 놓은 객체class StubItem : public Item{public:

StubItem() {};~StubItem() {};virtual std::string GetName() {

return " 체력만땅물약 ";

}virtual bool IsValid() {

return true; }virtual bool IsAppliable(Actor*

actor) {

return true; }virtual int GetPrice() {

return 500; }virtual int GetDiscountPrice() {

return 200; }

};

TEST(TestDouble, test_double_stub_object){

Actor* actor = new Actor("Warrior");

EXPECT_EQ(0, actor->GetTotalItemCount());

Item* item = new StubItem();actor->AddItem(item);

Item* last_added_item = actor-

>GetLastAddedItem();

EXPECT_EQ(500, last_added_item-

>GetPrice());EXPECT_EQ(" 체력만땅물약 ",

last_added_item->GetName());}

• 객체의 특정 상태를 가정해서 만들어 놓은 단순 구현체이다 .

• 하드 코딩되어 있기 때문에 로직이 들어가는 부분은 테스트 할 수 없다

Page 16: [2011 04 11]mock_object 소개

가짜 객체 (Fake Object)여러 개의 인스턴스를 대표할 수 있는 경우이거나 , 좀더 복잡한 구현이 들어가 있는 객체를 지칭한다

class FakeObjectItem : public Item{FakeObjectItem() {

appliable_state_list_.push_back(Actor::kDeath);appliable_state_list_.push_back(Actor::kZombie);

};virtual bool IsAppliable(Actor* actor) {

Actor::ActorState actor_state = actor->GetActorState();

std::list<Actor::ActorState>::iterator it = std::find(appliable_state_list_.begin(),appliable_state_list_.end(),

actor_state);if(it != appliable_state_list_.end()){

return true;}return false;

}

TEST(TestDouble, test_double_fake_object){

Actor* actor = new Actor("Warrior");

EXPECT_EQ(0, actor->GetTotalItemCount());actor->SetActorState(Actor::kWalk);Item* item = new StubItem();EXPECT_EQ(true, actor->UseItem(item));Item* fake_item = new

FakeObjectItem();EXPECT_EQ(false, actor-

>UseItem(fake_item));actor->SetActorState(Actor::kDeath);EXPECT_EQ(true, actor-

>UseItem(fake_item));}

bool Actor::UseItem(Item* item){

if(item->IsAppliable(this) == true)

{return true;

}return false;

}

• 가짜 객체를 지나치게 구현하면 , 가짜 객체 자체를 테스트 해야 할 정도로 복잡해질 수도 있다 .• 적절한 수준에서 구현을 접고 , 필요 시 Mock 프레임워크를 사용해라 .

Page 17: [2011 04 11]mock_object 소개

테스트 스파이 (Test Spy)특정 메소드의 정상 호출 여부를 확인할 목적으로 구현

class SpyItem : public Item{SpyItem() { appliable_state_list_.push_back(Actor::kDeath); appliable_state_list_.push_back(Actor::kZombie);};virtual bool IsAppliable(Actor* actor) { Actor::ActorState actor_state = actor->GetActorState(); std::list<Actor::ActorState>::iterator it = std::find(appliable_state_list_.begin(),appliable_state_list_.end(), actor_state); is_appliable_call_count ++; if(it != appliable_state_list_.end()) { … }Int GetAppliableCallCount() { return is_appliable_call_count ++; }TEST(TestDouble, test_double_test_spy){ Actor* actor = new Actor("Warrior"); EXPECT_EQ(0, actor->GetTotalItemCount());

actor->SetActorState(Actor::kDeath);

Item* item = new SpyItem(); EXPECT_EQ(true, actor->UseItem(item));

int method_call_count = ((SpyItem*)item)->GetAppliableCallCount(); EXPECT_EQ(1, method_call_count);}

• 감시 대상이 되는 것은 무엇이든 기록한다

• 아주 특수한 경우를 제외하고 잘 쓰이지 않는다 . 필요한 경우 Mock 프레임워크를 이용하는 것이 더 간편함 .

• Mock 프레임워크에서 대부분 기본 제공한다 .

Page 18: [2011 04 11]mock_object 소개

Mock 객체 (Mock Object)

행위 기반 테스트 Vs 상태 기반 테스트

• 상태 기반 테스트• 테스트 대상 클래스의 메소드를 호출하고 , 그 결과값과

예상 값을 비교하는 방법

MethodA

입력 A

결과 값예상 값??

Page 19: [2011 04 11]mock_object 소개

Mock 객체 (Mock Object)

행위 기반 테스트 Vs 상태 기반 테스트

• 행위 기반 테스트• 올바른 로직 수행 판단의 근거로 특정한 동작의

수행여부를 이용한다 . • 리턴 값을 확인하는 것만으로는 예상대로 동작했음을

보증하기 어려운 경우에 사용한다

MethodA

입력 A

MethodB MethodA

입력 B

MethodB실행

Page 20: [2011 04 11]mock_object 소개

Mock 객체 (Mock Object)

Mock 객체는 행위를 검증하기 위해 사용되는 객체• 수동으로 만들 수도 있지만 , 대부분 Mock 프레임워크를 이용• 대표적인 C++ Mock 프레임워크• Google Mock

class MockItem : public Item{public: MockItem() {}; virtual ~MockItem() {};

MOCK_METHOD0(GetName, std::string()); MOCK_METHOD0(IsValid, bool()); MOCK_METHOD1(IsAppliable, bool(Actor*)); MOCK_METHOD0(GetPrice, int());};

TEST(TestDouble, test_double_gmock_test){ Actor* actor = new Actor("Warrior"); EXPECT_EQ(0, actor->GetTotalItemCount());

MockItem* item = new MockItem();

EXPECT_CALL(*item, IsAppliable(actor)) .Times(::testing::AtLeast(2)) .WillOnce(::testing::Return(true)) .WillOnce(::testing::Return(false));

EXPECT_EQ(true, actor->UseItem(item)); EXPECT_EQ(false, actor->UseItem(item));}

Page 21: [2011 04 11]mock_object 소개

Googlemock(code.google.com/p/googlemock)

• jMock, EasyMock, Hamcrest 의 영향을 받아 만들어진 C++ Mocking Framework •googlemock 자료는 이곳에 있는 게 거의 전부

Page 22: [2011 04 11]mock_object 소개

Test Double 의 연속성

http://msdn.microsoft.com/ko-kr/magazine/cc163358.aspx

Page 23: [2011 04 11]mock_object 소개

Mock 사용시 유의사항1. Mock 프레임워크가 정말 필요한지 잘 따져본다• Mock 을 사용하는 것 자체가 목적이 되면 안된다 . Mock 객체가 필요한 부분이 나오는 것이 아니라 , Mock 객체가 적용될 수 있는 부분을 찾으려고 애쓰는 현상은 올바르지 못하다• 한번 Mock 프레임워크를 사용하면 해당 테스트 케이스를 유지하는데 지속적인 비용이 발생한다 .• 가능하다면 설계를 바꿔서라도 Mock 이 필요없는 의존성 적은 구조를 만들어라 . 그것이 Mock 객체를 쓰기 위해 노력 하는 것 보다 낫다

2. Mock 은 Mock 일 뿐이다 .• Mock 객체를 사용해 아무리 잘 동작하게 만들었어도 , 실제 객체에서도 잘 동작하리라는 보장은 없다 Mock 객체는 실제 객체로 대체되어 테스트해야

하는 시점이 온다 .

• 초반부터 실제 객체를 사용할 수 있고 , 그 비용이 크지 않다면 Mock 객체를 사용하지 말자 .

Page 24: [2011 04 11]mock_object 소개

참고 자료

• 고품질 쾌속 개발을 위한 TDD 실천법과 도구

• xUnit 테스트 패턴

• C++ 프로젝트에 단위 테스트 도입하기 ( 온라인서버프로그래머 세미나 발표자료 )

Page 25: [2011 04 11]mock_object 소개

Q / A

Page 26: [2011 04 11]mock_object 소개

END