TDD step by step

   什么是TDD,字面很好理解,曾经的我以为就是先写测试,然后写实现,接着跑测试,通过,收工。今天厚着脸皮抓到高级敏捷咨询师力岩同学一起Pair,告诉我什么是真正的TDD,他以一个小Case的练习把我带进了TDD的大门,下面就来总结一下他是怎么step by step TDD的。
    需求:国际象棋,有一个车(Rook),一个国王(King),当国王和车处于一条横线或一条竖线的时候,就叫做将军(checkmate),现在就根据TDD来实现这个Story。

1、创建测试用例,
    @Test
    //注1:方法命名一定要清楚,代码即文档
    public void should_rook_checkmates_king_when_rook_and_king_in_the_same_line() throws Exception {
        //注2:完全按照自己的想法来写,比方说这儿,我们需要一个rook和一个king,并且要放到一个棋盘上才能判断是否将军,是吧?
        ChessBoard chessboard = new ChessBoard();
        King king = new King();
        Rook rook = new Rook();
        
        chessboard.addKing(king,3,4);
        chessboard.addRook(rook,4,4);
        
        assertThat(rook.isCheckmate(),is(true));
    }
当然此时的代码肯定会有编译错误,这时,我们可以通过IDE的fix功能让编译通过先,记住,只是让编译通过,不要去想新加的类啊,方法的测试,设计什么的。不要偏离主航道,切记。

2、运行测试用例。经过step 1之后,我们会得到几个对象,和一些dummy的方法,因为是dummy的,很明显会报各种错误,NullPointer啊什么的。

3、以最简单的方法让测试通过,不要去考虑具体的实现逻辑实现,代码美观这些问题,专注于一个点,让测试通过,以我们这个测试为例,我们直接让rook的isCheckmate方法返回true就行了。

4、补测试用例,很明显,我们前面的方法很容易就绕过了测试的保护,说明我们的测试用例覆盖不够。接下来,加一个测试用例覆盖没有将军的情况
    @Test
    public void should_not_rook_checkmates_king_when_rook_and_king_is_not_in_the_same_line() {
        ChessBoard chessboard = new ChessBoard();
        King king = new King();
        Rook rook = new Rook();

        chessboard.addKing(king,3,3);
        chessboard.addRook(rook,4,4);

        assertThat(rook.isCheckmate(),is(false));
    }


5、重跑所有测试用例,新补的这个测试用例不能通过。

6、修复失败的测试用例,专注于修复失败用例,怎么简单怎么来,这个Story下我们就需要修改rook的isCheckmate()方法。跟step 1一样,用最简单的方法使编译通过,这时的Rook类代码如下
public class Rook {
    private King king;
    private int x;
    private int y;

    public boolean isCheckmate() {
        //注1:要判读是否将军,必须先知道King的位置吧
        int king_x = getKing().getX();
        int king_y = getKing().getY();
        //注2:横坐标或者纵坐标相同,则将军
        if(x == king_x || y==king_y){
            return true;
        }
        return false;
    }

    public King getKing() {
        return king;
    }
}
同时,King类中也新增了2个dummy方法getX(),getY().

7、重跑所有测试,测试仍然不通过,继续fix测试,进过一系列的修改让测试通过,Rook的代码编程这样了
public class Rook {
    private King king;
    private int x;
    private int y;
    private ChessBoard chessboard;

    public boolean isCheckmate() {
        //注1:要判读是否将军,必须先知道King的位置吧
        int king_x = getKing().getX();
        int king_y = getKing().getY();
        //注2:横坐标或者纵坐标相同,则将军
        if (x == king_x || y == king_y) {
            return true;
        }
        return false;
    }

    public King getKing() {
        //注3:这个king的应该是从一个棋盘上扣下来的才有意义
        return chessboard.getKing();
    }

    //注4:chessboard从哪儿来,给个setter让外部能传入,什么时候设呢?把rook放到棋盘的时候
    public void setChessboard(ChessBoard chessboard) {
        this.chessboard = chessboard;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }
}
还有其他很多修改就不一一列出。

8、所有代码逻辑完成,测试通过,接下来就是Bad Smell的清理,以Rook类为例,很明显可以看出的Bad Smell 有一下几点
public class Rook {
    //smell 1:删除无用变量
    private King king;
    //smell 2: x,y强耦合,可以内聚成一个变量
    private int x;
    private int y;
    private ChessBoard chessboard;

    public boolean isCheckmate() {
        int king_x = getKing().getX();
        int king_y = getKing().getY();
        //smell 3:if条件的含义不明,可以抽出方法
        //smell 4: 整个if语句可以inline成一个语句
        if (x == king_x || y == king_y) {
            return true;
        }
        return false;
    }.....
记住每清理一个Bad Smell就运行一下测试,小步快跑,避免breakdown测试。

9、展示一下最后完成的Rook类
public class Rook {
    private ChessBoard chessboard;
    private ChessBoardPoint position;

    public boolean isCheckmate() {
        return isRookCheckmate();
    }

    private boolean isRookCheckmate() {
        return position.getX() == getKing().getPosition().getX() || position.getY() == getKing().getPosition().getY();
    }

    public King getKing() {
        return chessboard.getKing();
    }

    public void setChessboard(ChessBoard chessboard) {
        this.chessboard = chessboard;
    }

    public void setChessboardPosition(ChessBoardPoint chessBoardPoint) {
        this.position = chessBoardPoint;
    }
}
当然这不是终点,只是起点,可能还有很多bad smell,这整个的TDD的过程中,看着代码一步一步的变优美是一个非常享受的过程。


总结: 今天的Pair,感受最深的是,专注,思想永远在一条线上,不要过多的去考虑当前不涉及的东西。

你可能感兴趣的:(TDD)