Java 测试驱动开发--“井字游戏” 游戏实战

栏目: Java · 发布时间: 6年前

内容简介:Java 测试驱动开发--“井字游戏” 游戏实战

TDD 介绍

TDD是测试驱动开发(Test-Driven Development)的英文简称,是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是敏捷方法的核心实践,但不只适用于XP(Extreme Programming),同样可以适用于其他开发方法和过程。

-- 百度百科

准备工具

TDD只是一种开发模式,它并没有用到新的技术。

  • Java : 因为它是主流的编程语言,应用广泛,相关实践也非常多。

  • IntelliJ-IDEA : Java 主流IDE(集成开发工具)。

  • JUnit : Java 主流单元测试框架,当然,你选择 TestNG 也是完全可以的。

  • Gradle : 构建工具。

TDD 开发模式

红灯 -- 绿灯 -- 重构 流程是TDD的基石。 这个过程就像打乒乓球,快速的在测试代码和实现代码之间切换。

TDD 开的过程: 每次只考虑一个需求。首先编写一个测试,看看它是否未通过;然后编写实现这个测试的代码,运行所有测试并验证它们是否全部通过;最后,通过重构改进代码。不断重复这个过程,直到成功实现所有需求。

需求

本系列实战 “ 井字游戏 ” ,这是一个非常简单的小游戏。

Java 测试驱动开发--“井字游戏” 游戏实战

说明:

是一种在3*3格子上进行的连珠游戏,和五子棋比较类似,由于棋盘一般不画边框,格线排成井字故得名。游戏需要的 工具 仅为纸和笔,然后由分别代表O和X的两个游戏者轮流在格子里留下标记(一般来说先手者为X)。由最先在任意一条直线上成功连接三个标记的一方获胜。

不会玩的同学可以先去完两把: 井字小游戏

需求1

先定义边界,以及将棋子放在哪些地方非法。

可将棋子放在3×3棋盘上任何没有棋子的地方。

将需求分成三个测试:

  • 如果棋子放在超出了X轴边界的地方,就引发 RuntimeException 异常。
  • 如果棋子放在超出了Y轴边界的地方,就引发 RuntimeException 异常。
  • 如果棋子放在已经有棋子的地方,就引发 RuntimeException 异常。

测试用例 1

默认你已经会使用 JUnit 单元测试框架了,根据上面的三个测试,我们先来完成第一个。

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;


public class TicTacToeTest {

    @Rule
    public ExpectedException exception =  ExpectedException.none();

    private TicTacToe ticTacToe;

    @Before
    public final void before() {
        ticTacToe = new TicTacToe();
    }

    @Test
    public void whenXOutsideBoardThenRuntimeException() {
        exception.expect(RuntimeException.class);
        ticTacToe.play(5, 2);
    }

}

测试调用 TicTacToe 类的 play() 方法,假设第一个参数是 x 轴,第二个参数是 y 轴,前面需求已经规定,棋盘是3×3的规格,所以参数必须不能小于1或大于3。

x 轴为5会引发异常。在 whenXOutsideBoardThenRuntimeException() 测试用例中,预期这被测代码会抛出 RuntimeException 异常。

实现功能 1

接下来,我们要实现功能代码了,以满足测试用例通过。

public class TicTacToe {


    public void play(int x, int y) {
        if (x < 1 || x > 3) {
            throw new RuntimeException("X is outside board");
        }
    }
}

实现代码非常简单,创建 TicTacToe 类和 play() 方法,判断 x 参数,如果小于1或大于3 将抛出 RuntimeException 异常。

** 现在再次执行 测试用例 1 检查它是否运行通过。

测试用例 2

继续在 TicTacToeTest 测试类中创建将的测试用例。

……

@Test
public void whenYOutsideBoardThenRuntimeException(){
    exception.expect(RuntimeException.class);
    ticTacToe.play(2,5);
}

这条用例用于验证棋盘 y 轴范围抛 RuntimeException 异常。

实现功能 2

继续修改 TicTacToe 的功能代码。使 测试用例2 运行通过。

