go--test的那些事

单元测试

测试单个文件和单个方法

// 测试当前包下的所有测试文件
go test 
// 测试指定文件(测试单个文件,一定要带上被测试的原文件)
go test -v **_test.go  **.go
// 测试指定文件下的指定韩式
go test -v **_test.go  **.go  -test.run FuncName
// 测试单个方法
go test -v -test.run FuncName


例如:

go test -v ./algorithm/luck_envelope_test.go ./algorithm/luck_envelope.go
go test -v ./algorithm/luck_envelope_test.go ./algorithm/luck_envelope.go -test.run TestSimpleRandList

参考链接:go test 测试单个文件和测试单个函数

单元测试基本写法

  • Go 语言推荐测试文件和源代码文件放在一块,测试文件以 _test.go 结尾。
  • 测试用例名称一般命名为 Test 加上待测试的方法名。
  • 测试用的参数有且只有一个,在这里是 t *testing.T。
  • 基准测试(benchmark)的参数是 *testing.B,TestMain 的参数是 *testing.M 类型。
    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 main

import "testing"

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

testing 的测试用例形式

测试用例有四种形式:

TestXxxx(t *testing.T)    // 基本测试用例
BenchmarkXxxx(b *testing.B) // 压力测试的测试用例
Example_Xxx()  // 测试控制台输出的例子
TestMain(m *testing.M) // 测试 Main 函数

给个 Example 的例子 :(Example 需要在最后用注释的方式确认控制台输出和预期是不是一致的)

func Example_GetScore() {
        score := getScore(100, 100, 100, 2.1)
        fmt.Println(score)
        // Output:
        // 31.1
    }

Benchmark 基准测试

概述

基准测试,是一种测试代码性能的方法,比如你有多种不同的方案,都可以解决问题,那么到底是那种方案性能更好呢?这时候基准测试就派上用场了。

基准测试主要是通过测试CPU和内存的效率问题,来评估被测试代码的性能,进而找到更好的解决方案。比如链接池的数量不是越多越好,那么哪个值才是最优值呢,这就需要配合基准测试不断调优了。

基准测试基本写法

基准测试用例的定义如下:

func BenchmarkName(b *testing.B){
    // ...
}

函数名必须以 Benchmark 开头,后面一般跟待测试的函数名
参数为 b *testing.B。
执行基准测试时,需要添加 -bench 参数。

例如:
使用 RunParallel 测试并发性能

// 基准测试
func BenchmarkSimpleRandList(b *testing.B) {
	count,amount := int64(10), int64(100)
	for i := 0; i < b.N; i++ {
		SimpleRandList(count,amount)
	}
}

// RunParallel 测试并发性能
func BenchmarkSimpleRandList2(b *testing.B) {
	count,amount := int64(10), int64(100)
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			SimpleRandList(count,amount)
		}
	})
}

基准测试命令

// 测试所有基准测试函数
go test -bench=.
//测试指定函数的基准测试
go test -bench=原函数名
// 增加内存统计
go test -bench=. -benchmem
// 标志指定运行时间
go test -bench=. -benchtime=20s

例如:

go test -benchmem -bench=.
go test -benchmem -bench=SimpleRandList
go test -bench=SimpleRandList

基准测试结果

在这里插入图片描述

循环前耗时配置

如果基准测试在循环前需要一些耗时的配置,则可以先重置定时器:

func BenchmarkBigLen(b *testing.B) {
    big := NewBig()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        big.Len()
    }
}

子测试(Subtests)

子测试是 Go 语言内置支持的,可以在某个测试用例中,根据测试场景使用 t.Run创建不同的子测试用例:

// calc_test.go

func TestMul(t *testing.T) {
	t.Run("pos", func(t *testing.T) {
		if Mul(2, 3) != 6 {
			t.Fatal("fail")
		}

	})
	t.Run("neg", func(t *testing.T) {
		if Mul(2, -3) != -6 {
			t.Fatal("fail")
		}
	})
}

之前的例子测试失败时使用 t.Error/t.Errorf,这个例子中使用 t.Fatal/t.Fatalf,区别在于前者遇错不停,还会继续执行其他的测试用例,后者遇错即停
测试:go test -run TestMul/pos -v

测试命令

func TestFoo(t *testing.T) {
    // <setup code>
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) { ... })
    // <tear-down code>
}
go test -run ''      # 执行所有测试。
go test -run Foo     # 执行匹配 "Foo" 的顶层测试,例如 "TestFooBar"。
go test -run Foo/A=  # 对于匹配 "Foo" 的顶层测试,执行其匹配 "A=" 的子测试。
go test -run /A=1    # 执行所有匹配 "A=1" 的子测试。

运行并验证示例

在这里插入图片描述

网络测试

TCP

假设需要测试某个 API 接口的 handler 能够正常工作,例如 helloHandler

func helloHandler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello world"))
}

那我们可以创建真实的网络连接进行测试:

// test code
import (
	"io/ioutil"
	"net"
	"net/http"
	"testing"
)

func handleError(t *testing.T, err error) {
	t.Helper()
	if err != nil {
		t.Fatal("failed", err)
	}
}

func TestConn(t *testing.T) {
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	handleError(t, err)
	defer ln.Close()

	http.HandleFunc("/hello", helloHandler)
	go http.Serve(ln, nil)

	resp, err := http.Get("http://" + ln.Addr().String() + "/hello")
	handleError(t, err)

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	handleError(t, err)

	if string(body) != "hello world" {
		t.Fatal("expected hello world, but got", string(body))
	}
}
  • net.Listen(“tcp”, “127.0.0.1:0”):监听一个未被占用的端口,并返回 Listener。
  • 调用 http.Serve(ln, nil) 启动 http 服务。
  • 使用 http.Get 发起一个 Get 请求,检查返回值是否正确。
  • 尽量不对 http 和 net 库使用 mock,这样可以覆盖较为真实的场景。

httptest

针对 http 开发的场景,使用标准库 net/http/httptest 进行测试更为高效。
上述的测试用例改写如下:

// test code
import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestConn(t *testing.T) {
	req := httptest.NewRequest("GET", "http://example.com/foo", nil)
	w := httptest.NewRecorder()
	helloHandler(w, req)
	bytes, _ := ioutil.ReadAll(w.Result().Body)

	if string(bytes) != "hello world" {
		t.Fatal("expected hello world, but got", string(bytes))
	}
}

使用 httptest 模拟请求对象(req)和响应对象(w),达到了相同的目的。

学习链接:
https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter09/09.2.html
https://geektutu.com/post/quick-go-test.html#7-Benchmark-%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95
http://shouce.jb51.net/gopl-zh/ch11/ch11-04.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值