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

2016-10-17 09:47:13来源:oschina作者:暗夜在火星人点击

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

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)
}
}
}

Golang接口模拟

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
}

Read(p []byte) (n int, err error)
}

}
}

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)
}
}

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模板渲染测试

Welcome page

# Welcome Frank!

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

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)
}
}

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

{"Salutation": "Hello Frank!"}

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的用户。它提供了巨大的机会，以确保你提供了良好的代码和优质的体验。

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