感悟测试驱动开发

软件开发方法学的泰斗Kent Beck先生最为推崇"模式、极限编程和测试驱动开发"。在他所创造的极限编程(XP)方法论中,就向大家推荐"测试先行"这一最佳实践,并且还专门撰写了《测试驱动开发》一书,详细说明如何实现。测试驱动开发是极限编程的重要特点,它以不断的测试推动代码的开发,从而实现既简化代码,又保证质量的目标。

  一看到"测试先行"、"测试驱动"这样的名字,就深深地激起了我强烈的好奇心,开始了自己的探索之旅..

  心灵震憾

  一段时间的学习,让我的内心受到了深深的震撼。我们原来的方法居然如此的笨我面对测试先行这一名字时,当时最大的疑问就是"程序都还没有写出来, 测试什么呀!"。后来一想,其实这是一个泥瓦匠都明白的道理,却是自己在画地为牢。我们来看看两个不同泥瓦匠是

  如何工作的吧:

  工匠一:先拉上一根水平线,砌每一块砖时,都与这根水平线进行比较,使得每一块砖都保持水平。

  工匠二:先将一排砖都砌完,然后拉上一根水平线,看看哪些砖有问题,再进行调整。

  你会选择哪种工作方法呢?你一定会骂工匠二笨吧!这样多浪费时间呀! 然而你自己想想,你平时在编写程序的时候又是怎么做的呢?我们就是按工匠二的方法在干活的呀!甚至有时候比工匠二还笨,是整面墙都砌完了,直接进行"集成测试",经常让整面的墙倒塌。看到这里,你还觉得自己的方法高明吗?

  单元测试长期以来被忽视

  每一个程序员都知道应该为自己的代码编写测试程序,但却很少这样做。当人们问为什么的时候,最常听到的回答就是:"我们的开发工作太紧张了"。但这样却导致了一个恶性循环,越是没空编写测试程序,代码的效率与质量越差,花在找Bug、解决Bug的时间也越来越多, 实际效率大大降低。由于效率降低了,因此时间更紧张,压力更大。你想想,为什么不拉上一根水平线呢?难道,我们不能够将后面浪费的时间花在单元测试上,使得我们的程序一开始就更加健壮,更加易于修改吗?抛弃原来的托词吧!

  我们的自动化水平太低了

  有人还会解释说,那是因为拉根水平线很简单,而写测试程序却是十分复杂的。我暂且对这句话本身不置可否。不过也体现了一个新问题,我们需要更加方便、省时的编写测试程序的方法。

  要测试一个类,最简单的方法是直接在调试器中使用表达式观察对象的值与状态,你也可以在程序中加上一些断言、打印中间值等,当然还可以编写专门的测试程序。但是这些方法都有一个很大的局限性,都需要加入人工的判断和分析。

  由此,自动化测试的引入才是解决之道。正是因为如此,提倡"测试驱动开发"的人群,开发出一系列的自动化单元测试框架xUnit,现在已经有针对Java、Pyhton、C++、PHP等各种常用语言的测试框架。这足以搪塞住那些以"编写测试代码太麻烦"为理由的开发人员,让他们没有理由逃避单元测试。

  正如Robert Martin所说:"测试套件运行起来越简单,就会越频繁地运行它们。测试运行越多,就会越快地发现和那些测试的任何背离。如果能够一天多次地运行所有的测试,那么系统的失效时间就决不会超过几分钟"。

  认清测试驱动开发

  测试驱动开发理论最初源于对这些问题的思考:

  1)如果我们能够在编写程序代码之前先进行测试方案的设计,会怎样?

  2)如果我们保证除非没有这个功能将导致测试失败,否则就不在程序中实现该功能,会怎样?

  3)换一个角度,如果当测试时发现必须增加某项功能才能够通过测试时, 我们就增加这一功能,会怎样?

  大师们通过带着这些问题的实践, 发现这的确是一个提高软件代码质量, 使得效率得到保障的一个很好出发点。

  以这样的思路进行软件开发,可以保证程序中的每一项功能都有测试来验证它是正确的,而且每当功能被无意修改时, 测试程序会发现。同时,也使我们获得了一个新的观察点,从对程序调用者有利的视角来观察我们的程序,这使得我们在关心程序功能的本身还能够对接口予以足够感悟测试驱动开发的关注,使得其更容易被调用。另外,这种思路下的代码,将变得更加易于调用,也就必须使其与其它代码保持低耦合性。并且,当你想复用这些模块时,测试代码给出了很好的示例。这一切,使得软件开发工作的质量一下子变得有保障了。

  因此,测试驱动开发的精髓在于: 将测试方案设计工作提前,在编写代码之前先做这一项工作; 从测试的角度来验证设计,推导设计; 同时将测试方案当作行为的准绳,有效地利用其检验代码编写的每一步,实时验证其正确性,实现软件开发过程的"小步快走"。

  实践测试驱动开发

  下面,我就结合一个实际的小例子,来说明如何进行"测试驱动开发"。本实例在J2SE SDK 1.4.2环境下开发,以及配套工具JUnit 3.8.1。

  任务简述

  队列是一种在程序开发中十分常用的数据结构,在此我就以编写一个实现队列功能的类--Queue为例进行说明。该类将实现以下基本运算:

  判断队列是否为空:empty()

  插入队列(即在队列未尾增加一个数据元素):inqueue(x)

  出队列(也就是将队列首数据元素删除):outqueue()

  取列头(也就是读者队列首数据元素的值):gethead()

  清空队列(也就是将队列的所有数据元素全删除): clear()

  查询x在队列中的位置:search(x)

  测试案例分析

  在测试驱动开发实践中,第一步就是考虑测试方案,通过分析该类的功能,我们可以得到以下测试案例:

  1) 队列为空测试

  TC01: 队列新建时,应为空;

  TC02: 清空队列后,应为空;

  TC03: 当出队列操作次数与插入队列操作次数一样时,应为空;

  2) 插入队列测试:

  TC04: 插入队列操作后,新数据元素将插入在队列的未尾;

  TC05: 插入队列操作后,队列将一定不为空;

  3) 出队列测试

  TC06: 出队列操作后,第一个数据元素将被从队列中删除;

  4) 取队头测试

  TC07: 取队头操作将获得队列中的第一个数据元素。

  5) 清空队列测试

  TC08: 清空队列操作后,队列将为空队列;

  注: 此处为了讲解的方便,并未将所有的测试用例都列出,同时也选择了一些十分简单的测试用例。

  第一次迭代

  我们首先编写第一个测试代码,这一测试代码只考虑了测试案例TC01, 也就是保证新建的队列为空:

