什么是 TDD ?

TDD 有广义和狭义的区分。

广义角度指的是 ATDD(Acceptance Test Driven Development),包括 BDD(Behavior Driven Test Development)和 Consumer-Driven Contracts Development 等。

狭义角度指的是 UTDD(Unit Test Driven Development),也是我们要讲的单元测试驱动开发。

为什么需要 TDD?

传统开发周期:

graph LR
设计--> 编码
编码 -->测试

测试驱动开发周期:

graph LR
测试--> 编码
编码 -->重构

从上图可以看出 TDD 以测试的角度开始避免了一开始就过度设计和避免了功能不符合预期的可能。这对我们写程序来说将会充满信心,因为我们知道代码会按我们预期的方式执行。而因为有测试环节这个安全网存在,后期重构时也会更放心。

实例

需求:

给你一个整数n. 从 1 到 n 按照下面的规则打印每个数:
* 如果这个数被3整除,打印fizz.
* 如果这个数被5整除,打印buzz.
* 如果这个数能同时被3和5整除,打印fizz buzz.
* 其他情况下需要原样输出

这是一个经典的题目 FizzBuzz ,我们首先创建一个测试类:

public class FizzBuzzTests {

    @Test
public void test(){ }
}

我们测试的应该是一个对象,所以我们声明一个 FizzBuzz 对象,并且他有一个 of 方法,这个方法可以接收一个数值型参数返回一个字符串结果:

public class FizzBuzzTests {

    @Test
public void test(){
FizzBuzz fizzBuzz = new FizzBuzz();
String result = fizzBuzz.of(1);
}
}

这个时候 IDE 编译会报错,因为根本没有这个类和方法,我们先来创建类和方法:

public class FizzBuzz {

    public String of(int input) {
return null;
}
}

可以看到 of 方法只是一个空实现,在之后我们将逐步实现这个功能。

需求一:其他情况下需要原样输出

测试方法:

@Test
public void testOriginalOutput(){
FizzBuzz fizzBuzz = new FizzBuzz();
String result = fizzBuzz.of(1);
Assert.assertEquals("1",result);
}

我们先来运行下测试方法,发现控制台报错,错误信息如下:

java.lang.AssertionError:
Expected :1
Actual :null

这不符合我们预期的结果,接下来我们需要实现下 of 方法让它符合我们的预期,

public String of(int input) {
return String.valueOf(input);
}

再次执行测试方法,发现变绿了说明测试成功。

需求二:如果这个数被3整除,打印fizz

测试方法:

@Test
public void testFizzOutput(){
FizzBuzz fizzBuzz = new FizzBuzz();
String result = fizzBuzz.of(3);
Assert.assertEquals("fizz",result);
}

运行测试方法,控制台报错:

org.junit.ComparisonFailure:
Expected :fizz
Actual :3

调整 of 方法:

public String of(int input) {
if (input % 3 == 0) {
return "fizz";
}
return String.valueOf(input);
}

执行测试方法,变绿通过。

需求三:如果这个数被5整除,打印buzz

测试方法:

@Test
public void testBuzzOutput(){
FizzBuzz fizzBuzz = new FizzBuzz();
String result = fizzBuzz.of(5);
Assert.assertEquals("buzz",result);
}

运行测试方法,控制台报错:

org.junit.ComparisonFailure:
Expected :buzz
Actual :5

调整 of 方法:

public String of(int input) {
if (input % 3 == 0) {
return "fizz";
}
if (input % 5 == 0) {
return "buzz";
}
return String.valueOf(input);
}

执行测试方法,变绿通过。

需求四:如果这个数能同时被3和5整除,打印fizz buzz

测试方法:

@Test
public void testFizzBuzzOutput(){
FizzBuzz fizzBuzz = new FizzBuzz();
String result = fizzBuzz.of(15);
Assert.assertEquals("fizz buzz",result);
}

运行测试方法,控制台报错:

org.junit.ComparisonFailure:
Expected :fizz buzz
Actual :fizz

调整 of 方法:

public String of(int input) {
if (input % 3 == 0) {
return "fizz";
}
if (input % 5 == 0) {
return "buzz";
}
if (input % 15 == 0) {
return "fizz buzz";
}
return String.valueOf(input);
}

我们 不加思索 的把之前的逻辑拷贝了一份,然后运行测试方法,结果发现测试不通过:

org.junit.ComparisonFailure:
Expected :fizz buzz
Actual :fizz

仔细研究了下,发现原来是判断的顺序有问题,调整后方法实现如下:

public String of(int input) {
if (input % 15 == 0) {
return "fizz buzz";
}
if (input % 3 == 0) {
return "fizz";
}
if (input % 5 == 0) {
return "buzz";
}
return String.valueOf(input);
}

执行测试方法,变绿通过。

