测试驱动开发(TDD)
测试驱动开发的基本概念##
- 为什么会出现测试驱动开发
- 当有一个新的任务时,往往第一个念头就是如何去实现它呢?
- 拿到任务就开始编码,一边写,变修改和设计
- 我已经调试了好几遍,应该不会有问题了,好了,先休息一下吧
- 测试是QA的事,我为什么要做啊,我做了,他们做什么?
- 奇怪了,怎么代码跟开发文档有这么大的区别啊?
- 开发部在搞什么啊,怎么代码有这么多的BUG?
- 这段代码想干嘛?
- ……
- 什么是测试驱动
- 测试驱动是一种开发形式:
- 首先要编写测试代码
- 除非存在相关测试,否则不编写任何的产品代码
- 由测试来决定需要写什么样的代码
- 要求维护一套详细的测试集
- 测试驱动是一种开发形式:
- 目标
- 代码整洁可用
- 只有测试失败时,我们才写代码
- 消除重复设计,优化设计结构
- 代码整洁可用
测试驱动开发的流程##

Test-Driven Design(TDD)##
是一种开发风格,它要求程序员做到:
- 在写产品代码之前,先写它的单元测试(Unit Tests)
- 没有单元测试的Class不运行作为产品代码
- 单元测试用例决定了如何写产品代码
- 不断地成功运行所有的单元测试用例
- 不断地完善单元测试用例
Test-Driven Design是把需求分析,设计,质量控制量化的过程!
如何做Test-Driven Design and Development###
在开发一个新功能之前:
- 首先确定“做什么”(不是要如何做!)
- 例如,一个网站的增加用户的功能,我们需要一个方法来增加一个用户:
public void addUser(User user)- 功能包括:增加一个用户(在数据库中插入一条记录);如果已经有相同的用户,应该返回一个用户已存在的消息
- 例如,一个网站的增加用户的功能,我们需要一个方法来增加一个用户:
- 然后为这个功能写单元测试例子(Unit Test)
- 单元测试用例要覆盖这个Method的“做什么”
- 所以我们至少要有两个测试用例:
- Test Case1:测试成功增加一个用户
- Test Case 2:测试增加一个已存在的用户
- 其它边缘情况测试:Test Case 3:传入的User对象为NULL
- 写生成(Production)代码
- 我们清楚地知道这段代码需要做什么,清晰的表明这段代码的Contracts。
- 只需要能实现在Unit Test中的Contracts和能够通过它的Unit Test
- 运行Unit Test
- 如果顺利通过,你已经很好的完成了你的任务
- 如果没通过,修补代码直到能通过Unit Test为止
- 如果出现在Unit Test中没预先设定的结果,在Unit Test中增加一个Test Case,修补代码直到通过所有的Test Case为止。
TDD 和 PSP###
Personal Software Process的development

Test-Driven Design and Development

什么时候写Tests?###
- 如果你要写一个新的功能,请先写它的测试用例
- 如果你要在没有经过测试的代码上写新的功能,请先写目前代码的测试用例
- 如果你要Fix一个Bug,请先为这个Bug写一个测试用例
- 如果你要Refactor没有测试过的代码,请先写一个测试用例
- 如果发现一个边缘例外值,请为它写一个测试用例
xUnit 工具###
- Java Class的测试Framework: JUnit
- Java Swing app 的测试Framework: JFCUnit
- Java Server Side(EJB,Servlet)的测试Framework:Catus
- HTML Page的测试Framework: HTMLUnit、HTTPUnit
- C++的测试Framework: CPPUnit
- .Net APP的测试Framework: .NetUnit
实例##
考虑一个质因数分解的例子:将任何一个大于1的自然数分解成质因子相乘,如4=2*2,24=2*2*2*3等。
第一天###
- 建立Maven工程



创建如下Maven工程:

在pom.xml文件中加入JUnit信息:

- 根据测试驱动要求,写一个TestCase:


写第一个TestCase:
public class FactorTest {
@Test
public void testFactor() {
assertEquals(2, Factor.factor(2));
}
}
创建一个类Factor和静态方法factor:
public class Factor {
public static int factor(int i){
return 2;
}
}
运行第一个TestCase:

测试通过:

