单元测试

go自带的测试包 #

简单测试示例 #

  1. 创建测试文件xxx_test.go

  2. 创建测试函数TestXXX(t *testing.T)

func TestUser(t *testing.T)  {
	fmt.Println("开始测试用户")
	t.Run("测试子函数",addUser)
}

func addUser(t *testing.T)  {
	t.Log("我的第一函数")
}

#go test -v --run TestUser xxx_test.go[文件名可以不用指定]

#output
=== RUN   TestUser
开始测试用户
=== RUN   TestUser/测试子函数
    main_test.go:19: 我的第一函数
--- PASS: TestUser (0.00s)
    --- PASS: TestUser/测试子函数 (0.00s)
PASS
ok      test    2.212s

testing.M测试函数执行之前做一些其他操作

func TestMain(m *testing.M)  {
	fmt.Println("所有测试执行执行")
	m.Run()
}

#go test -v 

#output
所有测试执行执行
=== RUN   TestUser
开始测试用户
=== RUN   TestUser/测试子函数
    main_test.go:19: 我的第一函数
--- PASS: TestUser (0.00s)
    --- PASS: TestUser/测试子函数 (0.00s)
PASS
ok      test    2.009s

setup 和 teardown #

如果在同一个测试文件中,每一个测试用例运行前后的逻辑是相同的,一般会写在 setup 和 teardown 函数中。例如执行前需要实例化待测试的对象,如果这个对象比较复杂,很适合将这一部分逻辑提取出来;执行后,可能会做一些资源回收类的工作,例如关闭网络连接,释放文件等。标准库 testing 提供了这样的机制:

func setup() {
	fmt.Println("Before all tests")
}

func teardown() {
	fmt.Println("After all tests")
}

func Test1(t *testing.T) {
	fmt.Println("I'm test1")
}

func Test2(t *testing.T) {
	fmt.Println("I'm test2")
}

func TestMain(m *testing.M) {
	setup()
	code := m.Run()
	teardown()
	os.Exit(code)
}

testing.t的函数 #

t.FailNow() //立即终止测试
t.Fail() //仅标记不终止

单元测试框架提供的日志方法
  	  
Log	打印日志同时结束测试
Logf	格式化打印日志同时结束测试
Error	打印错误日志同时结束测试
Errorf	格式化打印错误日志同时结束测试
Fatal	打印致命日志同时结束测试
Fatalf	格式化打印致命日志同时结束测试

基准测试——获得代码内存占用和运行效率的性能数据 #

package tests

import "testing"

func Benchmark_Add(b *testing.B)  {
	var n int
	for i := 0; i < b.N; i++ {
		n++
	}
}

# go test -v -bench=Benchmark_Add benchmark_census_test.go
-bench可以是.可以是Add也是可以是Benchmark_Add

#output
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz
Benchmark_Add
Benchmark_Add-4         1000000000               0.4148 ns/op
PASS
ok      command-line-arguments  2.569s

基准测试框架对一个测试用例的默认测试时间是 1 秒。开始测试时,当以 Benchmark 开头的基准测试用例函数返回时还不到 1 秒,那么 testing.B 中的 N 值将按 1、2、5、10、20、50……递增,同时以递增后的值重新调用基准测试用例函数

通过-benchtime参数可以自定义测试时间

go test -v -bench=. -benchtime=5s benchmark_census_test.go

goos: linux
goarch: amd64
Benchmark_Add-4           10000000000                 0.33 ns/op
PASS
ok          command-line-arguments        3.380s

在命令行中添加-benchmem参数以显示内存分配情况

go test -v -bench=Add -benchmem benchmark_census_test.go
go test -v -bench=. -benchmem benchmark_census_test.go
go test -v -bench=Benchmark_Add -benchmem benchmark_census_test.go

#output
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz
Benchmark_Add
Benchmark_Add-4         1000000000               0.4203 ns/op          0 B/op          0 allocs/op
PASS
ok      command-line-arguments  2.665s

控制计时器 #

