标签:func testing 单元测试 test TestAdd go PASS GO
作为golang人我们一定要养成写单元测试的习惯,这样才能保证我们交付质量,也称为TDD(Test Driven Development)测试驱动开发
什么是单元测试
- 单元是应用的最小可测试部件,如函数和对象的方法
- 单元测试是软件开发中对最小单位进行正确性检验的测试工作
为什么进行单元测试
- 保证变更/重构的正确性,特别是在一些频繁变动和多人合作开发的项目中
- 简化调试过程:可以轻松的让我们知道哪一部分代码出了问题
- 单测最好的文档:在单测中直接给出具体接口的使用方法,是最好的实例代码
单元测试用例编写的原则
- 单一原则:一个测试用例只负责一个场景
- 原子性:结果只有两种情况:Pass/Fail
- 优先要核心组件和逻辑的测试用例
- 高频使用库,util,重点覆盖这些
单测用例约定
-
文件名必须要xx_test.go命名
-
文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数
类型 格式 作用 基准函数 函数名前缀为Benchmark 测试函数的性能 测试函数 函数名前缀为Test 测试程序的一些逻辑行为是否正确 示例函数 函数名前缀为Example 为文档提供示例文档
-
-
测试方法必须是Testxx开头
-
方法中的参数 必须是t *testing.T
-
测试文件和被测试文件必须在一个包中
其中参数t用于报告测试失败和附加的日志信息。 testing.T的拥有的方法如下:
func (c *T) Error(args ...interface{})
func (c *T) Errorf(format string, args ...interface{})
func (c *T) Fail()
func (c *T) FailNow()
func (c *T) Failed() bool
func (c *T) Fatal(args ...interface{})
func (c *T) Fatalf(format string, args ...interface{})
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})
func (c *T) Name() string
func (t *T) Parallel()
func (t *T) Run(name string, f func(t *T)) bool
func (c *T) Skip(args ...interface{})
func (c *T) SkipNow()
func (c *T) Skipf(format string, args ...interface{})
func (c *T) Skipped() bool
动手环节
简单testing
// xx_tets.go
package models
import (
"testing"
)
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
a := 10
b := 20
want := 30
actual := Add(a, b)
if want != actual {
t.Errorf("[Add 函数参数:%d %d][期望:%d][实际:%d]", a, b, want, actual)
}
}
// **执行go test -v .**
// **=== RUN TestAdd**
// **--- PASS: TestAdd (0.00s)**
// **PASS
// ok**
运行过程:go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件。
切片单元测试
(1/5)写一个Split函数(main.go)
package main
import (
"strings"
)
func Split(s, sep string) (result []string) {
i := strings.Index(s, sep)
for i > -1 {
if i == 0 { // 如果划分是开头的第一个则进
s = s[len(sep):]
} else {
result = append(result, s[:i])
s = s[i+len(sep):]
}
i = strings.Index(s, sep)
}
result = append(result, s)
return
}
(2/5)同样的包下,写一个main_test.go测试文件
package main
import (
"reflect"
"testing"
)
func TestSplit(t *testing.T) {
got := Split("a:b:c", ":") // 程序输出的结果
want := []string{"a", "b", "c"} // 期望的结果
if !reflect.DeepEqual(want, got) { // 因为slice不能比较直接,借助反射包中的方法比较
t.Errorf("expected:%v, got:%v", want, got) // 测试失败输出错误提示
}
}
(3/5)文件结构
$ ls
main.go main_test.go
(4/5)测试通过例子
$ go test
PASS
ok test 0.002s
$ go test -v
=== RUN TestSplit
--- PASS: TestSplit (0.00s)
PASS
ok test 0.001s
(5/5)测试失败例子
$ go test .
--- FAIL: TestSplit (0.00s)
main_test.go:12: expected:[a b d], got:[a b c]
FAIL
exit status 1
FAIL test 0.001s
$ go test -v .
=== RUN TestSplit
main_test.go:12: expected:[a b d], got:[a b c]
--- FAIL: TestSplit (0.00s)
FAIL
exit status 1
FAIL test 0.001s
测试组
添加更多的测试用例,保证在交付前把缺陷解决完,添加TestMoreSplit
函数
func TestMoreSplit(t *testing.T) {
type test struct {
input string
sep string
want []string
}
// 插入测试数据
tests := []test{
{input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
{input: ":a:b:c", sep: ":", want: []string{"a", "b", "c"}},
{input: "abc", sep: ":", want: []string{"abc"}},
{input: "abcd", sep: "bc", want: []string{"a", "d"}},
{input: "云原生", sep: "云", want: []string{"原生"}},
}
// 遍历slice
for _, d := range tests {
got := Split(d.input, d.sep)
if !reflect.DeepEqual(got, d.want) {
t.Errorf("expected:%v, got:%v", d.want, got)
}
}
}
go test -v .结果
$ go test -v .
=== RUN TestSplit
--- PASS: TestSplit (0.00s)
=== RUN TestMoreSplit
--- PASS: TestMoreSplit (0.00s)
PASS
ok test 0.002s
子测试
自定义子测试名称
package main
import (
"testing"
)
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
t.Run("正数", func(t *testing.T) {
if Add(10, 20) != 30 {
t.Fatal("add.zhengshu.error")
}
})
t.Run("负数", func(t *testing.T) {
if Add(-20, 10) != -10 {
t.Fatal("add.fushu.error")
}
})
}
// go test -v .
// === RUN TestAdd
// === RUN TestAdd/正数
// === RUN TestAdd/负数
// --- PASS: TestAdd (0.00s)
// --- PASS: TestAdd/正数 (0.00s)
// --- PASS: TestAdd/负数 (0.00s)
// PASS
还可以这样写
package main
import (
"testing"
)
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
type test struct {
num1 int
num2 int
want int
}
tests := map[string]test{
"正数": {num1: 10, num2: 20, want: 30},
"负数": {num1: -20, num2: 10, want: -10},
}
for name, i := range tests {
t.Run(name, func(t *testing.T) {
got := Add(i.num1, i.num2)
if got != i.want {
t.Fatalf("%v error, 期望: %d, 实际: %d", name, i.want, got)
}
})
}
}
// $ go test -v .
// === RUN TestAdd
// === RUN TestAdd/正数
// === RUN TestAdd/负数
// --- PASS: TestAdd (0.00s)
// --- PASS: TestAdd/正数 (0.00s)
// --- PASS: TestAdd/负数 (0.00s)
// PASS
// ok test (cached)
执行某一个函数
$ go test -run=TestSplit -v .
=== RUN TestSplit
--- PASS: TestSplit (0.00s)
PASS
ok test 0.001s
// 模糊测试
$ go test -run=TestMore.* -v .
=== RUN TestMoreSplit
--- PASS: TestMoreSplit (0.00s)
PASS
ok test 0.001s
// 执行子测试函数
$ go test -run=TestAdd/正数 -v .
=== RUN TestAdd
=== RUN TestAdd/正数
--- PASS: TestAdd (0.00s)
--- PASS: TestAdd/正数 (0.00s)
PASS
ok test 0.002s
跳过某个测试函数
go test -short
测试覆盖率
用于统计目标包有百分之多少的代码参与了单测
$ go test -v -cover
=== RUN TestSplit
--- PASS: TestSplit (0.00s)
=== RUN TestMoreSplit
--- PASS: TestMoreSplit (0.00s)
PASS
coverage: 100.0% of statements
ok test 0.002s
-coverprofile
参数,用来将覆盖率相关的记录信息输出到一个文件中
$ go test -v -cover -coverprofile=split.out
=== RUN TestSplit
--- PASS: TestSplit (0.00s)
=== RUN TestMoreSplit
--- PASS: TestMoreSplit (0.00s)
PASS
coverage: 100.0% of statements
ok test 0.001s
// 该命令会打开本地的浏览器窗口生成一个HTML报告
$ go tool cover -html=split.out
表驱动测试 table-driven tests
// cal.go
package main
func Add(x int) (res int) {
res = x + 2
return res
}
// cal_test.go
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAdd(t *testing.T) {
assert.Equal(t, Add(5), 7, "they should be equal")
}
func TestCal(t *testing.T) {
ass := assert.New(t)
var tests = []struct {
input int
expected int
}{
{2, 4},
{1, 3},
{-2, 0},
{-6, -4},
{6, 8},
}
for _, test := range tests {
ass.Equal(Add(test.input), test.expected)
}
}
GoConvey测试框架
// student.go
package models
import "fmt"
type Student struct {
Name string
ChiScore int
EngScore int
MathScore int
}
func NewStudent(name string) (*Student, error) {
if name == "" {
return nil, fmt.Errorf("name为空")
}
return &Student{
Name: name,
}, nil
}
func (s *Student) GetAvgScore() (int, error) {
score := s.ChiScore + s.EngScore + s.MathScore
if score == 0 {
return 0, fmt.Errorf("全部为0分")
}
return score / 3, nil
}
// student_test.go
package models
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestNewStudent(t *testing.T) {
Convey("start test new", t, func() {
stu, err := NewStudent("")
Convey("空的name初始化报错", func() {
So(err, ShouldBeError) // 判断err
})
Convey("stu对象为nil", func () {
So(stu, ShouldBeNil) // 判断stu
})
})
}
// go test -v .
// === RUN TestNewStudent
// start test new
// 空的name初始化报错 .
// stu对象为nil .
// 2 total assertions
// --- PASS: TestNewStudent (0.00s)
// PASS
// 进阶
package models
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestNewStudent(t *testing.T) {
Convey("start test new", t, func() {
stu, err := NewStudent("")
Convey("空的name初始化报错", func() {
So(err, ShouldBeError)
})
Convey("stu对象为nil", func() {
So(stu, ShouldBeNil)
})
})
}
func TestScore(t *testing.T) {
stu, _ := NewStudent("xx")
Convey("不设置分数可能出错", t, func() {
sc, err := stu.GetAvgScore()
Convey("获取分数出错了", func() {
So(err, ShouldBeError)
})
Convey("分数为0", func() {
So(sc, ShouldEqual, 0)
})
})
Convey("正常情况", t, func() {
stu.ChiScore = 60
stu.EngScore = 70
stu.MathScore = 80
score, err := stu.GetAvgScore()
Convey("获取分数出错了", func() {
So(err, ShouldBeNil)
})
Convey("平均分大于60", func() {
So(score, ShouldBeGreaterThan, 60)
})
})
}
// go test -v .
// === RUN TestNewStudent
// start test new
// 空的name初始化报错 .
// stu对象为nil .
// 2 total assertions
// --- PASS: TestNewStudent (0.00s)
// === RUN TestScore
// 不设置分数可能出错
// 获取分数出错了 .
// 分数为0 .
// 4 total assertions
// 正常情况
// 获取分数出错了 .
// 平均分大于60 .
// 6 total assertions
// --- PASS: TestScore (0.00s)
// PASS
// ok
安装图形化goconvey
// 安装二进制,添加环境变量
go get github.com/smartystreets/goconvey
// 运行
goconvey
testify
// cal.go
package main
func Add(x int) (res int) {
res = x + 2
return res
}
// cal_test.go
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAdd(t *testing.T) {
assert.Equal(t, Add(5), 7, "they should be equal")
}
testify/mock数据
https://github.com/euclidr/testingo
标签:func,testing,单元测试,test,TestAdd,go,PASS,GO 来源: https://www.cnblogs.com/Otiger/p/16221876.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。