于是形成质因数分解的第一个版本。
第二天###
第一个测试用例可以测试2,那么3呢?
新增加一个测试用例:
public class FactorTest {
@Test
public void testFactor() {
assertEquals(2, Factor.factor(2));
assertEquals(3, Factor.factor(3));
}
}
根据测试用例,修改Factor类的代码以通过测试用例:
public class Factor {
public static int factor(int i){
if(i==2) return 2;
if(i==3) return 3;
return 0;
}
}
运行测试用例:

测试通过!
第三天###
在前2个测试用例的基础上,增加4,又会怎样呢?

发现测试4,不能通过?怎么办?开始修改Factor类的代码!
首先希望测试4时,返回的是质因子的集合,修改测试用例:
assertEquals(Arrays.asList(2,2), Factor.factor(4));
对应的,其它测试用例相应发生更改:
public class FactorTest {
@Test
public void testFactor() {
assertEquals(Arrays.asList(2), Factor.factor(2));
assertEquals(Arrays.asList(3), Factor.factor(3));
assertEquals(Arrays.asList(2,2), Factor.factor(4));
}
}
由于返回的是数组集合,因此修改Factor类的factor方法:
public class Factor {
public static List<Integer> factor(int i){
List<Integer> l=new ArrayList<Integer>();
if(i==2) l.add(2);
if(i==3) l.add(3);
return l;
}
}
跑测试,发现测试不通过,据此修改factor方法,以使得4的测试能够通过:
public class Factor {
public static List<Integer> factor(int i){
List<Integer> l=new ArrayList<Integer>();
if(i==2) l.add(2);
if(i==3) l.add(3);
if(i==4){
l.add(2);
l.add(2);
}
return l;
}
}
运行JUnit Test:

发现测试通过,OK!
第四天###
那么5呢?能测试通过吗?

不行!修改Factor类的factor方法:
public class Factor {
public static List<Integer> factor(int i){
List<Integer> l=new ArrayList<Integer>();
if(i==2) l.add(2);
if(i==3) l.add(3);
if(i==4){
l.add(2);
l.add(2);
}
if(i==5) l.add(5);
return l;
}
}
测试通过:

如果再继续这么写下去,你会发现你的代码臃肿不堪,不忍直视!
那么这时候,我们需要对代码进行重构优化!
当你无节操的修改代码以通过测试时,我们一定要在某个点停下来看看,我们是不是可以再优化些什么?
测试驱动容易让人进入一个局部关注点,不太关注思维的系统整体点。
停下脚步,回头看看我们的Factor类的factor方法,发现 i=2,i=3,i=5要做的事情似乎一样。
第一步,可以考虑将代码改成:
public class Factor {
public static List<Integer> factor(int i){
List<Integer> l=new ArrayList<Integer>();
l.add(i);
if(i==4){
l.add(2);
l.add(2);
return l;
}
return l;
}
}
运行测试:

发现4不能通过!发现4不能直接加。
怎么办?调整代码顺序!
public class Factor {
public static List<Integer> factor(int i){
List<Integer> l=new ArrayList<Integer>();
if(i==4){
l.add(2);
l.add(2);
return l;
}
l.add(i);
return l;
}
}
运行JUnit Test:

测试通过!与未重构前的代码相比,看上去漂亮多了!
第五天###
接下来,我们测试6:
public class FactorTest {
@Test
public void testFactor() {
assertEquals(Arrays.asList(2), Factor.factor(2));
assertEquals(Arrays.asList(3), Factor.factor(3));
assertEquals(Arrays.asList(2,2), Factor.factor(4));
assertEquals(Arrays.asList(5), Factor.factor(5));
assertEquals(Arrays.asList(2,3), Factor.factor(6));
}
}
显然,6不能通过!因此修改Factor类的factor方法:
public class Factor {
public static List<Integer> factor(int i){
List<Integer> l=new ArrayList<Integer>();
if(i==4){
l.add(2);
l.add(2);
return l;
}
if(i==6){
l.add(2);
l.add(3);
return l;
}
l.add(i);
return l;
}
}
测试通过!
回头再看,i=4和i=6的代码,发现有一个拆解的过程,都有一个共同的因子:2
因此,考虑对代码进行优化:
public class Factor {
public static List<Integer> factor(int i){
List<Integer> l=new ArrayList<Integer>();
if(i%2==0){
l.add(2);
i=i/2;
}
l.add(i);
return l;
}
}
运行测试:

