经过 Go 学习 TDD

一、了解 TDD

TDD (Test Driven Development)是敏捷开发中的一项中心实践和技术,也是一种规划办法论。

TDD的中心思想是在开发功能代码之前,先编写单元测验用例代码,测验代码确定需求编写什么产品代码。

TDD 作业流程

  • 先分化使命,别离关注点(后边有演示),用实例化需求,澄清需求细节
  • 只关注需求,程序的输入输出,不关心中心进程,写测验
  • 用最简略的办法满意当时这个小需求即可
  • 重构,进步代码健壮性
  • 再次测验,补重用例,修复 Bug
  • 提交

流程如图所示:

img

TDD 的三条规矩

  1. 除非是为了使一个失利的 unit test 经过,否则不允许编写任何产品代码
  2. 在一个单元测验中,只允许编写刚好能够导致失利的内容(编译过错也算失利)
  3. 只允许编写刚好能够使一个失利的 unit test 经过的产品代码

TDD 的难点

TDD 说起来十分简略,可是在落地的时分许多团队都失利了。TDD 的主要难点在以下方面:

  • 不会合理拆分需求
  • 不会写有效的单元测验
  • 不会写刚好的完成
  • 不会重构

想要最大化利用好 TDD 开发的优势,那么就必须解决好上诉问题。

二、Go 的 Test 入门运用

Go 言语推荐测验文件和源代码文件放在一块,测验文件以 _test.go 结束。比如,当时 package 有 calc.go 一个文件,咱们想测验 calc.go 中的 AddMul 函数,那么应该新建 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