TDD实战(二)“井字游戏” 需求一
虫师 创建于 almost 7 years 之前
最后更新: less than a minute 之前
阅读数: 131
保证你会玩“井字游戏” 并理解它的规则。
需求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 ,使它们全部运行通过。