发现2不能通过,继续修改代码,排除2的情况:
public class Factor {
public static List<Integer> factor(int i){
List<Integer> l=new ArrayList<Integer>();
if(i%2==0 && i>2){
l.add(2);
i=i/2;
}
l.add(i);
return l;
}
}
测试通过

第六天###
在此基础上,我们测试8:
assertEquals(Arrays.asList(2,2,2), Factor.factor(8));
发现测试不能通过!
发现8包含多个2,在Factor类的factor方法中,似乎应该将if语句改成while循环:
public class Factor {
public static List<Integer> factor(int i){
List<Integer> l=new ArrayList<Integer>();
while(i%2==0 && i>2){
l.add(2);
i=i/2;
}
l.add(i);
return l;
}
}
测试通过!

第七天###
测试9:
assertEquals(Arrays.asList(3,3), Factor.factor(9));
测试不通过!
public class Factor {
public static List<Integer> factor(int i){
List<Integer> l=new ArrayList<Integer>();
while(i%2==0 && i>2){
l.add(2);
i=i/2;
}
while(i%3==0 && i>3){
l.add(3);
i=i/3;
}
l.add(i);
return l;
}
}
测试通过!
分别再对9,10,11,12,20进行测试:
assertEquals(Arrays.asList(3,3), Factor.factor(9));
assertEquals(Arrays.asList(2,5), Factor.factor(10));
assertEquals(Arrays.asList(11), Factor.factor(11));
assertEquals(Arrays.asList(2,2,3), Factor.factor(12));
assertEquals(Arrays.asList(2,2,5), Factor.factor(20));
测试通过:

第八天###
测试25:
assertEquals(Arrays.asList(5,5), Factor.factor(25));
测试不能通过!原因很简单,代码里面没有对因子5再做处理!
最直观地:
public class Factor {
public static List<Integer> factor(int i){
List<Integer> l=new ArrayList<Integer>();
while(i%2==0 && i>2){
l.add(2);
i=i/2;
}
while(i%3==0 && i>3){
l.add(3);
i=i/3;
}
while(i%5==0 && i>5){
l.add(5);
i=i/5;
}
l.add(i);
return l;
}
}
测试通过!
这时候,我们需要停下脚步,优化代码,消除重复。
在TestCase的保护下,考虑对代码进行修改:
public class Factor {
public static List<Integer> factor(int i){
List<Integer> l=new ArrayList<Integer>();
int k=2;
while(k<i){
while(i%k==0 && i>k){
l.add(k);
i=i/k;
}
k++;
}
l.add(i);
return l;
}
}
测试通过!
考虑一个复杂的TestCase:
assertEquals(Arrays.asList(2,2,2,3,5,7,7,11), Factor.factor(2*2*2*3*5*7*7*11));
测试通过!