import junit.framework.*;
//每个使用JUnit编写的测试代码都应该包括本行
public class testQueue extends TestCase
//创建一个测试用例,继承TestCase
{
protected Queue q1;
public static void main (String[] args)
{
junit.textui.TestRunner.run (suite());
//执行测试用例
}
protected void setUp() //环境变量准备
{
q1= new Queue();
}
public static Test suite() //通用格式,指定测试内容
{
return new TestSuite(testQueue.class);
}
public void testEmpty() //以下每个方法就是一个测试
{
assertTrue(q1.empty());
//当队列新建时,应为空-TC01
}
}

 

  安装JUnit十分简单,只需在www.junit.org中下载最新的软件包(ZIP格式), 然后将其解压缩,并且将"JUnit安装目录junit.jar" 以及"JUnit安装目录"都加到系统环境变量CLASSPATH中去即可。

  执行套件可以像上述程序一样在main方法中使用,也可以直接在命令行调用:java junit.textui.TestRunner 测试类名(文本格式)、java junit.awtui.TestRunner 测试类名(图形格式,AWT版)、java junit.swingui.TestRunner测试类名(图形版,Swing版)。

  编译执行(即在命令行执行javac testQueue.java和javatestQueue), 你会发现屏幕上出现提示:

  .E 一个小点说明执行了一个测试用例,E表示其失败

  Time: 0.11 说明执行测试共花费了0.11秒

  There was 1 error: 说明存在一个错误

  1) testEmpty(testQueue)java.lang.NoClassDefFoundError: Queue

  at testQueue.setUp(testQueue.java:13)

  at testQueue.main(testQueue.java:9)

  FAILURES!!!

  Tests run: 1, Failures: 0, Errors: 1

  测试没有通过是肯定的,因为Queue类都还没有写呢?怎么可能通过测试,因此,我们就编写以下代码,以使测试通过:

