# 从正确的方式开始测试你的Go应用

``````/**
* 谨献给Yoyo
*
* 原文出处：https://www.toptal.com/go/your-introductory-course-to-testing-with-go
* @author dogstar.huang <chanzonghuang@gmail.com> 2016-08-11
*/
``````

## Go中的表格测试

`avg.go`

``````func Avg(nos ...int) int {
sum := 0
for _, n := range nos {
sum += n
}
if sum == 0 {
return 0
}
return sum / len(nos)
}
``````

`avg_test.go`

``````func TestAvg(t *testing.T) {
for _, tt := range []struct {
Nos    []int
Result int
}{
{Nos: []int{2, 4}, Result: 3},
{Nos: []int{1, 2, 5}, Result: 2},
{Nos: []int{1}, Result: 1},
{Nos: []int{}, Result: 0},
{Nos: []int{2, -2}, Result: 0},
} {
if avg := Average(tt.Nos...); avg != tt.Result {
t.Fatalf("expected average of %v to be %d, got %d\n", tt.Nos, tt.Result, avg)
}
}
}
``````

• 首先，在测试函数名称的“Test”前缀。这是必需的，以便工具把它作为一种有效的测试检测出来。
• 测试函数名称的后半部分通常是待测试函数或者方法的名称，在这里是`Avg`
• 我们还需要传入称为`testing.T`的测试结构，其允许控制测试流。有关此API的更多详细信息，请访问此文档

## Golang接口模拟

Go语言所提供的最伟大和最强大的功能称为接口。除了在进行程序架构设计时获得接口的强大功能和灵活性外，接口也为我们提供了令人惊讶的机会来解耦组件以及在交汇点全面测试他们。

`readn.go`

``````// readN reads at most n bytes from r and returns them as a string.
buf := make([]byte, n)
if err != nil {
return "", err
}
return string(buf[:m]), nil
}
``````

• `readN`被一个大小为n的缓冲调用
• `readN`返回一个错误如果抛出异常

``````type Reader interface {
Read(p []byte) (n int, err error)
}
``````

``````type ReaderMock struct {
}

}
``````

``````var _ io.Reader = (*MockReader)(nil)
``````

``````func TestReadN_bufSize(t *testing.T) {
total := 0
mr := &MockReader{func(b []byte) (int, error) {
total = len(b)
return 0, nil
}}
if total != 5 {
t.Fatalf("expected 5, got %d", total)
}
}
``````

``````func TestReadN_error(t *testing.T) {
expect := errors.New("some non-nil error")
mr := &MockReader{func(b []byte) (int, error) {
return 0, expect
}}
if err != expect {
t.Fatal("expected error")
}
}
``````

## Golang方法模拟

``````func printSize(n int) {
if n < 10 {
log.Println("SMALL")
} else {
log.Println("LARGE")
}
}
``````

``````var show = func(v ...interface{}) {
log.Println(v...)
}
``````

``````func printSize(n int) {
if n < 10 {
show("SMALL")
} else {
show("LARGE")
}
}
``````

``````func TestPrintSize(t *testing.T) {
var got string
oldShow := show
show = func(v ...interface{}) {
if len(v) != 1 {
t.Fatalf("expected show to be called with 1 param, got %d", len(v))
}
var ok bool
got, ok = v[0].(string)
if !ok {
t.Fatal("expected show to be called with a string")
}
}

for _, tt := range []struct{
N int
Out string
}{
{2, "SMALL"},
{3, "SMALL"},
{9, "SMALL"},
{10, "LARGE"},
{11, "LARGE"},
{100, "LARGE"},
} {
got = ""
printSize(tt.N)
if got != tt.Out {
t.Fatalf("on %d, expected '%s', got '%s'\n", tt.N, tt.Out, got)
}
}

// careful though, we must not forget to restore it to its original value
// before finishing the test, or it might interfere with other tests in our
// suite, giving us unexpected and hard to trace behavior.
show = oldShow
}
``````

## Golang模板渲染测试

``````<html>
<body>
Welcome <span class="name">Frank</span>!
</h1>
</body>
</html>
``````

GoQuery使用类似jQuery的API查询HTML结构，是用于测试程序标签输出的有效性是必不可少的。

`welcome__test.go`