回头看看这段代码是否可接受?
小结##
测试驱动开发:
- 能够保证编写单元测试
- 使程序员坚持编写测试
- 有助于澄清接口和行为的细节
- 可证明、可再现、自动的验证
- 增强改变事物的信心
测试驱动开发(TDD)的更多相关文章
- 测试驱动开发(TDD)的思考
极限编程 敏捷开发是一种思想,极限编程也是一种思想,它与敏捷开发某些目标是一致的.只是实现方式不同.测试驱动开发是极限编程的一部分. 1.极限编程这个思路的来源 Kent Beck先生最早在其极限编程 ...
- 测试驱动开发 TDD
一.详解TDD 1.1.TDD概念 :Test Drived Develop 测试驱动开发是敏捷开发中的一项核心实践和技术,也是一种方法论.TDD的原理是在开发功能代码之前,编写单元测试用例代码,测试 ...
- 测试驱动开发(TDD)及测试框架Mocha.js入门学习
组里马上要转变开发模式,由传统的开发模式(Developer开发,QA测试),转变为尝试TDD(Test-driven development,测试驱动开发)的开发模型.由此将不存在QA的角色,或者仅 ...
- 原创翻译-测试驱动开发(TDD)
测试驱动开发原则 翻译自<<Expert Python Programming>> 测试驱动开发是指首先编写包含所有测试软件特点的测试集,然后再去开发软件.也就是说,在编写软件 ...
- 44 | 测试先行:测试驱动开发(TDD)
- 浅谈测试驱动开发(TDD)
测试驱动开发(TDD)是极限编程的重要特点,它以不断的测试推动代码的开发,既简化了代码,又保证了软件质量.本文从开发人员使用的角度,介绍了 TDD 优势.原理.过程.原则.测试技术.Tips 等方面. ...
- (转)浅谈测试驱动开发(TDD)
测试驱动开发(TDD)是极限编程的重要特点,它以不断的测试推动代码的开发,既简化了代码,又保证了软件质量.本文从开发人员使用的角度,介绍了 TDD 优势.原理.过程.原则.测试技术.Tips 等方面. ...
- 敏捷开发 —— TDD(测试驱动开发)
测试驱动开发 TDD(Test-Driven Development)是敏捷开发的一项核心实践,同时也是一种设计技术和方法. 既然是测试驱动,便是测试,测试用例先行: 首先编写好测试用例,期待值,实际 ...
- 测试驱动开发与Python
最近在看一本书<Test-Driven Development with Python>,里面非常详细的介绍了如何一步一步通过测试驱动开发(TDD)的方式开发Web项目.刚好这本书中使用了 ...
- 基于Python的测试驱动开发实战
近年来测试驱动开发(TDD)受到越来越多的关注.这是一个持续改进的过程,能从一开始就形成规范,帮助提高代码质量.这是切实可行的而非天马行空的. TDD的全过程是非常简单的.借助TDD,代码质量会得到提 ...
随机推荐
- <经验杂谈>C#生成条形码
虽然二维码满天飞,但也不能忘了条形码,本篇介绍可以在C#中使用的1D/2D编码解码器.条形码的应用已经非常普遍,几乎所有超市里面的商品上面都印有条形码: 条形码的标准: 条形码的标准有ENA条形码.U ...
- MySQL之删_delete-truncate
MySQL增删改查之删_delete-truncate 一.DELETE语句 删除数据记录 1.在单表中删除行 语法: DELETE [IGNORE] FROM tbl_name [WHERE whe ...
- Python垃圾回收机制 总结
Python 垃圾回收机制 内存管理 Python中的内存管理机制的层次结构提供了4层,其中最底层则是C运行的malloc和free接口,往上的三层才是由Python实现并且维护的,第一层则是在第0层 ...
- Oracle RAC 11g DG Broker配置和测试
Oracle RAC 11g DG Broker配置和测试 之前在<RHEL6.4 + Oracle 11g DG测试环境快速搭建参考>已经简单说过. 本篇在实验环境中实际配置 环境: R ...
- JavaScript入门(二)
一.JS中的运算符 1.算术运算(单目运算符) + 加.-减.*乘. /除. %取余.++自增运算符 .--自减运算符; >>>+:有两种作用,链接字符串/加法运算符.当+两边全为数 ...
- Windows MDI(Multiple-Document Interface)
Windows多文档窗口编程中,需要注意的以下几点: 1.主窗口与文档窗口之间还有一个Client Window. 2.创建文档窗口.通常认为创建子窗口就用CreateWindow,但是MDI中创建文 ...
- Orleans---持久化
Orleans配置---持久化 这是Orleans系列文章中的一篇.首篇文章在此 Grain理想的生命周期应该如下图所示: 这就如美国电影中的大反派一样,死了再复活,死了再复活.当然如果复活的反派没有 ...
- 学习笔记TF049:TensorFlow 模型存储加载、队列线程、加载数据、自定义操作
生成检查点文件(chekpoint file),扩展名.ckpt,tf.train.Saver对象调用Saver.save()生成.包含权重和其他程序定义变量,不包含图结构.另一程序使用,需要重新创建 ...
- luogu1001 A+B Problem
A+B Problem 题目描述 输入两个整数a,b,输出它们的和(|a|,|b|<=10^9). 注意 1.pascal使用integer会爆掉哦! 2.有负数哦! 3.c/c++的main函 ...
- noip普及组2004 花生采摘
花生采摘 描述 鲁宾逊先生有一只宠物猴,名叫多多.这天,他们两个正沿着乡间小路散步,突然发现路的告示牌上贴着一张小小的纸条:"欢迎免费品尝我种的花生!--熊字". 鲁宾逊先生和多多 ...