public class Queue extends java.util.Vector
{
public Queue()
{
super();
}
public boolean empty()
{
return super.isEmpty();
}
}

 

  将这个类编译后,再次执行测试程序,这时将出以下提示:

  . 一个小点说明执行了一个测试用例,没有E表示其成功

  Time: 0.11

  OK (1 test)

  你还可以使用前面我们说到的另两个命令,使测试反馈以图形化的形式体现出来,例如,执行java junit.awtui.TestRunner testQueue, 将出现:

  图1

  第二次迭代

  接下来,我们修改测试程序,加入测试案例TC04、TC05的考虑。

import junit.framework.*;
public class testQueue extends TestCase
{
protected Queue q1,q2;
public static void main (String[] args)
{
junit.textui.TestRunner.run (suite());
}
protected void setUp() {
q1= new Queue();
q2= new Queue();
q2.inqueue("first"); /对队列q2执行插入队列操作
q2.inqueue("second");
}
public static Test suite()
{
return new TestSuite(testQueue.class);
}
public void testEmpty()
{
assertTrue(q1.empty());
//当队列新建时,应为空-TC01
}
public void testInqueue()
{
assertTrue(!(q2.empty()));
//执行了插入队列操作,队列就应不为空-TC05
assertEquals(1,q2.search("second"));
//search方法用于确定元素在队列中的位置
//后插入的数据元素,应在未尾-TC04
//插入两个,第一个在位置0, 第二在位置1
}
}

 

  根据这个测试代码,我们需要在Queue类中添加上inqueue() 和search() 两个方法,如下所示:

public class Queue extends java.util.Vector
{
public Queue()
{
super();
}
public boolean empty()
{
return super.isEmpty();
}
public synchronized void inqueue (Object x)
{
super.addElement(x);
}
public int search(Object x)
{
return super.indexOf(x);
}
}

 

  编译之后,再次执行java junit.awtui.TestRunnertestQueue, 你将再次看到成功的绿色。

  

  图2

  我们仔细看一下这一界面。

  1) 最上面列出了测试代码的类名,右边有一个"Run" 按钮,当你需要再次运行这一测试代码时,只需单击这个按钮。另外,将"Reload classesevery run" 选项打上勾很有用,当你测试未通过(出现红色时), 你可以转身去修改代码,修改完后,只需再按"Run" 按钮就可以再次运行。

  2) 中间区域是一个状态汇报区,红色表示未通过,统计了共运行了多少个测试(也就是在TestCase类中方法的数量)。

  3) 如果测试时出现错误,例如,我们不小心将"assertTrue(!(q2.empty()));" 误写成为"assertTrue(q2.empty());" 就将造成测试失败:

  注:由于第一个测试还是通过的,因此你会看到绿色条一闪。这时,你将会发现JUnit会将错误列出来,并且对应的"Run"按钮也由灰变成了亮,这表示你可以转身修改,完成后单击这个"Run按钮"可以只做刚才失效的这个测试,这将节省大量的时间。

 

  同时,在最下面的窗体里,列出了失效的详细原因。

  

  后面的迭代

  到这里,开发还没有完成,但这种思想却已经通过这样两个短小的实践传递出去了,后面的活大家可以动手试一下。

  另外值得一提的是,这里虽然洋洋洒洒一大篇,实际两次迭代花费了我不到15分钟就完成了。而且,当看到绿条时,心里十分舒畅。

  一些遗憾

  文章到此就告一段落,但却有些许遗憾。

  遗憾之一:这只是一篇文章,没有办法把所有方面都讲得面面俱到,以致于大家可能无法马上上手。

  正是由于这样的原因,本文取名为"感悟", 与大家交流一下体会,希望能够帮助大家更好地接受"测试驱动开发"的理念,并开始着手实践。

  遗憾之二:笔者水平有限,无法解决大家的各种问题。

  让笔者感到欣慰的是,记载着这些答案的《测试驱动开发》、《敏捷软件开发》、《拥抱变化: 解析极限编程》等大作都已悉数摆上了中国的书店。路虽难走,但明师已有。

  实践永远是学习的最好方法,看到笔者的感悟,就开始极限之旅吧,因为那里风光无限,乐趣无限。当你掌握了测试驱动开发的精髓,那你就能够对你自己编写的所有代码充满信心,不再担心它们什么时候在你的后面放一冷箭,从此告别这给你带来无限压力的苦恼。

