首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

面向 C++ 的测试驱动开发(3)

面向 C++ 的测试驱动开发(3)

  • 我不能放棋子到已被占用的棋位置上。这个需求是个验证性需求,要保证棋子不能重叠和覆盖已在棋盘上的棋子,实现这个需求我只要重构现有的代码加上避免棋子重叠的逻辑。只要避免在 PutChess 时候,检查是否指定的位置是否已有棋子,如果是简单的抛出异常即可。有了这些基本的思路,我开始设计测试用例。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    TEST_F(TicTacToeTestFixture,BizException_Occupied){

        IGameBoard *gameBoard=new SimpleGameBoard("simple board");
    char xChar='X',yChar='0';
    EXPECT_NO_THROW(gameBoard->utChess(0,0,xChar));
    EXPECT_THROW(gameBoard->utChess(0,0,xChar),ChessOverlapException);
    EXPECT_NO_THROW(gameBoard->utChess(2,2,yChar));
    EXPECT_THROW(gameBoard->utChess(2,2,yChar),ChessOverlapException);
    delete gameBoard;
    }




    ChessOverlapException 是我将要实现的一个异常类,这个是在棋手试图放棋子到已有棋子的棋盘位置上时要抛出的异常。测试用例中,我在(0,0)和(2,2)这两个位置上放同样的棋子以触发这个异常。为了编译通过,我开始实现    ChessOverlapException。 ChessOverlapException 继承自 std::exception 我重载了 what 函数返回相应的异常信息。    把这个异常类的定义引入的测试工程中,编译通过运行测试,但却得到了红色 Red,案例失败:
    图    6.测试用例输出点击查看大图
    原因是我还没有重构 PutChess 函数以加入避免棋子被被覆盖的代码。现在来重构 PutChess 函数:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void SimpleGameBoard:utChess( int x,int y,char chess )
    {
    assert(x<xMaxDim&&y<yMaxDim);
    int xy=x*3+y;
    if(data_.size()==0){
            initboard_();
            data_[xy]=chess;
    return ;
        }
    if(data_[xy]!='+') {
    throw ChessOverlapException("chess overlap!");
        }
    else data_[xy]=chess;
    }




    重新编译测试工程并运行得到绿色 Green 通过。继续下一个需求。
  • 我要能判断是不是棋盘已满并无赢家。 这个需求用于判断是否是和棋的情况,棋盘满了但并无赢家,这是可能出现的一种情况,这个实现设计可以有两种方式. 一是重构 CheckWinOut 函数,使返回值携带更多的信息,比如和棋,有人胜出等。二是定义一个独立的函数去判断棋盘的当前状态。第一种方案较合理,开始设计这种方案的测试用例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    EST_F(TicTacToeTestFixture,IsEndedInADraw)
    {
    char xChess='X',yChess='O';
    IGameBoard *gameBoard=new SimpleGameBoard("simpleBoard");
    gameBoard->utChess(0,0,yChess);gameBoard->utChess(0,1,xChess);gameBoard->utChess(0,2,yChess);
    gameBoard->utChess(1,0,xChess);gameBoard->utChess(1,1,yChess);gameBoard->utChess(1,2,yChess);
    gameBoard->PutChess(2,0,xChess);gameBoard->PutChess(2,1,yChess);gameBoard->PutChess(2,2,xChess);

    GameBoardStatus status=gameBoard->CheckWinOut(yChess);
    EXPECT_TRUE(status==GAMEDRAW); <br>GameBoardStatus status2=gameBoard->CheckWinOut(xChess); EXPECT_TRUE(status2==GAMEDRAW);
    delete gameBoard;
    }




    以上的测试用例可以看出, 我设计了和棋的棋局,并想重构 CheckWinout 函数,使其返回枚举类型 GameBoardStatus 以表示棋局的状态,其中 GAMEDRAW 表示和棋状态。为了使工程能编译通过,开始定义这个枚举类型并重构 CheckWinOut 函数。实现所有设计,经过几次的 Red 失败,最终 形成代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    GameBoardStatus SimpleGameBoard::CheckWinOut(char chess)
    {

    if(IsThreeInLine_(chess)){
    return GAMEMWINOUT;
        }
    else if(IsEndedInADraw_()){
    return GAMEDRAW;
        }
    else{
    return GAMERUNNING;
        }
    }




    其中那个 IsEndedInADraw_是个受保护的成员函数,用于检测是否和棋。 在调通这个测试用例的过程中,我也更新了测试JugeThreeInLine。因为重构 ChecWinOut 改变了返回类型。
  • 我需要能复位棋盘,以便于重新开始下棋。
  • 我需要用对记住玩家,以便于我能特例化 Player。6 和 7 需求的测试案例和实现比较比较简单,不在赘述,7 的要求是要建立玩家 Player,这个主要是说要能实例化玩家。可以看附带的工程。
  • 我需要能保存和加载棋局能力,以便于我能下次回来继续之前的游戏。这个需求是一个合理的需求,玩家可以保存和继续回来玩游戏,他的测试用例可以这样设计:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    TEST_F(TicTacToeTestFixture,SaveTheBoard)
    {
    IGameBoard * gameBoard=new SimpleGameBoard("simpleBoard");
    char xChess='x',yChess='o';
    gameBoard->PutChess(0,0,xChess);
    gameBoard->PutChess(1,2,yChess);
    IGameIO *gameIO=new SimpleGameIO();
    EXPECT_NO_THROW(gameIO->save(gameBoard,"somewhere"));
    delete gameBoard;
    delete gameIO;
    }
    TEST_F(TicTacToeTestFixture,LoadTheBoard)
    {
    IGameBoard * gameBoard=new SimpleGameBoard("simpleBoard");
    char xChess='x',yChess='o';
    gameBoard->PutChess(0,0,xChess);
    gameBoard->PutChess(1,2,yChess);
    IGameIO *gameIO=new SimpleGameIO();
    EXPECT_NO_THROW(gameIO->save(gameBoard,"somewhere"));
    IGameBoard *game=gameIO->load("somewhere");
    EXPECT_EQ(xChess,game->GetChess(0,0));
    EXPECT_EQ(yChess,game->GetChess(1,2));
    EXPECT_EQ('+',game->GetChess(2,2));
    delete game;




    1
    2
    3
    delete gameBoard;
    delete gameIO;
    }




    这里用两个测试用例来覆盖这个需求,一个是保存棋盘,一个是加载棋盘。由这个测试用例可以看到,要通过这个测试,必须要定义 IGameIO 接口和 SimpeGameIO 类。 保存棋盘的媒介是文件。按照 TDD 的开发要求,测试单元本身最好是脱离对第三方系统的依赖,但测试中必然会用到第三方系统,解决这些问题的方法有几种。创建第三方系统的 Stub 类或是 FakedObject,第三种选择是 Mock 框架,如 Gmock。 Gmock 的设计理念是基于接口的,只要是第三方访问提供的是接口,这些访问就可以可以被用 Gmock 模拟。可以看参考文献获取更多的信息。 限于篇幅不再赘述。一下是完成所有测试用例的测试结果。
    图    7.测试用例输出或许你会注意到有些测试用例的设计,只是以点盖面,如果想要更多的验证点可以借助于 Gtest            提供的参数化测试设计测试数据,然后去测试实现的类和逻辑。 还有死亡测试的用例,可以在参考资源中的 Gtest 资源中查看。
返回列表