Java 测试驱动开发--“井字游戏” 游戏实战
TDD 介绍
TDD是测试驱动开发(Test-Driven Development)的英文简称,是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是敏捷方法的核心实践,但不只适用于XP(Extreme Programming),同样可以适用于其他开发方法和过程。
-- 百度百科
### 准备工具
TDD只是一种开发模式,它并没有用到新的技术。
Java : 因为它是主流的编程语言,应用广泛,相关实践也非常多。
IntelliJ-IDEA : Java 主流IDE(集成开发工具)。
Gradle : 构建工具。
#### TDD 开发模式
“红灯 -- 绿灯 -- 重构” 流程是TDD的基石。 这个过程就像打乒乓球,快速的在测试代码和实现代码之间切换。
TDD 开的过程: 每次只考虑一个需求。首先编写一个测试,看看它是否未通过;然后编写实现这个测试的代码,运行所有测试并验证它们是否全部通过;最后,通过重构改进代码。不断重复这个过程,直到成功实现所有需求。
### 需求
本系列实战 “井字游戏” ,这是一个非常简单的小游戏。

说明:
是一种在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 ,使它们全部运行通过。
### 完整实例
TDD实战(一)开发模式
TDD实战(二)“井字游戏” 需求一
TDD实战(三)“井字游戏” 需求二
TDD实战(四)“井字游戏” 需求三
TDD实战(五)“井字游戏” 需求三
TDD实战(六)“井字游戏” 需求四
TDD实战(七)Jacoco 代码覆盖率
### 最后
本系列中的例子,来自《Java 测试驱动开发》 一书,我这几天在看,本身不厚,两百多页,内容也不虚,我很讨厌,概念讲的高大上,例子只贴关键代码,这种书根本没法下手操作。而这本书中的例子是可以跟着敲下来的,虽然代码有些小错误。如果感兴趣的同学,可以买正版支持一下,我自己写书,所以知道写书的辛苦。
说说TDD 的感受,就是很爽!
看到每次测试用例全部通过很爽!!
重构代码很爽!! 因为有测试用例这一牢靠的后盾。

看到这样一张报告,不知道你是否为对自己开发的代码充满了信心。我想这就是 TDD 的魅力!!
完整项目代码:https://github.com/defnngj/TDD
Java 测试驱动开发--“井字游戏” 游戏实战的更多相关文章
- 基于Python的测试驱动开发实战
近年来测试驱动开发(TDD)受到越来越多的关注.这是一个持续改进的过程,能从一开始就形成规范,帮助提高代码质量.这是切实可行的而非天马行空的. TDD的全过程是非常简单的.借助TDD,代码质量会得到提 ...
- Android快乐贪吃蛇游戏实战项目开发教程-01项目概述与目录
一.项目简介 贪吃蛇是一个很经典的游戏,也很适合用来学习.本教程将和大家一起做一个Android版的贪吃蛇游戏. 我已经将做好的案例上传到了应用宝,无病毒.无广告,大家可以放心下载下来把玩一下.应用宝 ...
- TDD(测试驱动开发)学习二:创建第一个TDD程序
本节我们将学习一些测试驱动开发环境的搭建,测试驱动开发概念和流程.所涉及的内容全部会以截图的形式贴出来,如果你也感兴趣,可以一步一步的跟着来做,如果你有任何问题,可以进行留言,我也会很高兴的为你答疑. ...
- 测试驱动开发(TDD)
测试驱动开发的基本概念 为什么会出现测试驱动开发 当有一个新的任务时,往往第一个念头就是如何去实现它呢? 拿到任务就开始编码,一边写,变修改和设计 我已经调试了好几遍,应该不会有问题了,好了,先休息一 ...
- 测试驱动开发实践2————从testList开始
内容指引 1.测试之前 2.改造dto层代码 3.改造dao层代码 4.改造service.impl层的list方法 5.通过testList驱动domain领域类的修改 一.测试之前 在" ...
- 【Cocos游戏实战】功夫小子第八课之游戏打包和相关问题说明
至此,功夫小子系列的Cocos2d-x的实战文章就结束了. 源代码地址: https://github.com/SuooL/KungfuBoy 如须要资源请邮件我 1020935219@qq.com ...
- C#-面向对象:争议TDD(测试驱动开发)
----------------------- 绝对原创!版权所有,转发需经过作者同意. ----------------------- 在谈到特性的使用场景时,还有一个绝对离不开的就是 单元测试 按 ...
- (译)TDD(测试驱动开发)的5个步骤
原文:5 steps of test-driven development https://developer.ibm.com/articles/5-steps-of-test-driven-deve ...
- 测试驱动开发与Python
最近在看一本书<Test-Driven Development with Python>,里面非常详细的介绍了如何一步一步通过测试驱动开发(TDD)的方式开发Web项目.刚好这本书中使用了 ...
随机推荐
- GDAL编译
使用cmd命令行编译 1.首先在“开始菜单\所有程序\Microsoft Visual Studio 2008\Visual Studio Tools\ Visual Studio 2008命令提示” ...
- C++ size_t 和size_type的区别
为了使自己的程序有很好的移植性,c++程序员应该尽量使用size_t和size_type而不是int, unsigned size_t是全局定义的类型:size_type是STL类中定义的类型属性,用 ...
- javaWeb中URLEncoder.encode空格问题
近期开发一个在线坐席的功能.发现推送的消息中空格变成了+ .查询发现URLEncoder.encode的问题.曾经用的时候也没注意过,解决的方法网上是对URLEncoder.encode的之后的字符串 ...
- google C++编程风格指南之头文件的包括顺序
google C++编程风格对头文件的包括顺序作出例如以下指示: (1)为了加强可读性和避免隐含依赖,应使用以下的顺序:C标准库.C++标准库.其他库的头文件.你自己project的头文件.只是这里最 ...
- lua 中pairs 和 ipairs差别
ipairs 和pairs在lua中都是遍历tbale的函数可是两者有差别 1.pairs遍历table中的全部的key-vale 而ipairs会依据key的数值从1開始加1递增遍历相应的table ...
- gcc/g++ 命令的经常使用选项
gcc/g++ 命令的经常使用选项格式(选项 解释) -o FILE 指定输出文件名称.在编译为目标代码时,这一选项不是必须的.假设FILE没有指定,缺省文件名称是a.out. -c 仅仅编译生成目标 ...
- Maven构建真正的J2EE项目
今天同事问起我眼下用Maven构建的多模块项目架构和曾经用Eclipse创建的Web项目的问题.以下将讲一下使用maven搭建多模块的J2ee项目,以及採用这样的方式搭建项目对日后项目的水平拆分和垂直 ...
- 在dotnet core web api中支持CORS(跨域访问)
最近在写的Office add-in开发系列中,其中有一个比较共性的问题就是在add-in的客户端脚本中访问远程服务时,要特别注意跨域访问的问题. 关于CORS的一些基本知识,请参考维基百科的说明:h ...
- WPF中展示HTML
业务需求:将具有表格信息的HTML片段在WPF中展示出来,并像网页端一样,可以进行input的填写,checkbox选择,最后以HTML的形式完成保存. 天真的以为直接引入WPF中的WebBrowse ...
- 十三、 Spring Boot 启动加载数据 CommandLineRunner
实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求. 为了解决这样的问题,spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来 ...