目录
1. 概念理解——TDD
1.1 什么是TDD?
TDD 是 Test-Driven Development 的首字母缩写,其中文为 “测试驱动开发”,在百度百科中,TDD 的定义如下:
TDD 是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD 是 XP(Extreme Programming)的核心实践。它的主要推动者是 Kent Beck。
1.2 TDD 编程方式
在传统的编程方式中,程序员在需求分析结束后开始编码,直到编写完成之后才开始业务逻辑的测试,这使得程序的调试变的困难,而且通过这种方式编写的程序通常逻辑不够清晰,可读性较差,使得程序的修改和维护变得十分困哪。为了消除传统编程方式带来的弊端,TDD 的编程方式与传统方式有很大不同:
- 先分解任务,将大问题分解为若干个小问题;
- 举例,用实例化需求,澄清需求细节;
- 写测试,只关注需求,程序的输入输出,不关心中间过程;
- 写实现,不考虑别的需求,用最简单的方式满足当前这个小需求;
- 重构,使先前编写的代码变得整洁规范;
- 执行测试,若出现问题则补充测试用例,并修正代码;
- 代码编写完成。
1.3 TDD 的优点
- 我们每次只需实现一个子任务,降低了开发者的思维负担;
- 每次实现一个子任务都需进行单元测试,可以及时修正错误;
- 提前编写测试可以帮助我们明确需求及其细节,避免需求理解的偏差等问题;
- 覆盖完全的单元测试使得优化代码或修改业务逻辑时变得安全;
1.4 TDD 的流程
- 写一个测试用例;
- 尝试运行测试(必然失败);
- 先用最少的代码通过编译,执行测试;
- 把代码补充完整,使得它能通过测试;
- 代码重构,提高代码质量;
- 编写、执行基准测试。
1.5 相关词汇理解
- 重构:改善代码的内部结构,提升代码的质量和性能;
- 测试:功能测试,用于测试程序的逻辑是否正确;
- 基准测试:性能测试,用于测试函数性能。
2. “迭代”章节练习
2.1 修改测试代码,以指定字符重复的次数,然后修复代码
修改测试代码如下:
package iteration
import (
"testing"
"os"
"strconv"
)
func TestRepeat(t *testing.T) {
repeatCount,_ := strconv.Atoi(os.Args[len(os.Args)-1])
repeated := Repeat("a", repeatCount,_ := strconv.Atoi(os.Args[len(os.Args)-1]))
var expected string
for i:=0; i<repeatCount; i++{
expected += "a"
}
if repeated != expected {
t.Errorf("expected '%q' but got '%q'", expected, repeated)
}
}
此时使用测试命令 go test ./iteration -args repeatCount
进行测试,其中参数 repeatCount
指定了字符重复次数。测试结果如下,此时因为尚未修改 repeat
函数,报错信息提示输入参数过多:
之后修复 repeat
函数:
package iteration
func Repeat(character string, repeatCount int) string {
var repeated string
for i := 0; i < repeatCount; i++ {
repeated += character
}
return repeated
}
再次执行测试,能够顺利通过:
2.2 写一个 ExampleRepeat 完善函数文档
在 repeat_test.go
文件中增加以下示例测试函数:
func ExampleRepeat () {
repeated := Repeat("a", 5)
fmt.Println(repeated)
//Output: aaaaa
}
因为在该测试函数中使用了 fmt.Println
函数,因此在测试文件中需要额外导入 fmt
包。
另外需要注意的是,测试函数中的测试语句 //Output: aaaaa
是不可省略的,注释中指定了示例测试的输出,若示例函数的输出结果与注释中的结果不同,则测试失败。
2.3 编写测试函数,测试 string 包中的函数
选择 strings.compare
函数进行测试,需要编写测试文件 strings_test.go
,如下:
package strings
import (
"fmt"
"testing"
)
func TestCompare(t *testing.T) {
got1 := strings.Compare("a", "b")
expected1 := -1
if got1 != expected1 {
t.Errorf("expected '%v' but got '%v'", expected1 , got1)
}
got2 := strings.Compare("a", "a")
expected2 := 0
if got2 != expected2 {
t.Errorf("expected '%v' but got '%v'", expected2, got2)
}
got3 := strings.Compare("b", "a")
expected3 := 1
if got3 != expected3 {
t.Errorf("expected '%v' but got '%v'", expected3, got3)
}
}
func ExampleCompare() {
str := strings.Compare("abc", "abc")
fmt.Println(str)
//Output: 0
}
使用 go test ./strings_test.go
命令进行测试,可以通过:
3. TDD实现快速排序
3.1 编写测试用例 quicksort_test.go
单元测试的编写格式为:
- 文件名必须以 _test.go 结尾;
- 方法名必须是 Test 开头;
- 方法参数必须是 t *testing.T
因为我们需要实现的功能为快速排序,因此我们需要测试一个无序数组在经过 Quicksort 函数处理后是否有序(升序),据此可以编写如下测试:
package sort
import "testing"
func TestQuicksort(t *testing.T){
cases := []struct {
in, want []int
}{
{ []int{5, 3, 2, 1, 4}, []int{1, 2, 3, 4, 5} },
}
for _, c := range cases {
temp := c.in
Quicksort(c.in)
for i := 0; i < len(c.in); i++ {
if c.in[i] != c.want[i] {
t.Errorf("Quicksort(%q) == %q, want %q", temp, c.in, c.want)
break
}
}
}
}
3.2 尝试运行测试
使用 go test ./sort
尝试运行测试,显然,因为我们尚未实现 Quicksort 函数,因此该测试必定是失败的。虽然我们知道该测试不可能成功,但我们还是必须进行此次尝试,使得运行结果使我们所期待的失败(找不到 Quicksort 函数),而不是其他失败(编译错误等),这样当程序出现错误的时候,我们可以很快定位到出错的位置。运行测试的结果如下:
3.3 用最少的代码通过编译,执行测试
为了使测试过程不再出现编译错误,我们需要先定义 Quicksort 函数供测试函数调用:
package sort
func Quicksort(s []int) []int {
return s
}
此时执行测试的结果如下:
3.4 把代码补充完整,使得它能通过测试
package sort
func Quicksort(s []int) {
if len(s) > 0 {
x := s[0]
i := 0
j := len(s) - 1
for i < j {
for ; i < j; j-- {
if s[j] < x {
s[i] = s[j]
i++
break
}
}
for ; i < j; i++ {
if s[i] > x {
s[j] = s[i]
j--
break
}
}
}
s[i] = x
Quicksort(s[:i])
Quicksort(s[i+1:])
}
}
执行测试,如果测试失败,根据失败信息可以快速定位到出错位置进行更正,直到通过测试:
3.5 代码重构,提高代码质量
对过长的快速排序函数进行重构,将其分解为较短的,易理解的,重用性较高的几个函数:
package sort
func AdjustNum(s []int, x int, i int, j int) (int, int) {
for ; i < j; j-- {
if s[j] < x {
s[i] = s[j]
i++
break
}
}
for ; i < j; i++ {
if s[i] > x {
s[j] = s[i]
j--
break
}
}
return i, j
}
func AdjustSlice(s []int) int {
x := s[0]
i := 0
j := len(s) - 1
for i < j {
i, j = AdjustNum(s, x, i, j)
}
s[i] = x
return i
}
func Quicksort(s []int) {
if len(s) > 0 {
i := AdjustSlice(s)
Quicksort(s[:i])
Quicksort(s[i+1:])
}
}
再次执行测试,确保快速排序函数的功能不发生改变:
3.6 编写、执行基准测试
基准测试的编写格式为:
- 文件名必须以 _test.go 结尾;
- 方法名必须是 Test 开头;
- 方法参数必须是 b *testing.B
按照上面的要求编写基准测试(将基准测试与单元测试放在同一测试文件中):
func BenchmarkQuicksort(b *testing.B) {
for i := 0; i < b.N; i++ {
Quicksort([]int{5, 3, 4, 2, 1})
}
}
基准测试的结果为: