`
feikiss
  • 浏览: 97949 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

测试驱动开发的实践

阅读更多
最近在学习测试驱动开发,也买了本“测试驱动开发的艺术”,个人感觉获益匪浅。
TDD中的原则很简单:编码只是为了修复未通过的测试
首先从书中的一个简单的例子开始学习。
大致需求如下:需要开发一个子系统,子系统支持邮件模板功能,使用者只需要点击几下鼠标就能给员工发送个性化的邮件了。那么我们该如何用TDD开发这个系统呢?首先应该分解需求,使其变得更小,更具体。可以把模板子系统可以分解成以下测试:
1. 没有任何变量的模板,渲染前后内容不变。
2. 含有一个变量的模板,渲染后变量应当替换为响应的值。
3. 含有多个变量的模板,渲染后变量应当替换成相应的值。
4. 系统会忽略模板中不存在的变量值。

我们尝试将其转换为测试:
1. 对模板“Hello, ${name}”求值,当name的值为Reader时,结果应当是Hello,Reader.
2. 对模板"${greeting},${name}"求值,两个变量值分别为"Hello""Reader",结果应当是Hello,Reader .
3. 对模板"Hello,${name}" 求值,其中变量中没有相应的值时应当抛出异常。
等等等......
   下面开始我们的第一个TDD开发。现在请打开IDE, Just now - -.
写一个失败的测试:
public class TestTemplate {

	@Test
	public void oneVariable(){
		Template template = new Template("Hello,${name}");
		template.set("name","Reader");
		String expected = "Hello,Reader";
		String actual = template.execute();
		assertEquals(expected, actual);
	}
}

记得在引入包的时候加上
import static org.junit.Assert.*;
否则assertEquals会报语法错误。
这时候,IDE肯定会迫不及待的告诉我们Template类根本不存在,我们可以利用IDE生成相应的code.
这是我们应该会有如下代码清单:
public class Template {

	public Template(String string) {
	}

	public void set(String string, String string2) {
		
	}

	public String execute() {
		return null;
	}

}

好,接下来干嘛呢?当然是运行单元测试了,这时候我们的结果肯定是失败的,因为我们根本就没有去写实现。
下一步呢?工作来了,让测试跑通!
怎么让测试通过呢?记着,我们编码的目的只是为了让测试通过,不用想太多了~不知你的实现方式是什么,试下下面这个实现方法看能否使测试跑通。
public class Template {

	...//和前面一样,此处略

	public String execute() {
		return "Hello,Reader";
	}

}

好,运行前面的测试,这时候我们看到,绿条出现了,说明测试通过。
当然,可以说这是投机取巧,但测试驱动开发的原则就是为了修复失败的测试。显然这种实现方式不够好,因为有硬编码的存在,所以我们需要清理代码。
下面我们加上第二条测试:
@Test
	public void differentTemplate(){
		Template template = new Template("Hi,${name}");
		template.set("name","Reader");
		String expected = "Hi,Reader";
		String actual = template.execute();
		assertEquals(expected, actual);
		
	}

运行失败,显然,是时间修改我们的实现了。也就是说我们必须要用某种方式解析模板了。
继续使用伪实现。
首先我们要保存变量值和模板文件,也要在evaluate方法中用变量值替换模板文本中的变量,实现代码如下:
public class Template {

	private String templateText;
	private String variableValue;
	public Template(String templateText) {
		this.templateText = templateText;
	}

	public void set(String variable, String value) {
		this.variableValue = value;
	}

	public String execute() {
		return this.templateText.replaceAll("\\$\\{name\\}", variableValue);
	}

}

这个你可能感觉是在作*弊,因为我们仍旧有硬编码,就是查找${name}的正则表达式。但这不是作*弊,我们要小步前进,记着,小步前进。
怎么消除硬编码呢?在测试中添加多个变量恐怕是最好的消除方法了吧。
好,工作又来了,消除伪实现,继续添加多变量测试。在测试类中添加如下代码:
@Test
	public void multipleVariables(){
		Template template = new Template("${one},${two},${three}");
		template.set("one","1");
		template.set("two","2");
		template.set("three","3");
		String expected = "1,2,3";
		String actual = template.execute();
		assertEquals(expected, actual);
	}

测试,运行,失败(如果不失败反而不正常了 ;-) )
现在我们可以使用查找替换的方法实现功能,代码清单如下:
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public class Template {

	private Map<String, String> variables; //store the variables.
	private String templateText;
	public Template(String templateText) {
		variables = new HashMap<String, String>();
		this.templateText = templateText;
	}

	public void set(String variable, String value) {
		this.variables.put(variable, value);
	}

	public String execute() {
		String result = templateText;
		for(Entry<String,String> entry:variables.entrySet()){ // iterator the variables.
			String regex = "\\$\\{" + entry.getKey() + "\\}";
			result = result.replaceAll(regex, entry.getValue());
		}
		return result;
	}
	
}

下面运行我们的测试代码,全部绿条,通过。
接下来我们测试一下如果输入模板中不存在的变量会是什么效果,添加测试用例:

@Test
	public void unknownVariableAreIgnored(){
		Template template = new Template("Hi,${name}");
		template.set("name","Reader");
		template.set("do not exist","Hi"); //this variable is not exist in the template.
		String expected = "Hi,Reader";
		String actual = template.execute();
		assertEquals(expected, actual);
	}

我猜这个是能够通过测试的,因为我们的业务代码已经很强大了,运行,的确全部通过运行了。
下面,是时间重构了,因为测试代码和产品代码同等重要,我们看下我们的测试代码清单:
public class TestTemplate {

	@Test
	public void oneVariable(){
		Template template = new Template("Hello,${name}");
		template.set("name","Reader");
		String expected = "Hello,Reader";
		String actual = template.execute();
		assertEquals(expected, actual);
	}
	
	@Test
	public void differentTemplate(){
		Template template = new Template("Hi,${name}");
		template.set("name","Reader");
		String expected = "Hi,Reader";
		String actual = template.execute();
		assertEquals(expected, actual);
		
	}
	
	@Test
	public void multipleVariables(){
		Template template = new Template("${one},${two},${three}");
		template.set("one","1");
		template.set("two","2");
		template.set("three","3");
		String expected = "1,2,3";
		String actual = template.execute();
		assertEquals(expected, actual);
	}
	
	@Test
	public void unknownVariableAreIgnored(){
		Template template = new Template("Hi,${name}");
		template.set("name","Reader");
		template.set("do not exist","Hi"); //this variable is not exist in the template.
		String expected = "Hi,Reader";
		String actual = template.execute();
		assertEquals(expected, actual);
	}
}

我们会发现里面有很多重复冗余的代码等待我们去清理了,首先所有的测试都使用了Template对象,所以我们最好将其提取为成员变量,其次所有的测试方法都把evaluate方法的返回值作为被比较对象进行比较,最好能够消除这种重复。同时我们也应该回头检视我们的测试代码,消除重复的测试,比如第一个,第二个和最后一个就是重复的,另外我们还可以让unknownVariableAreIgnored()使用multipleVariables()中的模板文件。
所以,消除冗余测试并统一风格后的测试代码如下:
public class TestTemplate {

	private Template template;
	@Before
	public void setUp(){
		template = new Template("${one},${two},${three}");
		template.set("one","1");
		template.set("two","2");
		template.set("three","3");
	}
	@Test
	public void multipleVariables(){
		String expected = "1,2,3";
		assertTemplateEvaluatesTo(expected);
	}
	
	@Test
	public void unknownVariableAreIgnored(){
		template.set("do not exist","Hi"); //this variable is not exist in the template.
		String expected = "1,2,3";
		assertTemplateEvaluatesTo(expected);
	}
	
	private void assertTemplateEvaluatesTo(String expected){
		assertEquals(expected,template.evaluate());
	}
}


现在的测试代码看着是不是更加简练了呢?这样测试代码本身更轻快短小,仅仅关注要测试的业务逻辑。
接下来,我们现在该继续写测试,添加新功能了。目前我们的模板引擎已经有了基本的功能,下一步应该考虑添加错误处理功能了。不过步骤依然如此,循环渐进,一步一步来,记着,小步前进。当我们继续完善功能的时候,我们会发现我们的evaluate()方法会越来越臃肿,这时候又到了重构的时候了,但由于我们有单元测试做保证,只要保持我们的绿灯常亮,就可以放心的去重构。
分享到:
评论
1 楼 feikiss 2012-07-09  
随着业务逻辑的深入,重构也会无处不在。

相关推荐

    测试驱动开发实践介绍ppt.ppt

    测试驱动开发实践介绍ppt.ppt 测试驱动开发实践介绍ppt.ppt

    测试驱动开发实践介绍ppt

    测试驱动开发实践介绍ppt 测试驱动开发实践介绍ppt

    测试驱动开发及开发实践.pdf

    测试驱动开发及开发实践.pdf 测试驱动开发及开发实践.pdf

    测试驱动开发实践

    不知道大家有没听过“测试先行的开发”这一说法,作为一种开发实践,在过去进行开发时,一般是先开发用户界面或者是类,然后再在此基础上编写测试。但在TDD中,首先是进行测试用例的编写,然后再进行类或者用户界面...

    C#测试驱动开发

    要使测试驱动开发在软件行业中得以繁荣兴盛,需要一些条件,《C#测试驱动开发》从讨论这些条件开始。软件开发发展到今天,有其历史和特定的条件,理解这些很重要。避免重复过去的错误也很重要。在自己当前的开发实践...

    测试驱动开发介绍及实践.pptx

    TDD测试驱动开发讲稿,配合技术分享视频:https://www.bilibili.com/video/BV1t64y1u7C1

    java测试驱动开发教程+代码实例

    《Java测试驱动开发》介绍如何将各种TDDzui佳实践应用于Java开发,主要内容包括:用Java语言进行TDD会用到的各种工具和框架,所需环境搭建;通过实际应用程序,展示TDD优点及开发中应注意的主要问题;TDD是如何通过...

    测试驱动开发的艺术 epub电子书

    全书内容循序渐进,先侧重基础内容,讨论测试驱动开发和验收,然后进入动手实践部分,逐一讲解如何对各种技术应用TDD,最后介绍基于验收测试驱动的测试先行的方式构建完整的系统。本书面向各个层次的Java程序员。...

    《java测试驱动开发》 源码

    《java测试驱动开发》 源码 本书介绍如何将各种TDD最佳实践应用于Java开发,主要内容包括:用Java语言进行TDD会用到的各种工具和框架,所需环境搭建;通过实际应用程序,展示TDD优点及开发中应注意的主要问题;TDD是...

    测试驱动开发.pdf

    测试驱动开发 中文版 pdf 英文版 chm 清晰

    TDD_Java_Practices:Java 中的测试驱动开发实践

    ${1:TDD_Java_Practices} ==================Java 中的测试驱动开发实践基本 Junit 测试的红色/绿色/重构演示:Mockito 概述和基本概念用于开始模拟的 ICalculator 演示OrderService 了解更多基础知识Mockito 功能...

    测试驱动开发(中文版)

    测试驱动开发,单元测试,工程实践、极限编程、敏捷开发

    测试驱动开发的艺术.azw3

    《测试驱动开发的艺术》...第二部分将测试驱动开发用于具体的实践,重点讲解了TDD的各种技术;第三部分着重介绍了验收测试驱动开发,包括Fit框架、实现验收测试的方法等,最后讲解了引入TDD的各种技巧。此为azw3版本

    敏捷实践之测试驱动开发.pptx

    结合Etest的开发经验以一个简单的示例来机械地介绍测试驱动开发 结合前面介绍的内容讨论测试驱动开发的一些原则要点等

    TDD测试驱动开发

    TDD测试驱动开发,你值得拥有! TDD测试驱动开发,你不会后悔!

    java测试驱动开发 带书签中文完整版

    本书介绍如何将各种TDD最佳实践应用于Java开发,主要内容包括:用Java语言进行TDD会用到的各种工具和框架,所需环境搭建;通过实际应用程序,展示TDD优点及开发中应注意的主要问题;TDD是如何通过模拟内部和外部依赖...

    测试驱动开发 影印版

    《测试驱动开发 影印版》 软件工程是计算机学科中一个十分重要的研究领域。自20世纪60年代以来,人们在这一领域做了大量的工作,逐渐形成了系统的软件开发理论、技术和方法,它们在软件开发实践中发挥了重要作用。...

    C++程序设计实践与技巧:测试驱动开发【试读】

    本书是一本关于设计原则、编程实践、测试驱动开发的指南,旨在帮助C++ 程序员用测试驱动开发方法构建高性能解决方案。全书共11 章,涵盖测试驱动开发的基本工作方式、潜在好处、怎样利用测试驱动开发解决设计缺陷、...

Global site tag (gtag.js) - Google Analytics