8. Mock Framework 사용 유의사항Mock Object ??사진출처 : http://blog.naver.com/yes10001조각하기 쉬운 재료(나무,점토)를 이용해 추후 만들어질 제품의 양을 흉내 낸 모조품모듈의겉모양이 실제 모듈과 비슷하게 보이도록 만든 가짜 객체소프트웨어 개발
9. Mock Object 사용 예(1/3)UserRegister를 구현, User 암호 저장 기능에 대한 테스트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));}테스트성공!!
10. 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;};MD5 기반으로다른 개발자가독립적으로 구현하기로..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를 만들어 줄 때까지 기다려야 하는가?
11. Mock Object 사용 예(3/3)MD5Cipher처럼 보이는 객체를 만들어서 사용하자.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);}MockMD5Cipher의 구현자체는 임시적이겠지만, 정말 구현하려고 하는 SavePassword기능을 테스트 케이스로 만들기에는 충분한 코드이다.
12. 언제 Mock Object를 만들 것인가?모듈이 가진 의존성이 근본적인 원인모듈이 필요로 하는 의존성은테스트 작성을 어렵게 만든다.
13. 언제 Mock Object를 만들 것인가?1. 테스트 작성을 위한 환경 구축이 어려울 때환경 구축을 위한 작업시간이 많이 필요할 때
16. 아직 모듈 개발이 완료되지 않았거나 심각한 버그가 있는 경우2. 테스트가 특정 경우나 순간에 의존적일 때네트워크 지연에 따른 예외처리를 하고자 하는 경우
17. 테스트 할 때마다 원하는 만큼의 네트워크 지연을 만들기 위해수동적으로 환경을 조성하기에는 노력이 많이 든다.Mock에 대한 분류테스트 대역(Test Double)오리지널 객체를 사용해서 테스트를 진행하기가 어려운 경우이를 대신해서 테스트를 진행할 수 있도록 만들어 주는 객체
24. 테스트 과정에서 메소드 호출이 필요한 경우더미보다 좀더 발전된 객체를 사용해야 한다테스트 스텁(Test Stub)더미 객체가실제로 동작하는 것처럼 보이게 만들어 놓은 객체class StubItem : public Item{public:StubItem() {}; ~StubItem() {}; virtual std::string GetName() { return "체력만땅물약"; } virtual boolIsValid() { return true; } virtual boolIsAppliable(Actor* actor) { return true; } virtual intGetPrice() { return 500; } virtual intGetDiscountPrice() { 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());} 객체의 특정 상태를 가정해서 만들어 놓은단순 구현체이다.
25. 하드 코딩되어 있기 때문에 로직이들어가는 부분은 테스트 할 수 없다가짜 객체(Fake Object)여러 개의 인스턴스를 대표할 수 있는 경우이거나, 좀더 복잡한 구현이 들어가 있는 객체를 지칭한다class FakeObjectItem : public Item{FakeObjectItem() {appliable_state_list_.push_back(Actor::kDeath);appliable_state_list_.push_back(Actor::kZombie);};virtual boolIsAppliable(Actor* actor) { Actor::ActorStateactor_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; }bool Actor::UseItem(Item* item){if(item->IsAppliable(this) == true){ 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));} 가짜 객체를 지나치게 구현하면, 가짜 객체 자체를 테스트 해야 할 정도로복잡해질 수도 있다.
26. 적절한 수준에서 구현을 접고, 필요 시 Mock 프레임워크를 사용해라.테스트 스파이(Test Spy)특정메소드의 정상 호출 여부를 확인할 목적으로 구현 감시대상이 되는 것은무엇이든 기록한다
27. 아주 특수한 경우를 제외하고 잘 쓰이지않는다. 필요한 경우 Mock 프레임워크를이용하는 것이 더 간편함.
28. Mock 프레임워크에서대부분 기본 제공한다.class SpyItem : public Item{SpyItem() {appliable_state_list_.push_back(Actor::kDeath);appliable_state_list_.push_back(Actor::kZombie);};virtual boolIsAppliable(Actor* actor) { Actor::ActorStateactor_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()){ … }IntGetAppliableCallCount() { 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));intmethod_call_count = ((SpyItem*)item)->GetAppliableCallCount(); EXPECT_EQ(1, method_call_count);}
41. 가능하다면 설계를 바꿔서라도 Mock이 필요없는 의존성 적은구조를 만들어라. 그것이 Mock 객체를 쓰기 위해 노력 하는 것 보다 낫다2.Mock은 Mock일 뿐이다. Mock 객체를 사용해 아무리 잘 동작하게 만들었어도, 실제 객체에서도잘 동작하리라는 보장은 없다 Mock객체는 실제 객체로 대체되어 테스트해야하는 시점이 온다.
42. 초반부터 실제 객체를 사용할 수 있고, 그 비용이 크지 않다면 Mock 객체를 사용하지 말자.참고 자료 고품질 쾌속 개발을 위한 TDD실천법과 도구