经过 Go 学习 TDD
一、了解 TDD
TDD (Test Driven Development)是敏捷开发中的一项中心实践和技术,也是一种规划办法论。
TDD的中心思想是在开发功能代码之前,先编写单元测验用例代码,测验代码确定需求编写什么产品代码。
TDD 作业流程
- 先分化使命,别离关注点(后边有演示),用实例化需求,澄清需求细节
- 只关注需求,程序的输入输出,不关心中心进程,写测验
- 用最简略的办法满意当时这个小需求即可
- 重构,进步代码健壮性
- 再次测验,补重用例,修复 Bug
- 提交
流程如图所示:
TDD 的三条规矩
- 除非是为了使一个失利的 unit test 经过,否则不允许编写任何产品代码
- 在一个单元测验中,只允许编写刚好能够导致失利的内容(编译过错也算失利)
- 只允许编写刚好能够使一个失利的 unit test 经过的产品代码
TDD 的难点
TDD 说起来十分简略,可是在落地的时分许多团队都失利了。TDD 的主要难点在以下方面:
- 不会合理拆分需求
- 不会写有效的单元测验
- 不会写刚好的完成
- 不会重构
想要最大化利用好 TDD 开发的优势,那么就必须解决好上诉问题。
二、Go 的 Test 入门运用
Go 言语推荐测验文件和源代码文件放在一块,测验文件以 _test.go
结束。比如,当时 package 有 calc.go
一个文件,咱们想测验 calc.go
中的 Add
和 Mul
函数,那么应该新建 calc_test.go
作为测验文件。
example/
|--calc.go
|--calc_test.go
倘若calc.go
的代码如下:
package main
func Add(a int, b int) int {
return a + b
}
func Mul(a int, b int) int {
return a * b
}
那么测验代码 calc_test.go
能够书写如下代码:
package examples
import "testing"
// 测验用例称号一般命名为 Test 加上待测验的办法名
// 测验用的参数有且只有一个,在这里是 t *testing.T
func TestAdd(t *testing.T) {
if ans := Add(1, 2); ans != 3 {
t.Errorf("1 + 2 expected be 3, but %d got", ans)
}
if ans := Add(-10, -20); ans != -30 {
t.Errorf("-10 + -20 expected be -30, but %d got", ans)
}
}
func TestMul(t *testing.T) {
if ans := Mul(1, 2); ans != 2 {
t.Errorf("1 * 2 expected be 2, but %d got", ans)
}
if ans := Mul(-10, -20); ans != 200 {
t.Errorf("-10 * -20 expected be 200, but %d got", ans)
}
}
然后在命令行履行 go test
:
➜ examples go test
PASS
ok examples 0.100s
Tops:在 go test
后边加-v
能够会显现每个用例的测验成果;加-cover
能够查看覆盖率!
三、TDD 开发示例
使命:计算字符串中各个字母呈现的次数
为了简化使命,咱们规则字符串中只有小写字母
方针输入1:abcdaaf
方针输出1:a=3;b=1;c=1;d=1;f=1;
方针输入2:aabbccdd
方针输出2:a=2;b=2;c=2;d=2;
1. 需求拆分
因为使命需求比较简略,咱们能够不进行拆分。
2. 编写测验代码
根据需求咱们能够编写测验代码:
package examples
import (
"reflect"
"testing"
)
func TestCount(t *testing.T) {
cases := []struct {
input, except string
}{
{"abcdaaf", "a=3;b=1;c=1;d=1;f=1;"},
{"aabbccdd", "a=2;b=2;c=2;d=2;"},
}
for _, c := range cases {
t.Run(c.input, func(t *testing.T) {
if output := Count(c.input); !reflect.DeepEqual(output, c.except) {
t.Fatalf("'%s' expected '%s', but '%s' got",
c.input, c.except, output)
}
})
}
}
3. 运转测验得到失利的成果
因为没有界说 Count 函数,因此此时运转测验会报错,运转测验成果如下:
➜ examples go test
# examples [examples.test]
.\char_count_test.go:18:17: undefined: Count
FAIL examples [build failed]
4. 编写能够编译的完成
严格遵守 TDD 办法的过程与准则,现在只需让代码可编译,这样你就能够查看测验用例能否经过。
在 char_count.go
文件下编写代码如下:
package examples
func Count(input string) string {
return ""
}
5. 运转测验得到失利的成果
在此现已界说了 Count 函数,接下来就能够进一步履行测验代码里边的具体内容,可是运转测验的成果也会过错,这是因为 Count 函数界说的问题。运转测验成果如下:
➜ examples go test
--- FAIL: TestCount (0.00s)
--- FAIL: TestCount/abcdaaf (0.00s)
char_count_test.go:19: 'abcdaaf' expected 'a=3;b=1;c=1;d=1;f=1;', but '' got
--- FAIL: TestCount/aabbccdd (0.00s)
char_count_test.go:19: 'aabbccdd' expected 'a=2;b=2;c=2;d=2;', but '' got
FAIL
exit status 1
FAIL examples 0.101s
6. 编写能够经过测验的完成
package examples
import "fmt"
func Count(input string) string {
count := make([]int, 26)
for _, v := range input {
count[v-96]++
}
output := ""
for i, v := range count {
if v != 0 {
output += fmt.Sprintf("%s=%d;", string(rune(i+96)), v)
}
}
return output
}
7. 运转测验得到成功的成果
➜ examples go test
PASS
ok examples 0.103s
8. 重构
虽然代码现现已过了测验,可是其代码的规范性和简洁性还是存在许多问题,所以需求咱们对代码进行重构。
重构代码要求在不改变代码的逻辑和功能的前提下,尽可能的简化代码。简化的目的有增强代码的可读性、加速代码的履行速度等等。
常见的简化办法便是重用代码(将频繁运用的变量、常量以及函数另外界说出来,这样就能够在各个地方调用此变量、常量、函数即可)。
重构后的代码如下所示:
package examples
import "fmt"
func Count(input string) string {
count := make([]int, 26)
for _, v := range input {
count[v-'a']++
}
output := ""
for i, v := range count {
if v != 0 {
output += fmt.Sprintf("%s=%d;", string(rune(i+'a')), v)
}
}
return output
}
9. 基准测验(benchmarks)
根据TDD周期具体完成“迭代”章节的比如之后,还能够在此基础上编写基准测验。
在 Go 中编写基准测验(benchmarks)是该言语的另一个一级特性,它与在TDD中的编写测验过程非常类似。
基准测验代码如下:
func BenchmarkCount(b *testing.B) {
for i := 0; i < b.N; i++ {
Count("abcdaaf")
}
}
基准测验运转时,代码会运转 b.N 次,并丈量需求多长时间。代码运转的次数不会对你产生影响,测验框架会挑选一个它所认为的最佳值,以便让你取得更合理的成果。
编写完测验代码后,用 go test -bench=.
命令进行测验:
➜ examples go test -bench=.
goos: windows
goarch: amd64
pkg: examples
cpu: Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz
BenchmarkCount-4 799131 1272 ns/op
PASS
ok examples 1.136s