Golang Testing

 

单元测试覆盖率

  1. 测试覆盖率
go test -cover

结果类似于:

PASS
coverage: 90% of statements
ok  	github.com/sasakiyori/test	1.025s
  1. 区分unit test和integration test的覆盖率

参考: https://mickey.dev/posts/go-build-tags-testing/

unit test文件添加build tag

// +build !integration

integration test文件添加build tag

// +build integration

分别获取覆盖率:

# unit test
go test -cover
# integration test
go test --tags=integration -cover
  1. 测试覆盖率可视化

参考: https://go.dev/blog/cover

# 将测试结果输出为本地文件
go test -coverprofile=result_file

# 将测试结果按go function区分展示
go tool cover -func=result_file

# 将测试结果按html展示
go tool cover -html=result_file

# 将测试结果保存为html文件
go tool cover -html=result_file -o coverage.html

功能测试覆盖率

参考

https://medium.com/@manabie/test-coverage-of-go-services-during-integration-tests-6ff1bdbe33e0

代码实现

按go test的形式编译并启动可执行程序,手动触发daemon服务退出,即可统计整体覆盖率:

  • 在main.go同级目录下创建文件main_test.go,主要实现TestRun方法,内容与main.go中的main方法一致
  • 因为要生成覆盖率文件,需要让TestRun正常退出,因此使用context.CancelFunc,在原服务之外另起一个killServer,二者共享同一个context,通过调用killServer触发其cancelFunc,使原服务得到正常退出
package main

import (
	"context"
	"fmt"
	"net/http"
	"testing"
)

type killServer struct {
	server http.Server
	cancel context.CancelFunc
}

func newKillServer(addr string, cancel context.CancelFunc) *killServer {
	return &killServer{
		server: http.Server{
			Addr: addr,
		},
		cancel: cancel,
	}
}

func (s *killServer) Start() {
	s.server.Handler = s

	err := s.server.ListenAndServe()
	if err != nil {
		fmt.Println("KillServer Error:", err)
	}
}

func (s *killServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	// cancel the context
	s.cancel()
}

func TestRun(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	killServer := newKillServer(":19999", cancel)
	go killServer.Start()

	// same call with func main()
	go Run(ctx)

	<-ctx.Done()

	killServer.server.Shutdown(context.Background())
}

编译运行测试

  1. main_test.go编写完成后,运行命令生成可执行带测试文件xxx.test。其中coverpkg可以指定需要计算覆盖率的路径范围
go test -c ./ -cover -covermode=count -coverpkg=./...
  1. 运行测试文件,指定覆盖率输出文件
    ./xxx.test -test.coverprofile coverage.out
    
  1. 运行自己的功能测试,测试服务功能
  2. 测试完成后,调用killServer,退出测试程序,获得覆盖率,生成覆盖率输出文件
    curl 127.0.0.1:19999
    
  1. 查看覆盖率及输出文件
    go tool cover -html=coverage.out
    

mock方法论

  1. 在不使用其他plugin/package的前提下的mock方法论, 包含sql, file, http call等: https://www.cbinsights.com/research/team-blog/mocking-techniques-for-go/
    • Higher-order functions: 使用封装函数, 将需要mock的函数作为封装函数的参数, 达到可替换的效果
    • Monkey patching: mock package级别的方法时, 将其赋予一个package级别的变量,再在mock时对变量进行替换
    • interface substitution: 想mock某个interface时, mock一个struct完全实现interface的方法进行替换
    • Embedding interfaces: 想mock某个interface里的部分函数时,可以将interface作为业务代码函数入参,使用内嵌的方式改写部分函数
    • Mocking out downstream HTTP calls with net/http/httptest
  2. 好用的工具