就这样我们一步步的实现了需求并通过了测试,但是我们的工作结束了吗?

注: 在这里我们只是进行了功能性测试,一些异常情况并没有考虑进来,在实际项目中还需要更全面的测试。

从测试驱动开发周期图来看,我们还缺少了一步重构的步骤,那么什么叫重构呢?

重构有两种含义:
重构(名词):对软件内部结构的一种调整,目的是在不改变"软件之可察行为"前提下,提高其可理解性,降低其修改成本.
重构(动词):使用一系列重构准则(手法),在不改变"软件之可察行为"前提下,调整其结构

重构的两种含义都强调了不能调整软件的行为,如果有调整软件的行为的话就不能称之为重构了。

重构 FizzBuzz

我们先来看看 FizzBuzz 类的完整实现:

public class FizzBuzz {

    public String of(int input) {
if (input % 15 == 0) {
return "fizz buzz";
}
if (input % 3 == 0) {
return "fizz";
}
if (input % 5 == 0) {
return "buzz";
}
return String.valueOf(input);
} }

FizzBuzz 的实现很简单,但是我们还是发现了一些语义不清的代码,比如 input % 3 == 0,重构后代码如下:

public class FizzBuzz {

    public String of(int input) {
if (isFizzBuzz(input)) {
return "fizz buzz";
}
if (isRelatedTo(input, 3)) {
return "fizz";
}
if (isRelatedTo(input, 5)) {
return "buzz";
}
return String.valueOf(input);
} private boolean isRelatedTo(int input, int condition) {
return input % condition == 0 || valueOf(input).contains(valueOf(condition));
} private boolean isFizzBuzz(int input) {
return input % 15 == 0;
} }

与之前相比,语义更加清晰了。更多重构的技巧可以查看 Martin Fowler 《重构》。

除了实现类之外,测试类也不要忘记重构,我们先来看下 FizzBuzzTests

public class FizzBuzzTests {

    @Test
public void test(){
FizzBuzz fizzBuzz = new FizzBuzz();
String result = fizzBuzz.of(1);
} @Test
public void testOriginalOutput(){
FizzBuzz fizzBuzz = new FizzBuzz();
String result = fizzBuzz.of(1);
Assert.assertEquals("1",result);
} @Test
public void testFizzOutput(){
FizzBuzz fizzBuzz = new FizzBuzz();
String result = fizzBuzz.of(3);
Assert.assertEquals("fizz",result);
}
@Test
public void testBuzzOutput(){
FizzBuzz fizzBuzz = new FizzBuzz();
String result = fizzBuzz.of(5);
Assert.assertEquals("buzz",result);
}
@Test
public void testFizzBuzzOutput(){
FizzBuzz fizzBuzz = new FizzBuzz();
String result = fizzBuzz.of(15);
Assert.assertEquals("fizz buzz",result);
}
@Test
public void testNegativeNumberInput(){
FizzBuzz fizzBuzz = new FizzBuzz();
String result = fizzBuzz.of(-15);
Assert.assertEquals("fizz buzz",result);
}
}

我们看到

  • test 方法已经没有存在的意义了(在一开始告诉了我们 of 方法的行为)
  • FizzBuzz 对象的创建在每一个方法中都存在

重构后代码:

public class FizzBuzzTests {

    private FizzBuzz fizzBuzz;

    @Before
public void setUp() {
fizzBuzz = new FizzBuzz();
} @Test
public void testOriginalOutput() {
String result = fizzBuzz.of(1);
Assert.assertEquals("1", result);
} @Test
public void testFizzOutput() {
String result = fizzBuzz.of(3);
Assert.assertEquals("fizz", result);
} @Test
public void testBuzzOutput() {
String result = fizzBuzz.of(5);
Assert.assertEquals("buzz", result);
} @Test
public void testFizzBuzzOutput() {
String result = fizzBuzz.of(15);
Assert.assertEquals("fizz buzz", result);
} }

FizzBuzz的代码可以重构的点还有很多,比如常量提取等。但是基于现在来说已经够我们使用了,后期当有调整的时候我们可以再来重构它,记住 Bob大叔提到的童子军军规 让营地比你来时更干净!

