Mock Object 2011 04 11 Mock Object 13
Mock Object 소개 2011. 04. 11 원종필
Mock Object 사용 예(1/3) User. Register를 구현, User 암호 저장 기능에 대한 테스트 class User. Register { public: User. Register() {}; ~User. Register() {}; void Save. Password(const std: : string& user_id, const std: : string& user_pwd) { } user_pwd_table_[user_id] = user_pwd; std: : string Get. Password(const std: : string& user_id) { private: }; } std: : map<std: : string, std: : string>: : iterator it = user_pwd_table_. find(user_id); assert(it != user_pwd_table_. end()); return it->second; std: : map<std: : string, std: : string> user_pwd_table_; TEST(Password. Test, password_ciper_test) { User. Register* user_register = new User. Register(); std: : string user_id = "aether"; std: : string user_pwd = "potato"; } user_register->Save. Password(user_id, user_pwd); EXPECT_EQ(user_pwd, user_register->Get. Password(user_id)); 테스트 성공!!
Mock Object 사용 예(2/3) 요구 사항 – 사용자 암호는 반드시 암호화한 다음에 저장해야 한다 class Cipher { public: Cipher(); ~Cipher(); virtual std: : string Encryption(const std: : string& source)=0; virtual std: : string Decryption(const std: : string& source)=0; }; MD 5 기반으로 다른 개발자가 독립적으로 구현하기로. . TEST(Password. Test, password_ciper_test) { User. Register* user_register = new User. Register(); // Ciper* ciper =. . . 이거 만들어주면되는데. . std: : string user_id = "aether"; std: : string user_pwd = "potato"; user_register->Save. Password(user_id, cipher->Encryption(user_pwd)); std: : string decripted_pwd = cipher->Decryption(user_register->Get. Password(user_id)); EXPECT_EQ(user_pwd, decripted_pwd); } 테스트 코드를 통과시켜야 하는데. . . . 다른 개발자가 MD 5를 기반으로 Cipher를 만들어 줄 때까지 기다려야 하는가?
Mock Object 사용 예(3/3) MD 5 Cipher처럼 보이는 객체를 만들어서 사용하자. class Mock. MD 5 Cipher : public Cipher { public: Mock. MD 5 Cipher() ~Mock. MD 5 Cipher() {}; virtual std: : string Encryption(const std: : string& source) { return "8 ee 2027983915 ec 78 acc 45027 d 874316“; } }; virtual std: : string Decryption(const std: : string& source) { return "potato“; } TEST(Password. Test, password_ciper_test) { User. Register* user_register = new User. Register(); Cipher* cipher = new Mock. MD 5 Cipher(); std: : string user_id = "aether"; std: : string user_pwd = "potato"; } user_register->Save. Password(user_id, cipher->Encryption(user_pwd)); std: : string decripted_pwd = cipher->Decryption(user_register->Get. Password(user_id)); EXPECT_EQ(user_pwd, decripted_pwd); Mock. MD 5 Cipher의 구현자체는 임시적이겠지만, 정말 구현하려고 하는 Save. Password 기능을 테스트 케이스로 만들기에는 충분한 코드이다.
Mock에 대한 분류 테스트 대역(Test Double)의 종류 Test Doubl e Dummy Object Test Stub Test Spy Mock Object Fake Object 제라드 메스자로스(Gerard Meszaros)
class Item { public: 예제 설명 Item() {}; virtual ~Item() Actor에 Item 추가 기능 구현 - Item은 인터페이스 논의만 된 상태 0; {}; virtual std: : string Get. Name() = 0; virtual bool Is. Valid() = 0; virtual bool Is. Appliable(Actor* actor) = virtual int Get. Price() = 0; virtual int Get. Discount. Price() = 0; }; class Actor { public: Actor(const std: : string actor_name) ~Actor() {}; private: {}; void Add. Item(Item* item) { item_list_. push_back(item); } int Get. Total. Item. Count() { return item_list_. size(); } std: : list<Item*> item_list_; }; TEST(Test. Double, test_double_dummy_object) { Actor* actor = new Actor("Warrior"); EXPECT_EQ(0, actor->Get. Total. Item. Count()); //Item* item = actor->Add. Item(item); EXPECT_EQ(1, actor->Get. Total. Item. Count()); }
더미 객체(Dummy Object) 더미 객체는 말 그대로 모조품, 단순한 껍데기에 해당한다 class Dummy. Item : public Item { public: Dummy. Item() {}; ~Dummy. Item() {}; virtual std: : string Get. Name() { assert(false); return ""; } virtual bool Is. Valid() { assert(false); return false; } virtual bool Is. Appliable(Actor* actor) { assert(false); return false; } virtual int Get. Price() { assert(false); return -1; } virtual int Get. Discount. Price() { assert(false); return -1; } }; TEST(Test. Double, test_double_dummy_object) { Actor* actor = new Actor("Warrior"); EXPECT_EQ(0, actor->Get. Total. Item. Count()); Item* item = new Dummy. Item(); actor->Add. Item(item); EXPECT_EQ(1, actor->Get. Total. Item. Count()); } • 단지 인스턴스화 된 객체가 필요할 뿐 객체의 기능까지 필요하지 않은 경우 사용 • 더미 객체의 메소드가 호출 됐을 때의 동작은 보장하지 않는다. • 테스트 과정에서 메소드 호출이 필요한 경우 더미보다 좀더 발전된 객체를 사용해야 한다
테스트 스텁(Test Stub) 더미 객체가 실제로 동작하는 것처럼 보이게 만들어 놓은 객체 class Stub. Item : public Item { public: Stub. Item() {}; ~Stub. Item() {}; virtual std: : string Get. Name() { return "체력만땅물약"; } virtual bool Is. Valid() { return true; } virtual bool Is. Appliable(Actor* actor) { return true; } virtual int Get. Price() { return 500; } virtual int Get. Discount. Price() { return 200; } TEST(Test. Double, test_double_stub_object) { Actor* actor = new Actor("Warrior"); EXPECT_EQ(0, actor->Get. Total. Item. Count()); Item* item = new Stub. Item(); actor->Add. Item(item); Item* last_added_item = actor->Get. Last. Added. Item(); EXPECT_EQ(500, last_added_item->Get. Price()); EXPECT_EQ("체력만땅물약", last_added_item->Get. Name()); } • 객체의 특정 상태를 가정해서 만들어 놓은 단순 구현체이다. • 하드 코딩되어 있기 때문에 로직이 들어가는 부분은 테스트 할 수 없다
가짜 객체(Fake Object) 여러 개의 인스턴스를 대표할 수 있는 경우이거나, 좀더 복잡한 구현이 들어가 있는 객체를 지칭한다 class Fake. Object. Item : public Item bool Actor: : Use. Item(Item* item) { { Fake. Object. Item() if(item->Is. Appliable(this) == true) { { appliable_state_list_. push_back(Actor: : k. Death); return true; appliable_state_list_. push_back(Actor: : k. Zombie); } }; return false; virtual bool Is. Appliable(Actor* actor) } { Actor: : Actor. State actor_state = actor->Get. Actor. State(); std: : list<Actor: : Actor. State>: : iterator it = std: : find(appliable_state_list_. begin(), appliable_state_list_. end(), actor_state); if(it != appliable_state_list_. end()) { return true; TEST(Test. Double, test_double_fake_object) } { return false; Actor* actor = new Actor("Warrior"); } EXPECT_EQ(0, actor->Get. Total. Item. Count()); actor->Set. Actor. State(Actor: : k. Walk); Item* item = new Stub. Item(); • 가짜 객체를 지나치게 구현하면, EXPECT_EQ(true, actor->Use. Item(item)); 가짜 객체 자체를 테스트 해야 할 정도로 Item* fake_item = new Fake. Object. Item(); EXPECT_EQ(false, actor->Use. Item(fake_item)); 복잡해질 수도 있다. actor->Set. Actor. State(Actor: : k. Death); • 적절한 수준에서 구현을 접고, 필요 시 EXPECT_EQ(true, actor->Use. Item(fake_item)); } Mock 프레임워크를 사용해라.
테스트 스파이(Test Spy) 특정 메소드의 정상 호출 여부를 확인할 목적으로 구현 class Spy. Item : public Item { Spy. Item() { appliable_state_list_. push_back(Actor: : k. Death); appliable_state_list_. push_back(Actor: : k. Zombie); }; virtual bool Is. Appliable(Actor* actor) { Actor: : Actor. State actor_state = actor->Get. Actor. State(); std: : list<Actor: : Actor. State>: : iterator it = std: : find(appliable_state_list_. begin(), appliable_state_list_. end(), actor_state); • 감시 대상이 되는 것은 무엇이든 기록한다 • 아주 특수한 경우를 제외하고 잘 쓰이지 않는다. 필요한 경우 Mock 프레임워크를 이용하는 것이 더 간편함. is_appliable_call_count ++; if(it != appliable_state_list_. end()) {…} Int Get. Appliable. Call. Count() { return is_appliable_call_count ++; } TEST(Test. Double, test_double_test_spy) { Actor* actor = new Actor("Warrior"); EXPECT_EQ(0, actor->Get. Total. Item. Count()); actor->Set. Actor. State(Actor: : k. Death); Item* item = new Spy. Item(); EXPECT_EQ(true, actor->Use. Item(item)); } int method_call_count = ((Spy. Item*)item)->Get. Appliable. Call. Count(); EXPECT_EQ(1, method_call_count); • Mock 프레임워크에서 대부분 기본 제공한다.
Mock 객체(Mock Object) Mock 객체는 행위를 검증하기 위해 사용되는 객체 • 수동으로 만들 수도 있지만, 대부분 Mock 프레임워크를 이용 • 대표적인 C++ Mock 프레임워크 • Google Mock class Mock. Item : public Item { public: Mock. Item() {}; virtual ~Mock. Item() {}; TEST(Test. Double, test_double_gmock_test) { Actor* actor = new Actor("Warrior"); EXPECT_EQ(0, actor->Get. Total. Item. Count()); Mock. Item* item = new Mock. Item(); EXPECT_CALL(*item, Is. Appliable(actor)). Times(: : testing: : At. Least(2)). Will. Once(: : testing: : Return(true)). Will. Once(: : testing: : Return(false)); MOCK_METHOD 0(Get. Name, std: : string()); MOCK_METHOD 0(Is. Valid, bool()); MOCK_METHOD 1(Is. Appliable, bool(Actor*)); MOCK_METHOD 0(Get. Price, int()); } EXPECT_EQ(true, actor->Use. Item(item)); EXPECT_EQ(false, actor->Use. Item(item));
Googlemock (code. google. com/p/googlemock) • j. Mock, Easy. Mock, Hamcrest의 영향을 받아 만들어진 C++ Mocking Framework • googlemock 자료는 이곳에 있는 게 거의 전부
Test Double의 연속성 http: //msdn. microsoft. com/ko-kr/magazine/cc 163358. aspx
Q/A
END
- Slides: 26