`
feikiss
  • 浏览: 100032 次
  • 性别: 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

    测试驱动开发(Test-Driven Development,简称TDD)是一种软件开发方法,它的核心思想是先编写测试用例,再编写满足这些测试用例的代码。这种方法强调在编码之前,先明确需求并创建能够验证功能是否正确的测试。TDD...

    测试驱动开发.pdf测试驱动开发.pdf

    总之,测试驱动开发是一种对软件开发流程产生革命性影响的实践方法,它要求开发者在产品开发过程中,持续地进行小规模的测试编写和代码实现。其目的是通过不断循环的测试和编码过程,提升软件的质量,减少缺陷,增强...

    C#测试驱动开发

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

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

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

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

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

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

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

    测试驱动开发Kent Beck

    《测试驱动开发》是Kent Beck的经典著作,这本书深入探讨了测试驱动开发(TDD)这一软件开发实践。TDD是一种编程方法论,它强调在编写实际功能代码之前,先编写测试用例,以此来指导软件设计和编码过程。通过这种...

    测试驱动开发-实例1

    ### 测试驱动开发(TDD)概述 测试驱动开发(Test-Driven Development,简称TDD)是一种软件开发方法论,它要求在编写实际功能代码之前先编写测试用例。这种方法有助于确保代码的质量,并使得代码更加健壮、易于维护...

    python测试驱动开发

    ### Python测试驱动开发 #### 知识点概览 1. **测试驱动开发(TDD)的概念** - 定义与原则 - TDD在软件开发生命周期中的作用 - 实施TDD的好处与挑战 2. **Python与测试驱动开发** - Python作为TDD的理想语言 -...

    测试驱动开发-中文英文.zip

    《测试驱动开发:通过实例》这本书是Kent Beck对这一实践的权威解释,书中通过实例展示了如何在实际项目中应用TDD。书中的英文版和中文版都提供了深入的理解和指导,帮助开发者理解和掌握这种开发模式。 在实际应用...

    测试驱动开发 测试驱动开发 测试驱动开发 测试驱动开发

    在学习和实践TDD时,参考相关书籍如Kent Beck的《测试驱动开发:By Example》(即压缩包中的图片可能源于此书)会有很大帮助。这些图片可能包含了TDD的示例、步骤解释或最佳实践的可视化展示。通过深入理解和实践TDD...

    测试驱动开发教程.pdf

    测试驱动开发教程, 敏捷开发技术实践方法。 对于想要尝试敏捷或者想提升下自己的开发理念的人来说,应该比较有帮助。

    TDD测试驱动开发.pptx

    "TDD测试驱动开发.pptx" TDD 测试驱动开发是一种软件开发方法,它强调通过编写自动化测试来驱动整个开发过程。TDD 是敏捷开发中的一个核心实践和技术,也是一种设计方法论。其主要包括两方面:测试先行和代码重构。...

    测试驱动开发的3项修炼:走出TDD丛林

    书中还可能涵盖了对测试驱动开发的误解和误区的分析,帮助开发者避免在实践TDD时可能出现的错误倾向,比如过分注重测试覆盖率而忽略了测试的质量,或者在不适宜的场景下盲目追求TDD而未能发挥其应有的优势。...

Global site tag (gtag.js) - Google Analytics