# TDD实战（二）“井字游戏” 需求一




保证你会玩“__井字游戏__” 并理解它的规则。

### 需求1
---

先定义边界，以及将棋子放在哪些地方非法。

>  可将棋子放在3×3棋盘上任何没有棋子的地方。

将需求分成三个测试：

* 如果棋子放在超出了X轴边界的地方，就引发 `RuntimeException` 异常。
* 如果棋子放在超出了Y轴边界的地方，就引发 `RuntimeException` 异常。
* 如果棋子放在已经有棋子的地方，就引发 `RuntimeException` 异常。


#### 测试用例 1
---
默认你已经会使用 JUnit 单元测试框架了，根据上面的三个测试，我们先来完成第一个。

```Java
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

接下来，我们要实现功能代码了，以满足测试用例通过。

```Java
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` 测试类中创建将的测试用例。

```Java
……

@Test
public void whenYOutsideBoardThenRuntimeException(){
    exception.expect(RuntimeException.class);
    ticTacToe.play(2,5);
}

```
这条用例用于验证棋盘 y 轴范围抛 `RuntimeException` 异常。

##### 实现功能 2

继续修改 `TicTacToe` 的功能代码。使 测试用例2 运行通过。

```Java
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` 测试类中创建将的测试用例。

```Java
……

@Test
public void whenOccupiedThenRuntimeException(){
    ticTacToe.play(2,1);
    exception.expect(RuntimeException.class);
    ticTacToe.play(2,1);
}
```

如果棋盘上的格子已经被占用，那么不允许再放子上去。

##### 实现功能 3

为了实现测试用例3 ，应该将棋子的位置存储在一个数组中。每当玩家放置新棋子时，都应确认棋子放在未占用的位置，否则引发异常。

```Java
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` 代码已经满足了测试的需求，但是有点令人迷惑。所以需要对现有的代码进行重构。

```Java

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 ，使它们全部运行通过。__




原始封面

![课程图片](https://images.unsplash.com/photo-1542744095-fcf48d80b0fd?w=300)


