《重构 改善既有代码的设计》 读书笔记(十六)

4.2.1 单元测试和功能测试

先讲讲单元测试和功能测试的差异。

单元测试

  • 目的是为了提高程序员的生产率。
  • 它是高度局部化的东西,每个测试类都隶属于单一包
  • 它能够测试其他包的接口,除此之外它将假设其他包一切正常。

功能测试

  • 目的是为了保证软件能够正常运作。
  • 从客户的角度保障质量,并不关心程序员的生产力。
  • 它们应该由一个专门的找BUG团队去负责开发,这个团队应该使用重量级工具和技术来帮助开发良好的功能测试。

当功能测试中出现问题时,要除去它至少做两件事。

1、修改代码。

2、编写单元测试,用以单独检测此BUG。

通常情况下,先编写单元测试有利于锁定BUG的位置。

我们现在所讲的JUnit框架是专门用来编写单元测试的,而功能测试往往以其他工具辅助进行。

重构需要单元测试。

4.3 添加更多测试

测试应该是一种风险驱动的行为,测试的目的是希望找到现在或将来可能出现的错误。

测试的要诀:测试你最担心出错的部分,这样收益最大。

现在我们要继续制作FileReaderTester的测试。

FileReader类什么地方容易出错呢?

4.3.1 末尾读取

其中的一个地方是:当输入流到达文件尾端,read()应该返回-1。

想想看,如果这个出错了,那就意味着我们在读取文件时会一直一直循环下去,或者报错。这不是我们期望的,所以要进行测试。

通过文本编辑器我查看到我的data.txt文件共有148个字符:

Bradman		99.94	52	80	10	6996	334		29
Pollock		60.97	23	41	4	2256	274		7
Headley		60.83	22	40	4	2256	270*	10
Sutcliffe	60.73	54	84	9	4555	194		16

所以我编写了如下代码:

/**
 * 测试读取到最后一个字符后,返回值是否是-1
 * 
 * @author newre
 * @throws IOException
 */
public void testReadEnd() throws IOException {
	int length = 148;
	for (int i = 0; i < length; i++)
		input.read();
	assertEquals(-1, input.read());
}

然后添加到测试套件中:

public static Test suite() {
	TestSuite suite = new TestSuite();
	suite.addTest(new FileReaderTester("testRead"));
	suite.addTest(new FileReaderTester("testReadEnd"));
	return suite;
}

当我们执行时,会分别测试testRead()和testReadEnd()两个测试方法。

事实上,测试套件具有一个特别的构造函数,它的参数是Test子类实例对象,然后它会将这个对象所在类中,所有以“test”开头的方法都当作测试用例:

public static Test suite() {
	TestSuite suite = new TestSuite(FileReaderTester.class);
	return suite;
}

我没有看JUnit4这一部分的写法,可能是以注解@Test代替这种写法,会将这个类中所有具有此注解的方法当作测试用例。

4.3.2 边界查看

测试的技巧:寻找边界条件。对于文件读取而言,边界条件是第一个字符、最后一个字符、读完后结束的读取,所以我们编写了如下代码:

/**
 * 读取测试文件的边界测试
 * 
 * @author newre
 * @throws IOException
 */
public void testReadBoundaries() throws IOException {
	assertEquals("read first char", 'B', input.read());
	int length = 148;
	for (int i = 1; i < length - 2; i++)
		input.read();
	assertEquals("read last char", '6', input.read());
	assertEquals("read at end", -1, input.read());
}

这一部分代码可能会有坑,我修改了一些内容,与书上的不太一样。

4.3.3 空白文件读取

寻找边界条件也包括寻找特殊的、可能导致失败的情况。
空文件是不错的边界条件。

/**
 * 测试空文件的读取
 * 
 * @author newre
 * @throws IOException
 */
public void testEmptyRead() throws IOException {
	File empty = new File("src/main/resources/empty.txt");
	FileOutputStream out = new FileOutputStream(empty);
	out.close();
	FileReader in = new FileReader(empty);
	assertEquals(-1, in.read());
}

此处的FileOutputStream的运用其实不是很好,因为它的作用仅仅是为了在没有empty的时候创建它,防止找不到文件的异常。
使用File的empty.exists(); empty.createNewFile()可能会好一点?

现在,我们等于又有了一个空文件的实例化,这一部分也同样可以放在setUp()中。

由于空文件的建立是一组代码,所以我们可以把那一部分代码提炼出来。

private File empty;

protected void setUp() throws Exception {
	try {
		input = new FileReader("src/main/resources/data.txt");
		empty = newEmptyFile();
	} catch (FileNotFoundException e) {
		throw new RuntimeException("unable to open test file.");
	}
}

private File newEmptyFile() throws IOException {
	File empty = new File("src/main/resources/empty.txt");
	if(!empty.exists())
		empty.createNewFile();
	return empty;
}

/**
 * 测试空文件的读取
 * 
 * @author newre
 * @throws IOException
 */
public void testEmptyRead() throws IOException {
	FileReader in = new FileReader(empty);
	assertEquals(-1, in.read());
	in.close();
}
4.3.4 末尾的末尾读取

如果读取文件末尾之后的位置,会发生什么事?同样应该返回-1,在testReadBoundaries()方法中添加语句:

assertEquals("read pass end", -1, input.read());

测试时,需要积极地去寻找,怎么才能破坏代码,制造BUG。

在写好测试代码后,需要去测试这部分代码是否真的能测试出错误。

4.3.5 尾声

虽然说,“任何测试都不能证明一个程序没有BUG”,但这并不影响我们的测试——测试总能够暴露一些问题,也能提高编程速度。

如果是专门搞测试的,也许需要考虑到所有所有所有的情况,但我们是开发的,没那么多闲工夫把所有情况考虑上,我们只能凭借程序员的直觉,测试一些很容易出错的地方。

当测试类增多,就能够体现出组合模式的好处:测试类符合树状的组织结构,都是同一类型的,具有同意行为的类,所以我们可以专门制作一个主控的测试类:

public class MasterTester extends TestCase {

	public static void main(String[] args) {
		junit.textui.TestRunner.run(suite());
	}

	public static Test suite() {
		TestSuite suite = new TestSuite();
		suite.addTest(new TestSuite(FileReaderTester.class));
		return suite;
	}
}

说了这么多,其实要点只有一个:请构筑一个良好的BUG检测器并经常运行它,这对任何开发工作都将大有裨益,并且是重构的前提。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NewReErWen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值