NDC/Dev

[NDC2013] 라이브 프로젝트에서 C++로 테스트 주도 개발하기

MAKGA 2021. 11. 13. 00:03
320x100

 


GoogleTest를 써라

TEST(AccountTest, ConstructorWidthBalance)
{
    Account account = Account(10000);
    if (account.Balance() != 10000)
    {
        FAIL();
    }
}

 

장점

xUnit을 포괄하는 기능

GoogleMock과의 연계

Google이 개발

 

 

의존성을 끊어라

TEST(Player, Fall)
{
    Player player;
    player.SetZ(1000);
    player.Fall(1.0f);
    EXPECT_EQ(900, player.GetZ());
}

Player::Player(Server* s, string id)
{
    s->ReceivePlayerInfo(this, id);
}

Player 객체를 생성할 때는 단독으로 생성하는게 거의 불가능하다.

다른 객체와의 연관성이 있기 마련인데, 이를 해결할 수 있는 방법은 가짜 객체다.

 

class Server
{
    void ReceovePlayerInfo(Player* p);
};

를 다음과 같이 변경한다.

class Server
{
    virtual void ReceivePlayerInfo(Player* p);
};

class NullServer : public Server
{
    virtual void ReceivePlayerInfo(Player* p) {};
}

 

TEST(Player, Fall)
{
    NullServer nullServer;
    Player player(&nullServer, "");
    player.SetZ(1000);
    player.Fall(1.0f);
    EXPECT_EQ(900, player.GetZ());
}

Player::Player(Server* s, string id)
{
    s->ReceivePlayerInfo(this, id);
}

 

테스트 작성을 힘들게 하는 유형

  • 객체 생성 불가
    테스트 코드 작성의 시작은 객체 생성 (여기서 막히는 경우가 다반사)
    어떤 클래스가 외부에 너무 의존성이 커서 객체를 쉽게 생성할 수 없는 문제
    주로 Player, Game, World등의 이름이 붙은 클래스에서 발생
    => 의존성 주입으로 해결(생성자 또는 Setter 주입)
    class Player
    {
        Player()
        {
            _server = new Server("192.168.0.1");
            _id = g_session->GetPlayerID();
            _server->GetPlayerInfo(this, _id);
        }
        // 다음과 같이 변환
        Player(Server* server, Session* session)
        {
            _server = server;
            _id = session->GetPlayerID();
            _server->GetPlayerInfo(this, _id);
        }
    };

    => 가짜 객체(Mock)
    TEST()
    {
        NullServer nullServer;
        NullSession nullSession;
        Player player(&nullServer, &nullSession);
    }​
    또는 프레임 워크를 사용한다 (GoogleMock)
  • class Server { void GetPlayerInfo(Player*, string); }; class MockServer : public Server { MOCK_METHOD2(GetPlayerInfo, void(Player*, string)); }; TEST() { MockServer mockServer; MockSession mockSession; EXPECT_CALL(mockSession, GetPlayerID()).WillOnce(Return("haha")); Player player(&mockServer, &mockSession); EXPECT_CALL(mockServer, GetPlayerInfo(&player, "haha")); }​
  • 자유 함수 호출
    send(), recv(), fopen(), CreateWindow(), Direct3DCreate()..
    => 래핑
    class WindowAPI
    {
        virtual HWND CreateWindow(...);
    };
    
    void Game::Init(WindowAPI* api)
    {
        api->CreateWindow(...);
    }
    
    TEST()
    {
        Game game;
        NullWindowAPI api;
        game.Init(&api);
    }
  • 전역 변수 접근
    Item* Inventory::DropItem(string name)
    {
        if (g_player.IsFlying()) {
            return NULL;
        }
    }
    
    TEST()
    {
        Inventory inventory;
        Item* item = new Item("장미칼");
        inventory.AddItem(item);
        EXPECT_EQ(item, inventory.DropItem("장미칼"));
    }​

WORKING EFFECTIVELY WITH LEGACY CODE

CppUnit 원작자, 레거시 코드 테스트 걸기, 24개의 종속성 끊기 기술


시행착오로 얻은 행동요령들

  • 막대 주기 최소화
  • 코드작성 중 리팩토링 금지
  • 복합한 Mock이면 테스트하라
  • 객체 생명 주기는 스마트포인터로

 

출처: 홍종찬 / 넥슨코리아

http://ndcreplay.nexon.com/NDC2013/sessions/NDC2013_0048.html#c=NDC2013

 

NDC Replay

여러분 안녕하세요. 넥슨 CSO팀의 홍종찬입니다. 반갑습니다. 이번 세션은요 테스트 주도 개발에 관한 거에요. 그런데 특히 라이브 프로젝트에서 C++를 사용하는 프로그래머님들을 대상으로 준

ndcreplay.nexon.com


 

320x100