什么才是 TDD 的正确打开方式?
文章目录
- 什么才是 TDD 的正确打开方式?
-
- 一、TDD 的定义
- 二、为什么用 TDD
-
- 1. TDD 的优缺点
- 2. TDD vs 传统测试
- 3. TDD 的设计周期
- 4. TDD 的意义
-
- ① 场景驱动
- ② 自动化
- ③ 思维方式
- 三、TDD 使用实例
-
- 1. 编程需求
- 2. 先行测试
-
- ① 测试文件编写
- ② 先行测试的意义
- 3. 最少代码运行测试
- 4. 代码补全,进行测试
- 5. 重构
- 6. 基准测试
- 四、使用 TDD 的注意事项
-
- ① 不做过多的测试设计
- ② 小迈步开发
- ③ 不同时做多件事情
- ④ 切忌教条
- ⑤ 使用 TDD 的时机
- 五、小结
一、TDD 的定义 【golang|什么才是 TDD 的正确打开方式()】
文章图片
- TDD 是测试驱动开发(Test-Driven Development)的英文简称,是敏捷开发中的一项核心实践和技术,也是一种设计方法论。
- TDD 的原理是,在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么样的产品代码。
- 总的来说,TDD 的核心思想就是通过测试来推动整个开发的进行,但并不只是单纯的测试工作,而是要把需求分析,设计,质量控制量化的过程
TDD 的思想是从测试样例入手,先写好测试用例,然后再去写实现,它的优缺点也是源自于此
- 优点:
- TDD 又被称为 “测试先行” ,它可以提高产品质量,开发人员一边写测试用例,一边编写业务代码来推动项目,在开发过程中随时可以拿出质量有保证的产品,并不会出现很多 bug
- 可以提前了解清楚需求,因为写测试用例的前提是弄懂了需求
- 有着快速的反馈,有丰富的测试用例来覆盖业务代码,一旦代码出错,就可以及时发现问题并改正
- 缺点:
- 因为需要对代码进行测试,要写好测试代码,所以无形中增大了开发者要编写的代码量。
- 验证不了算法怎么实现,只能判断算法本身的执行结果和性能是否符合要求
2. TDD vs 传统测试
TDD | 传统测试 |
---|---|
集中在验证测试是否正常工作的生产代码上 | 更多关注的是测试用例设计 |
测试每一行代码,实现100%覆盖测试 | 只测试部分代码,可能遗漏一些测试的case |
要求开发者应该 “有目的地测试”。要知道为什么要测试,在测试什么,需要测试什么级别 | 通常不会考虑可测试的要素,导致代码难以测试 |
确保开发出来的产品实际上满足它的需求,有助于建立开发者对产品质量的信心 | 通常是开发完产品才测试,可能会有不少 bug |
本质上 TDD 更强调创建生产代码而不是测试设计 | 专注于测试设计 |
文章图片
3. TDD 的设计周期
- 写一个新的测试用例
- 运行新加的测试用例,看到它失败(还没写功能代码)
- 编写业务代码,对开发代码做很小的修改,目的就是让新加的测试通过
- 运行所有的测试用例,然后看到所有测试都通过了
- 移掉重复的代码,对代码进行重构
文章图片
① 场景驱动
- 为了能够实现”先写测试程序,然后编码实现其功能“,开发者必须提前考虑代码要支持哪些场景。需要支持的场景,代码必须实现,不需要支持的场景,代码没必要过度设计。
- TDD(测试驱动开发)直观的解释了,什么样的设计才叫 ”刚刚好“
- 被自动化测试用例覆盖的代码,其修改方式是受保护的,不恰当的修改,会导致测试不过,更早被发现。
- 写出能自动化运行的测试用例,会降低测试成本,在修改代码时也能得到更快的反馈,以更短的时间确定 “自己开发的代码是正确的”
- 长期使用 TDD 实践能够改变一个人的编程习惯和思维方式。
- 由于 TDD 追求的是在编写任何实现代码之前,先编写测试用例。当编写代码的人脑里想的是「我要什么」,而不是具体细节的「我要怎么做」的时候,TDD 的目的也就达到了。
- TDD 是一个不断调整 Code 的过程,目的是执行通过事先编写的 Test。
golang
编写快速排序 QuickSort
,来具体展示 TDD 的主要流程1. 编程需求
- 设计一个
QuickSort
函数,实现快速排序算法。 - 具体要求:给定一个整数序列,通过调用
QuickSort
函数能将其排序为数值从小到大
① 测试文件编写
- 在没有写要实现的算法前,先写好测试的文件
- 测试文件要以
XXX_test.go
的格式命名,XXX 是被测试文件的名字。 - 在编写测试函数时,要注意首字母需要大写,且为
TestXXX
的格式,函数的参数固定为(t *testing.T)
,具体代码如下所示:
package quicksortimport "testing"func TestQuickSort(t *testing.T) {
arr1 := []int{2, 4, 5, 8, 6, 3, 1, 7}
arr2 := []int{2, 4, 5, 8, 6, 3, 1, 7} res := QuickSort(arr1, 0, 7)
expect := []int{1, 2, 3, 4, 5, 6, 7, 8} for i := 0;
i < 8;
i++ {
if res[i] != expect[i] {
t.Errorf("\n result %v\n but expect %v\n given %v", res,expect,arr2)
break
}
}
}
- Errorf 函数只有当测试失败时才会打印其中的内容,如果成功通过测试会显示 PASS
- 尝试运行测试,在终端显示失败
文章图片
- 思考你应该如何验证你的程序,帮助你写出一种比较容易验证的代码
- 防止测试本身就是错误的,有可能写出来的测试没有任何意义,无法帮助代码进行重构
- 为了检测测试代码的编写是否正确,需要先写一个简要的被测试的文件。
- 先不管任何设计,只要让测试过了就行
- 在同一个工作目录下的 QuickSort.go 中,实现代码如下
func QuickSort(arr [] int,low,high int) ([]int) {
return arr
}
- 在终端运行 go test 后得到以下结果
文章图片
- 如果测试文件编写没有问题,就可以补全被测试文件的代码,在命令行中输入 go test 命令即可进行测试
- 我们先使用冒泡排序的方式让其通过排序降序测试
func QuickSort(arr [] int,low,high int) ([]int) {
for i := low;
i <= high;
i++{
for j := i+1;
j <= high;
j++{
if arr[i] > arr[j] {
arr[i], arr[j] = arr[j], arr[i]
}
}
}
return arr
}
- 在命令行进行测试时,在
go test
后加上不同的参数,会测试不同的内容,具体请参考 Go test 命令行参数
- 快速排序是分而治之思想在排序算法上的应用。本质上说,快速排序应该算是在冒泡排序基础上的递归分治法
- 按照快速排序算法的原理来实现 QuickSort
func partition(arr []int, low, high int) int {
pivot := arr[low]
for low < high {
for low < high && pivot <= arr[high]{
high--
}
arr[low] = arr[high]
for low < high && pivot >= arr[low]{
low++
}
arr[high] = arr[low]
}
arr[low] = pivot
return low
}func QuickSort(arr [] int,low,high int) ([]int){
if high > low {
pivot := partition(arr, low, high)
QuickSort(arr, low, pivot-1)
QuickSort(arr, pivot+1, high)
}
return arr
}
- 测试 go test,提示正确,通过 PASS
文章图片
- 基准测试,通常指测试运行代码需要多少的时间
- testing.B 可使你访问隐性命名(cryptically named)b.N,b.N 代表使程序运行多少次
- 基准测试运行时,代码会运行 b.N 次,并测量需要多长时间。
- 代码运行的次数不会对你产生影响,测试框架会选择一个它所认为的最佳值,以便让你获得更合理的结果。
func BenchmarkQuickSort(b *testing.B) {
arr := []int{2, 4, 5, 8, 6, 3, 1, 7}
for i := 0;
i < b.N;
i++ {
QuickSort(arr, 0, 7)
}
}
- 命令行中执行命令 go test -bench=.
文章图片
- 以上就是 TDD 实现快速排序算法的例子,实际上 TDD 对于排序算法的效果不好,因为排序算法的接口不需要设计。
- TDD是用来测黑盒的,当需求已经具体到算法上「实现一个快排」TDD 就派不上用场了。我们只是把它作为一个介绍 TDD 流程的例子
- 想要在写代码前就作出好的设计是很有难度的。与其在最初去做好的设计,不如不断地重构,使代码进化
- TDD 希望程序的实现能与现阶段的需求完全契合,不做过度的设计。开发者要做的是根据现在的需求写测试,让测试通过,在此基础上把代码设计的更好更合理。
- 在 TDD 过程中频繁地运行测试,验证自己的实现或者对业务的理解是否正确。
- 一直重复
测试失败,测试通过,重构
的循环,步步为营,循序渐进。如果测试出错了,也比较容易回退到之前测试通过的状态。
- 每个步骤只做这个步骤该做的事情。
- 在 test failed 时,不要着急把业务逻辑给实现了
- 在 test passed 里,要做的是让测试尽快通过,而不是一边想着让测试通过,一遍还要重构。结果在测试还没通过的情况下就先重构,结果哪个步骤出错就较难判断。
- 在使用 TDD 的过程中不要用教条代替自己的思考,比如不要过于追求 100% 代码覆盖率,有时反倒会降低代码质量
- 此外,100% 代码覆盖率且通过测试的代码未必就代表程序是正确的:因为有可能测试以及实现都错了;或者可能测试仍不完备,存在实现没有测试到的场景
- TDD 的特质决定了它在应用开发初期是很缓慢的,只有当产品代码到了一定的规模才能显示出比较明显的优势
- 此外,TDD 比较追求完美,要求功能代码都能通过测试,导致开发出来的功能有限
- 众所周知互联网产业是一个争分夺秒的行业,如果企业要求短时间内快速上线一款产品,使用 TDD 可能并不是一个比较好的选择,总的来说需要挑业务
五、小结
- 本文是对 TDD,即测试驱动开发的正确使用进行讲解。
- TDD的基本思想是在开发功能代码前,先开发测试代码,并用测试代码验证功能实现是否满足需求或存在缺陷,在测试代码的驱动下优化功能代码的开发
- TDD 不是银弹,它验证不了算法怎么实现,只能判断算法本身的执行结果和性能是否符合要求。此外它也不可能适合所有的场景,但这不应该成为我们拒绝它的理由。不要轻易否定 TDD,如果要否定,起码要在认真实践过之后。
推荐阅读
- 【第四十期】shopee-golang后台一面面经
- 程序员|【最新面试】2022年软件测试面试题大全(持续更新)附答案
- 软件测试|2022软件测试自学路线分享,附完整资料,自学也能拿高薪哟
- go excelize 批量写入数据到Excel
- 【Go进阶—基础特性】接口
- 拓端tecdat|拓端tecdat|R语言多变量广义正交GARCH(GO-GARCH)模型对股市高维波动率时间序列拟合预测
- Go语言|Go语言编程笔记1(Hello World)
- 【第三十八期】字节跳动后台开发二面凉经
- Go语言|【Golang】做算法题可能会用到的知识