TDD 介绍

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

-- 百度百科

### 准备工具

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

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

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

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

  • 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 测试驱动开发--“井字游戏” 游戏实战的更多相关文章

  1. 基于Python的测试驱动开发实战

    近年来测试驱动开发(TDD)受到越来越多的关注.这是一个持续改进的过程,能从一开始就形成规范,帮助提高代码质量.这是切实可行的而非天马行空的. TDD的全过程是非常简单的.借助TDD,代码质量会得到提 ...

  2. Android快乐贪吃蛇游戏实战项目开发教程-01项目概述与目录

    一.项目简介 贪吃蛇是一个很经典的游戏,也很适合用来学习.本教程将和大家一起做一个Android版的贪吃蛇游戏. 我已经将做好的案例上传到了应用宝,无病毒.无广告,大家可以放心下载下来把玩一下.应用宝 ...

  3. TDD(测试驱动开发)学习二:创建第一个TDD程序

    本节我们将学习一些测试驱动开发环境的搭建,测试驱动开发概念和流程.所涉及的内容全部会以截图的形式贴出来,如果你也感兴趣,可以一步一步的跟着来做,如果你有任何问题,可以进行留言,我也会很高兴的为你答疑. ...

  4. 测试驱动开发(TDD)

    测试驱动开发的基本概念 为什么会出现测试驱动开发 当有一个新的任务时,往往第一个念头就是如何去实现它呢? 拿到任务就开始编码,一边写,变修改和设计 我已经调试了好几遍,应该不会有问题了,好了,先休息一 ...

  5. 测试驱动开发实践2————从testList开始

    内容指引 1.测试之前 2.改造dto层代码 3.改造dao层代码 4.改造service.impl层的list方法 5.通过testList驱动domain领域类的修改 一.测试之前 在" ...

  6. 【Cocos游戏实战】功夫小子第八课之游戏打包和相关问题说明

    至此,功夫小子系列的Cocos2d-x的实战文章就结束了. 源代码地址: https://github.com/SuooL/KungfuBoy 如须要资源请邮件我 1020935219@qq.com ...

  7. C#-面向对象:争议TDD(测试驱动开发)

    ----------------------- 绝对原创!版权所有,转发需经过作者同意. ----------------------- 在谈到特性的使用场景时,还有一个绝对离不开的就是 单元测试 按 ...

  8. (译)TDD(测试驱动开发)的5个步骤

    原文:5 steps of test-driven development https://developer.ibm.com/articles/5-steps-of-test-driven-deve ...

  9. 测试驱动开发与Python

    最近在看一本书<Test-Driven Development with Python>,里面非常详细的介绍了如何一步一步通过测试驱动开发(TDD)的方式开发Web项目.刚好这本书中使用了 ...

随机推荐

  1. linux个人情况总结

    linux主文件夹打不开 killall Nautilus linux下codeblocks编译文件权限不够,将文件建立在用户的目录下

  2. python re 正则匹配 split sub

    import re 编译: motif='([ST])Q' seq="SQAAAATQ" regrex=re.compile(motif) #编译成正则对象 regrex=re.c ...

  3. 使用questionsModel.values()后不能获取模型中的属性对应的外键属性值的解决方式

    class QuestionsModel(models.Model): author = models.ForeignKey(FrontUserModel,null=True) content = m ...

  4. python爬虫(七)_urllib2:urlerror和httperror

    urllib2的异常错误处理 在我们用urlopen或opener.open方法发出一个请求时,如果urlopen或opener.open不能处理这个response,就产生错误. 这里主要说的是UR ...

  5. Linux安装Nginx以及简单理解

    1.Nginx简单介绍 ①.Nginx:一个高性能的HTTP和反向代理服务器,高并发处理很不错. ②.反向代理:在计算机世界里,由于单个服务器的处理客户端(用户)请求能力有一个极限,当用户的接入请求蜂 ...

  6. 关于xshell:Connection closed by foreign host

    因为原来系统网有时掉,有时卡(同局域网别人没事),重新做了系统. 装了xmanager3,在用xshell连接linux服务器时,提示:服务器发送了一个无效的密钥,然后输出:Connection cl ...

  7. 分布式文件系统及FastDFS

    1.前言 今天来谈谈分布式文件系统,侧重点是文件系统,分布式稍微带一下.然后聊下我用的FastDFS的例子. 2.从小需求开始 我的博客的编辑器用的是markdown,它内嵌了一个文件上传功能,不过后 ...

  8. android checkbox 未选中状态 已选中状态 替换成自己的图片

    效果图: 未选中状态: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA ...

  9. iOS_正則表達式

    iOS 正則表達式 正則表達式,又称正规表示法.常规表示法(英语:Regular Expression,在代码中常简写为regex.regexp或RE).计算机科学的一个概念. 正則表達式使用单个字符 ...

  10. IntelliJ IDEA(五) :Settings(中)

    上篇介绍了Settings中的Appearance & Behavior和Keymap,这篇继续,将介绍Editor,Plugins,Version Control. 一.Editor(编辑) ...