转载于:https://www.cnblogs.com/junzhongxu/archive/2008/07/16/1243817.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/363628.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

创建一个学生信息表,与页面分离

一、需求分析 做一个jsp页面,动态显示信息表的内容。 1、 做一个实体类:StudentInfo (包含4个字段) 2、 如图模拟生成3条数据,本质上就是new StudentInfo 3个实例,每个实例代表一行记录(后面…

【Unity】材质基础

【Unity】材质基础 a.基本概念 b.Albedo Maps反射率贴图 c.Alpha Maps着色器shader下四大渲染模式 d.Metallic and Smoothness Maps e.Normal Maps法线贴图 f.Height Maps g.Occlusion Maps h.Emission Maps i.Detail Mask & Secondary Maps j.Standard 金属/Standard&…

科学计算机二进制算法,计算机是怎么理解二进制的?

计算机是怎么理解二进制的?计算机的发明最初纯粹是为了计算数字, 让一个机器能够通过输入不同的数字, 进行加减乘除等. 首先要约定好机器能处理的数是什么样的, 即输入是什么样的, 才能去制造计算机. 二进制只是一种尝试, 十进制也有科学家尝试过, 但由于复杂程度较二进制要高…

WPF 实现ScrollViewer的垂直偏移滚动跳转

问题:考虑屏幕大小,一般都是会在表单问卷的页面使用ScrollViewer。问卷中问题漏填漏选时,在提交时校验不过,需要滚动跳转至漏填漏选项。 页面如下: 每个选项使用StackPanel,并对复选框和单选的勾选事件进行…

限制 计算机中 某用户上网 win7,Win7旗舰版怎么限制孩子的上网时间?电脑限制孩子上网时间的方法...

Win7旗舰版怎么限制孩子的上网时间?我们在家里有孩子的时候非常害怕孩子沉迷与网络,不把心思放在学习上。所以会想方设法的限制孩子上网。那么我们怎么使用电脑的自带的功能限制孩子上网呢?下面小编就带着大家一起看一下吧!设置管…

论图计算

自从机械计算开始以来,图形概念就已经存在,并且在纯数学领域已经存在了数十年。 由于数据库的黄金时代,图形在软件工程中变得越来越流行。 图形数据库提供了一种持久化和处理图形数据的方法。 但是,图形数据库并不是存储和分析图形…

html 拼接onmouseout,HTML onmouseout事件用法及代码示例

将鼠标指针移出元素或其子元素时,将发生HTML DOM onmouseout事件。用法:在HTML中:在JavaScript中:object.onmouseout function(){myScript};在JavaScript中,使用addEventListener()方法:object.addEventListener(&quo…

7 selenium 模块

selenium 模块 一.简介 1.Python的一个第三方库,对外提供的接口可以操作浏览器,然后让浏览器完成自动化的操作。 2.自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题 selenium本质是通过驱动浏览器,完全…

针对新手的Java EE7和Maven项目-第3部分-定义ejb服务和jpa实体模块

从前面的部分恢复 第1部分 第2部分 我们在第三部分继续介绍,我们已经有一个父pom,并且已经为我们的war模块定义了pom。 在我们的原始设置中,我们定义了我们的应用程序将包含一个ejb jar形式的服务jar。 这是我们的Enterprise Java Bean&…

计算机本地网络如何共享,本地网络共享怎么实现

本地网络共享可以满足多台电脑同时联网,台式电脑实现网络共享可以使用路由器,笔记本电脑事项网络共享需要使用无线路由器或者无线网卡。那么本地网络共享又是如何实现的呢,下面为大家详细介绍一下。通过路由器实现本地网络共享:第…

Git初始化配置以及配置github

1,配置用户名和邮箱(这里是我github中配置的用户名和邮箱),执行下面命令后,在C:\Users\yaosq盘下会出现一个全局文件.gitconfig. git config --global user.name "这里换上你的用户名" git config --global…

数据知识栈

并发不适合胆小者 我们都知道并发编程很难正确实现。 这就是为什么在执行线程任务之后要进行大量的设计和代码审查会议。 您永远不会将并发问题分配给经验不足的开发人员。 仔细分析问题空间,提出设计,并记录和审查解决方案。 这就是通常处理线程相关任…

Spring Boot:快速启动MVC

我打算一年多以前写一篇关于Spring Boot的文章。 最后,我有时间和灵感。 因此,准备10到15分钟的高质量Spring教程。 我将用Gradle和嵌入式Tomcat演示Spring Boot的基础知识。 我使用Intellij IDEA而不是Eclipse,但是对于那些习惯Eclipse的人来…

布里斯托大学计算机科学专业排名,2021年布里斯托大学世界及专业排名 多个领域位居全英前十!...

它既是红砖大学的成员,也是罗素大学集团成员,在这所学校里共培养出了13位诺贝尔奖得主,这所学校就是布里斯托大学,该校的83%的研究成果都达到了世界领先水平,因此,越来越多的学生去布里斯托大学留学&#x…

Spring启动时的Spring社交示例,或者我如何不再担心和喜欢自动配置

对于Spring Boot 1.1.0.RC1,添加了自动配置和Spring Social的启动程序pom,这意味着我不必为pom添加一百个依赖关系,并且将为我处理许多毫无意义的Spring配置。 让我们来看一个例子。 我将实现一个两页的Web应用程序。 一个将显示给定用户的T…

Djang之cookie和session

一 会话跟踪 我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应。例如你给10086打个电话,你就是客户端,而10086服务人员就是服务器了。从双方接通电话那一刻起&a…

ASP.NET技巧(收集)

1、有没有办法让JavaScript的注释在客户端不可见呢&#xff1f; 答案很简单&#xff0c;就是&#xff1a;JavaScript注释 服务器端注释&#xff01; 行注释写法&#xff1a; //<%-- 这里写行注释 --%> 块注释写法&#xff1a; /*<%-- 这里写注释语句块&a…

Windows堆栈区别[转]

堆和栈的区别 (转贴) 非本人作也!因非常经典,所以收归旗下,与众人阅之!原作者不祥! 堆和栈的区别一、预备知识—程序的内存分配一个由c/C编译的程序占用的内存分为以下几个部分1、栈区&#xff08;stack&#xff09;— 由编译器自动分配释放 &#xff0c;存放函数的参数值&…

Tomcat启用HTTPS协议配置过程

Article1较为简洁&#xff0c;Article2较为详细&#xff0c;测试可行。 Article1 概念简介 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试…

springboot配置idea 热部署

背景&#xff1a; 在开发中&#xff0c;当我们修改代码之后&#xff0c;每次都要重新启动&#xff0c;很是浪费时间&#xff0c;在springboot中就有一种热部署方式&#xff0c;可以实现想要修改不需要每次都重新启动&#xff0c;保存即可生效 用法&#xff1a; 一、maven 添加 …