初识TDD的更多相关文章

  1. TDD(测试驱动开发)学习一:初识TDD

    首先说一下名词解释,TDD,英文名称Test-Driven Development,中文名称测试驱动开发,简单的断下句“测试/驱动/开发”,简单的理解一下,就是测试驱动着开发,大白话就是说用一边测试一 ...

  2. TDD

    初识TDD 首先说一下名词解释,TDD,英文名称Test-Driven Development,中文名称测试驱动开发,简单的断下句“测试/驱动/开发”,简单的理解一下,就是测试驱动着开发,大白话就是说 ...

  3. TDD(测试驱动开发)培训录

    2014年我一直从事在敏捷实践咨询项目,这也是我颇有收获的一年,特别是咨询项目的每一点改变,不管是代码质量的提高,还是自组织团队的建设,都能让我们感到欣慰.涉及人的问题都是复杂问题,改变人,改变一个组 ...

  4. TDD(测试驱动开发)培训录(转)

    本文转载自:http://www.cnblogs.com/whitewolf/p/4205761.html 最近也在了解TDD,发现这篇文章不错,特此转载一下. TDD(测试驱动开发)培训录 2015 ...

  5. 集DDD,TDD,SOLID,MVVM,DI,EF,Angularjs等于一身的.NET(C#)开源可扩展电商系统–Virto Commerce

    今天一大早来看到园友分享的福利<分享一个前后端分离方案源码-前端angularjs+requirejs+dhtmlx 后端asp.net webapi>,我也来分享一个吧.以下内容由笔者写 ...

  6. TDD(测试驱动开发)

    TDD(测试驱动开发)培训录 2014年我一直从事在敏捷实践咨询项目,这也是我颇有收获的一年,特别是咨询项目的每一点改变,不管是代码质量的提高,还是自组织团队的建设,都能让我们感到欣慰.涉及人的问题都 ...

  7. TDD随想录

    TDD随想录 谨以本文献给TDD的开创者与传播者 本文纯属个人经历,如有雷同纯属巧合 我从不觉得自己是一个好的程序员,甚至可能连合格都谈不上,不过在内心深处我却渴望着在编程这件事上获得成功. 可惜每次 ...

  8. TDD在Unity3D游戏项目开发中的实践

    0x00 前言 关于TDD测试驱动开发的文章已经有很多了,但是在游戏开发尤其是使用Unity3D开发游戏时,却听不到特别多关于TDD的声音.那么本文就来简单聊一聊TDD如何在U3D项目中使用以及如何使 ...

  9. 初步认识TDD

    TDD,测试驱动开发(Test Driven Development)是极限编程中倡导的程序开发方法,以其倡导先写测试程序,然后编码实现其功能得名.本文将对TDD有一个较为系统的认识.    基础属性 ...

随机推荐

  1. 牛客第七场 Minimum Cost Perfect Matching 规律

    题意:1-n-1个数和1-n-1个数两两匹配,每次匹配将两个数的值进行与运算,要求每次匹配与运算的和相加最小,问满足匹配的配对方式 分析:打表前10个数与运算最小的匹配,我们发现,从n-1开始按位取反 ...

  2. 牛客小白赛5 无关(relationship) 容斥原理(计算因子数的模板)

    链接:https://www.nowcoder.com/acm/contest/135/A来源:牛客网 若一个集合A内所有的元素都不是正整数N的因数,则称N与集合A无关.   给出一个含有k个元素的集 ...

  3. CodeForces 55D Beautiful numbers(数位dp+数学)

    题目链接:http://codeforces.com/problemset/problem/55/D 题意:一个美丽数就是可以被它的每一位的数字整除的数. 给定一个区间,求美丽数的个数. 显然这是一道 ...

  4. Spring中常用的23中设计模式

    1.spring 中常用的设计模式有23中  分类  设计模式  创建型 工厂方法模式(FactoryMethod).抽象工厂模式(AbstractFactory).建造者模式(Builder).原型 ...

  5. 【LeetCode】162-寻找峰值

    题目描述 峰值元素是指其值大于左右相邻值的元素. 给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引. 数组可能包含多个峰值,在这种情况下,返回任何一个 ...

  6. SAP压缩excel并发送mail案例

    "SAP压缩附件 REPORT ZMMR0033_DEL7 . TYPES: BEGIN OF bin_file,            name TYPE string,          ...

  7. java生成随机验证码图片

    import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; i ...

  8. 作为IT面试官,我如何考核计算机专业毕业生?作为培训班老师,我又如何提升他们?

    我最近几年一直在做技术面试官,除了面试有一定工作经验的社会人员外,有时还会面试在校实习生和刚毕业的大学生.同时,我也在学校里做过兼职讲师,上些政府补贴课程(这些课程有补贴,学生不用出钱),所以我会在不 ...

  9. 各种浏览器UA值

    UA  User-Agent:用户代理,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本.CPU 类型.浏览器及版本.浏览器引擎.浏览器语言.浏览器插件等. 标准格式为: 浏览器标识 ...

  10. Linux 笔记 - 第十七章 Linux LVM 逻辑卷管理器

    一.前言 在实际生产中,有时会遇到磁盘分区空间不足的情况,这时候就需要对磁盘进行扩容,普通情况下需要新加一块磁盘,重分区.格式化.数据复制.卸载旧分区.挂载新分区等繁琐的步骤,而且有可能造成数据的丢失 ...