有些测试需要一定的启动和初始化时间,如果从 Benchmark() 函数开始计时会很大程度上影响测试结果的精准性。testing.B 提供了一系列的方法可以方便地控制计时器,从而让计时器只在需要的区间进行测试。我们通过下面的代码来了解计时器的控制

func Benchmark_Add_TimerControl(b *testing.B) {
    // 重置计时器
    b.ResetTimer()
    // 停止计时器
    b.StopTimer()
    // 开始计时器
    b.StartTimer()
    var n int
    for i := 0; i < b.N; i++ {
        n++
    }
}

测试覆盖率 #

go test -v -coverprofile cover.out user_test.go user.go
go tool cover -html=cover.out -o cover.html 

测试http #

package client

import (
	"encoding/json"
	"fmt"
	"net/http"
)

type UserInfo struct {
	Name string `json:"name"`
	Age int `json:"age"`
}

func NewUserInfo() *UserInfo {
	return &UserInfo{
		Name: "Test",
		Age:  30,
	}
}

func HandleNewUser(w http.ResponseWriter, r *http.Request) {
	name := r.URL.Query().Get("name")
	fmt.Printf("url parameter user name is %s\n", name)

	say := r.FormValue("say")
	fmt.Printf("req say:' %s '\n", say)
	newUser := NewUserInfo()
	jData, _ := json.Marshal(newUser)
	w.WriteHeader(http.StatusOK)
	w.Header().Set("Content-Type", "application/json")
	w.Write(jData)
}

func Start()  {
	http.HandleFunc("/create",HandleNewUser)
	http.ListenAndServe(":9090",nil)
}



测试脚本
package client

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
)

func TestHandleNewUser(t *testing.T)  {
	postBody := url.Values{}
	postBody.Add("say", "hello world")
	req := httptest.NewRequest(http.MethodPost, "http://localhost:9090/create?name=linus", strings.NewReader(postBody.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	w := httptest.NewRecorder()

	HandleNewUser(w,req)
	if w.Code != http.StatusOK {
		t.Error("new user api error")
	}
	if w.Body.Len() == 0 {
		t.Error(" response is empty")
	}
	user := &UserInfo{}
	err := json.Unmarshal(w.Body.Bytes(), user)
	if err != nil {
		t.Error("response data error")
	}
	t.Logf("create user api response : %#v", user)
}


#主函数启动服务
package main

import "test/client"

func main()  {
	client.Start()
}

# go run main.go
#client> go test -v

#output
=== RUN   TestHandleNewUser
url parameter user name is linus
req say:' hello world '
    server_test.go:31: create user api response : &client.UserInfo{Name:"Test", Age:30}
--- PASS: TestHandleNewUser (0.00s)
PASS
ok      test/client     2.541s

使用断言Assert #

go get github.com/stretchr/testify
func TestNewUserInfo(t *testing.T) {
    a := assert.New(t)

    router := gin.New()
    const path = "/newUserInfo"
    router.POST(path, NewUserInfo)

    body := url.Values{}
    body.Set("say", "hello world")
    rr, err := testutils.PostFormRequst(path + "?name=lp", router, body)
    a.Nil(err)

    user := &model.UserInfo{}
    err = json.Unmarshal(rr.Body.Bytes(), user)
    a.Nil(err)
    a.NotEqual(user.Name, "")
    a.NotEqual(user.Age, 0)
    t.Logf("%#v\n", user)
}

其它库httpexpect库 #

var testurl string = "http://127.0.0.1:9999"

func TestHttpGetPass(t *testing.T) {
	e := httpexpect.New(t, testurl) //创建一个httpexpect实例
	e.GET("/device").               //ge请求
					Expect().
					Status(http.StatusOK). //判断请求是否200
					JSON().
					Object().
					ContainsKey("name").
					ValueEqual("name", "探针程序")
}

常用测试库 #

github.com/stretchr/testify
github.com/jarcoal/httpmock
github.com/gavv/httpexpect

testify里有assert相信有其他语言基础的同学一定知道他是做什么的,断言处理比如

    a.Nil(err)
    a.NotEqual(user.Name, "")
    a.NotEqual(user.Age, 0)