``````func TestWelcome_name(t *testing.T) {
resp, err := http.Get("http://localhost:3999/welcome?name=Frank")
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
doc, err := goquery.NewDocumentFromResponse(resp)
if err != nil {
t.Fatal(err)
}
if v := doc.Find("h1.header-name span.name").Text(); v != "Frank" {
t.Fatalf("expected markup to contain 'Frank', got '%s'", v)
}
}
``````

## 测试JSON接口

Golang经常用来写某种API，所以最后但并非最不重要的，让我们来看一些测试JSON API高级的方式。

``````{"Salutation": "Hello Frank!"}
``````

`welcome__test.go`

``````func TestWelcome_name_JSON(t *testing.T) {
resp, err := http.Get("http://localhost:3999/welcome.json?name=Frank")
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 200 {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
var dst struct{ Salutation string }
if err := json.NewDecoder(resp.Body).Decode(&dst); err != nil {
t.Fatal(err)
}
if dst.Salutation != "Hello Frank!" {
t.Fatalf("expected 'Hello Frank!', got '%s'", dst.Salutation)
}
}
``````

## Setup 和 Teardown

Golang的测试是容易的，但有一个问题，在这之前的JSON测试和模板渲染测试。他们都假设服务器正在运行，而这创建了一个不可靠的依赖。并且，需要一个“活”的服务器不是一个很好的主意。

``````func setup() *httptest.Server {
return httptest.NewServer(app.Handler())
}

func teardown(s *httptest.Server) {
s.Close()
}

func TestWelcome_name(t *testing.T) {
srv := setup()

url := fmt.Sprintf("%s/welcome.json?name=Frank", srv.URL)
resp, err := http.Get(url)
// verify errors & run assertions as usual

teardown(srv)
}
``````

## 结论

`Golang`的测试是一个很好的机会，它假定了你程序的外部视野和承担访问者的脚步，或是在大多数情况下，即你的API的用户。它提供了巨大的机会，以确保你提供了良好的代码和优质的体验。

------------------------

0 回复

• 请尽量让自己的回复能够对别人有帮助
• 支持 Markdown 格式, **粗体**、~~删除线~~、``单行代码``
• 支持 @ 本站用户；支持表情（输入 : 提示），见 Emoji cheat sheet
• 图片支持拖拽、截图粘贴等方式上传

# 从正确的方式开始测试你的Go应用

``````/**
* 谨献给Yoyo
*
* 原文出处：https://www.toptal.com/go/your-introductory-course-to-testing-with-go
* @author dogstar.huang <chanzonghuang@gmail.com> 2016-08-11
*/
``````

## Go中的表格测试

`avg.go`

``````func Avg(nos ...int) int {
sum := 0
for _, n := range nos {
sum += n
}
if sum == 0 {
return 0
}
return sum / len(nos)
}
``````

`avg_test.go`

``````func TestAvg(t *testing.T) {
for _, tt := range []struct {
Nos    []int
Result int
}{
{Nos: []int{2, 4}, Result: 3},
{Nos: []int{1, 2, 5}, Result: 2},
{Nos: []int{1}, Result: 1},
{Nos: []int{}, Result: 0},
{Nos: []int{2, -2}, Result: 0},
} {
if avg := Average(tt.Nos...); avg != tt.Result {
t.Fatalf("expected average of %v to be %d, got %d\n", tt.Nos, tt.Result, avg)
}
}
}
``````

• 首先，在测试函数名称的“Test”前缀。这是必需的，以便工具把它作为一种有效的测试检测出来。
• 测试函数名称的后半部分通常是待测试函数或者方法的名称，在这里是`Avg`
• 我们还需要传入称为`testing.T`的测试结构，其允许控制测试流。有关此API的更多详细信息，请访问此文档

## Golang接口模拟

Go语言所提供的最伟大和最强大的功能称为接口。除了在进行程序架构设计时获得接口的强大功能和灵活性外，接口也为我们提供了令人惊讶的机会来解耦组件以及在交汇点全面测试他们。

`readn.go`

``````// readN reads at most n bytes from r and returns them as a string.
buf := make([]byte, n)
if err != nil {
return "", err
}
return string(buf[:m]), nil
}
``````

• `readN`被一个大小为n的缓冲调用
• `readN`返回一个错误如果抛出异常

