永久更新地址:https://my.oschina.net/bysu/blog/1647738
写在前面:若有侵权,请发邮件by.su@qq.com告知。
本文主要是学习《Java测试驱动开发》过程中的记录,除了工具有点不一致之外,其他都是摘抄自书本。
转载者告知:如果本文被转载,但凡涉及到侵权相关事宜,转载者需负责。请知悉!
个人觉得这书相当不错,有兴趣的可以买来看看。
图书的相关链接:http://www.ituring.com.cn/book/1942
书中源码下载:https://download.csdn.net/download/gdzjsubaoya/10286870
开发“遥控军舰”
1.创建项目
书中主要是通过git导入项目到IntelliJ IDEA中,不过由于本人是用eclipse,所以我通过上面的网址下载了源码自本地,在eclipse中创建了一个gradle项目,然后把源码放到里面相关目录(没有试过导入书中的项目会怎样,因为书中用的是IntelliJ IDEA + gradle+TestNG,感兴趣的可以自己试一下)
工具:eclipse+gradle+TestNG,整个项目目录如下:
2.辅助类
假设这个项目最初是由你的一位同事开发的,他是位卓越的程序员和TDD践行者,你深信他编写的测试有极高的代码覆盖率。换言之,你完全可以依赖他已做的工作。然而,这位同事还未完成这个项目就去度假了,余下的工作将由你接手完成。他创建了所有辅助类: Direction、Location、Planet 和Point。你注意到相应的测试类也已编写好,它们的名称与被测试的类相同,但包含后缀Spec (如Directionspec )。使用这个后缀旨在明确这样一点: 它们不仅用于验证代码,还是可执行的规范。
除这些辅助类外,还有另外两个类: Ship (实现) 和shipspec (规范测试),你的大部分时间都将花在完善它们上。你将在ShipSpec中编写测试,再在Sh ip类中编写实现代码( 与本书前面做的完全相同)。
我们知道,测试不仅提供了验证代码的途径,还是可执行的文档。因此从现在开始,我们将测试称为“规范”。
每次编写规范或实现规范的代码后,我们都将运行测试。
项目创建完毕,着手第一个需求。
3.需求1
要移动军舰,需要知道它当前的位置;另外,还需知道军舰面向哪个方向:北、南、东还是西。因此,第一个需求如下:
给定军舰的起始位置(x,y)以及它面向的方向(N、S、E或W)。
处理这个需求前,先看一下可使用的辅助类。Point类存储了坐标x和y,其构造函数如下:
public Point(int x, int y) {
this.x = x;
this.y = y;
}
还有枚举类Directiion,它定义的值如下:
public enum Direction {
NORTH(0, 'N'),
EAST(1, 'E'),
SOUTH(2, 'S'),
WEST(3, 'W'),
NONE(4, 'X');
}
最后,还有Location类,其构造函数将前述两个类的对象作为参数:
public Location(Point point, Direction direction) {
this.point = point;
this.direction = direction;
}
知道这些后,为第一个需求编写测试就非常容易。你应该像前一章那样做。
请尝试自己编写规范,完成后再将其与本书提供的解决方案进行比较。对于实现规范的代码,也这样做:城市自己编写它们,完成后再与我们提供的解决方案进行比较。
1.规范
这个需求的规范如下:
@Test
public class ShipSpec {
public void whenInstantiatedThenLocationIsSet(){
Location location = new Location(new Point(21, 13), Direction.NORTH);
Ship ship = new Ship(location);
assertEquals(ship.getLocation(), location);
}
}
这个规范很简单,我们 只做了这样的检查:传递给构造函数Ship的Location对象是否被存储;能否通过获取函数getLocation访问它。
注解@Test
使用TestNG时,在类级指定注解@Test后,无需再指定应将哪些方法视为测试。在这里,所有的共有方法都被视为TestNG测试。
2.实现
这个规范的实现非常简单,只需将构造函数的参数赋给变量location即可:
public class Ship {
private final Location location;
public Ship(Location location){
this.location = location;
}
public Location getLocation(){
return location;
}
}
3.重构
我们知道,需要为每个规范实例化Ship,因此需要重构规范类,在其中添加一个用@BeforeMethod注解的方法。如下所示:
import org.testng.annotations.*;
import static org.testng.Assert.*;
@Test
public class ShipSpec {
private Ship ship;
private Location location;
@BeforeMethod
public void beforeTest(){
location = new Location(new Point(21, 13), Direction.NORTH);
ship = new Ship(location);
}
public void whenInstantiatedThenLocationIsSet(){
/*Location location = new Location(new Point(21, 13), Direction.NORTH);
Ship ship = new Ship(location);*/
assertEquals(ship.getLocation(), location);
}
}
我们没有引入任何新的行为,而只将部分代码移到了用@BeforeMethod注解的方法中,以免编写后面的规范时重复这些代码。这样,运行每个测试时,都将使用location为参数实例化一个Ship对象。
4.需求2
知道军舰在什么地方后,下面尝试移动它。首先,我们应该让它能够前进和后退。
实现让军舰前进和后退的命令(f和b)。
辅助类Location已包含方法forward和backward,它们实现了这项功能:
......
public boolean forward() {
return move(FORWARD, new Point(100, 100), new ArrayList<>());
}
public boolean forward(Point max) {
return move(FORWARD, max, new ArrayList<>());
}
public boolean forward(Point max, List<Point> obstacles) {
return move(FORWARD, max, obstacles);
}
public boolean backward() {
return move(BACKWARD, new Point(100, 100), new ArrayList<>());
}
public boolean backward(Point max) {
return move(BACKWARD, max, new ArrayList<>());
}
public boolean backward(Point max, List<Point> obstacles) {
return move(BACKWARD, max, obstacles);
}
......
1.规范
在军舰朝北的情况下,如果我们向前移动它,结果将如何呢?其y坐标将减1.如果军舰面向东呢?其x坐标应加1。
面对这样的情况,你的第一反应应该是编写两个类似下面的规范:
未完待续...
未完待续...
未完待续...