public class TicTacToe {


    public void play(int x, int y) {
        if (x < 1 || x > 3) {
            throw new RuntimeException("X is outside board");
        }else if(y < 1 || y > 3){
            throw  new RuntimeException("Y is outside board");
        }
    }

}

这里针对 play() 方法,增加对参数 y 的判断,如果小于1或大于3则抛出 RuntimeException 异常。

** 现在再次执行 测试用例 2 检查它是否运行通过。

** 另外,保证 测试用例 1 也是可以运行通过的。

测试用例 3

继续在 TicTacToeTest 测试类中创建将的测试用例。

……

@Test
public void whenOccupiedThenRuntimeException(){
    ticTacToe.play(2,1);
    exception.expect(RuntimeException.class);
    ticTacToe.play(2,1);
}

如果棋盘上的格子已经被占用,那么不允许再放子上去。

实现功能 3

为了实现测试用例3 ,应该将棋子的位置存储在一个数组中。每当玩家放置新棋子时,都应确认棋子放在未占用的位置,否则引发异常。

public class TicTacToe {


    private Character[][] board = {
            {'\0','\0','\0'},
            {'\0','\0','\0'},
            {'\0','\0','\0'}
    };

    public void play(int x, int y){
        if(x < 1 || x > 3){
            throw new RuntimeException("X is outside board");
        }else if(y < 1 || y > 3){
            throw  new RuntimeException("X is outside board");
        }
        if(board[x-1][y-1] != '\0'){
            throw new RuntimeException("Box is occupied");
        }else {
            board[x-1][y-1] = 'X';
        }
    }

}

检查放置棋子的位置是否被占用,如果未占用,就将相应数组元素的值从空(\0)改为占用(X), 注意, 我们还没有记录棋子是谁(X 还是 O)的。

** 再次执行 测试用例 1、2、3 ,使它们全部运行通过。

重构

虽然 TicTacToe 代码已经满足了测试的需求,但是有点令人迷惑。所以需要对现有的代码进行重构。

public class TicTacToe {


    private Character[][] board = {
            {'\0','\0','\0'},
            {'\0','\0','\0'},
            {'\0','\0','\0'}
    };

    public void play(int x, int y){
        checkAxis(x);
        checkAxis(y);
        setBox(x, y);
    }

    private void checkAxis(int axis){
        if(axis <1 || axis > 3){
            throw new RuntimeException("X is outside board");
        }
    }

    private void setBox(int x, int y){
        if(board[x-1][y-1] != '\0'){
            throw new RuntimeException("Box is occupied");
        }else {
            board[x-1][y-1] = 'X';
        }
    }

}

这次重构,对paly()的处理逻辑进行了拆分,功能与重构前一样。因为我们有测试代码,所以不用担心重构会带来bug。

** 再次执行 测试用例 1、2、3 ,使它们全部运行通过。

完整实例

最后

本系列中的例子,来自 《Java 测试驱动开发》 一书,我这几天在看,本身不厚,两百多页,内容也不虚,我很讨厌,概念讲的高大上,例子只贴关键代码,这种书根本没法下手操作。而这本书中的例子是可以跟着敲下来的,虽然代码有些小错误。如果感兴趣的同学,可以买正版支持一下,我自己写书,所以知道写书的辛苦。

说说TDD 的感受,就是很爽!

  • 看到每次测试用例全部通过很爽!!

  • 重构代码很爽!! 因为有测试用例这一牢靠的后盾。

Java 测试驱动开发--“井字游戏” 游戏实战

看到这样一张报告,不知道你是否为对自己开发的代码充满了信心。我想这就是 TDD 的魅力!!

完整项目代码: https://github.com/defnngj/TDD


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Computational Advertising

Computational Advertising

by Kushal Dave、Vasudeva Varma / Now Publishers Inc / 2014

Computational Advertising (CA), popularly known as online advertising or Web advertising, refers to finding the most relevant ads matching a particular context on the Web. The context depends on the t......一起来看看 《Computational Advertising》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具