``````type Reader interface {
Read(p []byte) (n int, err error)
}
``````

``````type ReaderMock struct {
}

}
``````

``````var _ io.Reader = (*MockReader)(nil)
``````

``````func TestReadN_bufSize(t *testing.T) {
total := 0
mr := &MockReader{func(b []byte) (int, error) {
total = len(b)
return 0, nil
}}
if total != 5 {
t.Fatalf("expected 5, got %d", total)
}
}
``````

``````func TestReadN_error(t *testing.T) {
expect := errors.New("some non-nil error")
mr := &MockReader{func(b []byte) (int, error) {
return 0, expect
}}
if err != expect {
t.Fatal("expected error")
}
}
``````

## Golang方法模拟

``````func printSize(n int) {
if n < 10 {
log.Println("SMALL")
} else {
log.Println("LARGE")
}
}
``````

``````var show = func(v ...interface{}) {
log.Println(v...)
}
``````

``````func printSize(n int) {
if n < 10 {
show("SMALL")
} else {
show("LARGE")
}
}
``````

``````func TestPrintSize(t *testing.T) {
var got string
oldShow := show
show = func(v ...interface{}) {
if len(v) != 1 {
t.Fatalf("expected show to be called with 1 param, got %d", len(v))
}
var ok bool
got, ok = v[0].(string)
if !ok {
t.Fatal("expected show to be called with a string")
}
}

for _, tt := range []struct{
N int
Out string
}{
{2, "SMALL"},
{3, "SMALL"},
{9, "SMALL"},
{10, "LARGE"},
{11, "LARGE"},
{100, "LARGE"},
} {
got = ""
printSize(tt.N)
if got != tt.Out {
t.Fatalf("on %d, expected '%s', got '%s'\n", tt.N, tt.Out, got)
}
}

// careful though, we must not forget to restore it to its original value
// before finishing the test, or it might interfere with other tests in our
// suite, giving us unexpected and hard to trace behavior.
show = oldShow
}
``````

## Golang模板渲染测试

``````<html>
<body>
Welcome <span class="name">Frank</span>!
</h1>
</body>
</html>
``````

GoQuery使用类似jQuery的API查询HTML结构，是用于测试程序标签输出的有效性是必不可少的。

`welcome__test.go`

``````func TestWelcome_name(t *testing.T) {
resp, err := http.Get("http://localhost:3999/welcome?name=Frank")
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
doc, err := goquery.NewDocumentFromResponse(resp)
if err != nil {
t.Fatal(err)
}
if v := doc.Find("h1.header-name span.name").Text(); v != "Frank" {
t.Fatalf("expected markup to contain 'Frank', got '%s'", v)
}
}
``````

## 测试JSON接口

Golang经常用来写某种API，所以最后但并非最不重要的，让我们来看一些测试JSON API高级的方式。

``````{"Salutation": "Hello Frank!"}
``````

`welcome__test.go`

``````func TestWelcome_name_JSON(t *testing.T) {
resp, err := http.Get("http://localhost:3999/welcome.json?name=Frank")
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 200 {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
var dst struct{ Salutation string }
if err := json.NewDecoder(resp.Body).Decode(&dst); err != nil {
t.Fatal(err)
}
if dst.Salutation != "Hello Frank!" {
t.Fatalf("expected 'Hello Frank!', got '%s'", dst.Salutation)
}
}
``````

## Setup 和 Teardown

Golang的测试是容易的，但有一个问题，在这之前的JSON测试和模板渲染测试。他们都假设服务器正在运行，而这创建了一个不可靠的依赖。并且，需要一个“活”的服务器不是一个很好的主意。

``````func setup() *httptest.Server {
return httptest.NewServer(app.Handler())
}

func teardown(s *httptest.Server) {
s.Close()
}

func TestWelcome_name(t *testing.T) {
srv := setup()

url := fmt.Sprintf("%s/welcome.json?name=Frank", srv.URL)
resp, err := http.Get(url)
// verify errors & run assertions as usual

teardown(srv)
}
``````

## 结论

`Golang`的测试是一个很好的机会，它假定了你程序的外部视野和承担访问者的脚步，或是在大多数情况下，即你的API的用户。它提供了巨大的机会，以确保你提供了良好的代码和优质的体验。

------------------------