阅读目录
测试工具 :go test
go test 本身可以携带很多的参数,熟悉这些参数,可以让我们的测试过程更加方便。
1 运行整个项目的测试文件
$ go test
pass
ok _/home/wangbm/golang/math 0.003s
2 只运行某个测试文件
math_test.go
math.go 是一对,缺一不可,前后顺序可对调。
$ go test math_test.go math.go
ok command-line-arguments 0.002s
3 加 -v 查看详细的结果
$ go test math_test.go math.go
=== run testadd
testadd: main_test.go:22: the result is ok
testadd: main_test.go:22: the result is ok
testadd: main_test.go:22: the result is ok
testadd: main_test.go:22: the result is ok
testadd: main_test.go:22: the result is ok
--- pass: testadd (0.00s)
pass
ok command-line-arguments 0.003s
4 只测试某个函数
-run
支持正则,如下例子中 testadd
,如果还有一个测试函数为 testadd02
,那么它也会被运行。
$ go test -v -run="testadd"
=== run testadd
testadd: math_test.go:22: the result is ok
testadd: math_test.go:22: the result is ok
testadd: math_test.go:22: the result is ok
testadd: math_test.go:22: the result is ok
testadd: math_test.go:22: the result is ok
--- pass: testadd (0.00s)
pass
ok _/home/wangbm/golang/math 0.003s
5 生成 test 的二进制文件:加 -c 参数
$ go test -v -run="testadd" -c
$
$ ls -l
total 3208
-rw-r--r-- 1 root root 95 may 25 20:56 math.go
-rwxr-xr-x 1 root root 3272760 may 25 21:00 math.test
-rw-r--r-- 1 root root 525 may 25 20:56 math_test.go
6 执行这个 test 测试文件:加 -o 参数
$ go test -v -o math.test
=== run testadd
testadd: math_test.go:22: the result is ok
testadd: math_test.go:22: the result is ok
testadd: math_test.go:22: the result is ok
testadd: math_test.go:22: the result is ok
testadd: math_test.go:22: the result is ok
--- pass: testadd (0.00s)
=== run testaum
testaum: math_test.go:30: 6
--- pass: testaum (0.00s)
pass
ok _/home/wangbm/golang/math 0.002s
7 测试安装/重新安装 依赖包,而不运行代码:加 -i 参数
# 这里没有输出
$ go test -i
单元测试
准备两个 go 文件::
e:\text\test_go\test\math\math.go
package math
func add(x, y int) int {
return x + y
}
e:\text\test_go\test\math\math_test.go
package math
import "testing"
func testadd(t *testing.t) {
t.log(add(1, 2))
}
然后使用 go test 工具去执行:
ps e:\text\test_go\test\math> go test .
ok test/math 0.365s
ps e:\text\test_go\test\math>
ps e:\text\test_go\test\math> go test . -v
=== run testadd
math_test.go:6: 3
--- pass: testadd (0.00s)
pass
ok test/math 0.359s
ps e:\text\test_go\test\math>
从上面这个例子中,可以总结中几点 go 语言测试框架要遵循的规则:
- 单元测试代码的 go文件必须以
_test.go
结尾,而前面最好是被测试的文件名(不过并不是强制的),比如要测试 math.go 测试文件名就为 math_test.go。 - 单元测试的函数名必须以test开头,后面直接跟要测试的函数名,比如要测试 add函数,单元测试的函数名就得是 testadd。
- 单元测试的函数必须接收一个指向
testing.t
类型的指针,并且不能返回任何值。
表组测试
add(1, 2) 是一次单元测试的场景,而 add(2, 4) ,add(3, 6) 又是另外两种单元测试的场景。
对于多种输入场景的测试,我们可以同时放在 testadd 里进行测试,这种测试方法就是表组测试。
修改 e:\text\test_go\test\math\math_test.go
如下:
package math
import "testing"
func testadd(t *testing.t) {
sum := add(1, 2)
if sum == 3 {
t.log("the result is ok")
} else {
t.fatal("the result is wrong")
}
sum = add(2, 4)
if sum == 6 {
t.log("the result is ok")
} else {
t.fatal("the result is wrong")
}
}
ps e:\text\test_go\test\math> go test . -v
=== run testadd
math_test.go:8: the result is ok
math_test.go:15: the result is ok
--- pass: testadd (0.00s)
pass
ok test/math 0.356s
ps e:\text\test_go\test\math>
如果输入的场景实在太多(比如下面用的五组输入),用上面的方法,可能需要写很多重复的代码,这时候可以利用表格测试法。
e:\text\test_go\test\math\math_test.go
package math
import (
"fmt"
"testing"
)
type testtable struct {
xarg int
yarg int
}
func testadd(t *testing.t) {
tables := []testtable{
{1, 2},
{2, 4},
{4, 8},
{5, 10},
{6, 12},
}
for _, table := range tables {
result := add(table.xarg, table.yarg)
if result == (table.xarg + table.yarg) {
t.log("" + fmt.sprintf("the result is ok %d", result))
} else {
t.fatal("the result is wrong")
}
}
}
ps e:\text\test_go\test\math> go test . -v
=== run testadd
math_test.go:25: the result is ok 3
math_test.go:25: the result is ok 6
math_test.go:25: the result is ok 12
math_test.go:25: the result is ok 15
math_test.go:25: the result is ok 18
--- pass: testadd (0.00s)
pass
ok test/math 0.351s
ps e:\text\test_go\test\math>
理清 go 中晦涩难懂的寻址问题
什么叫可寻址?
可直接使用 & 操作符取地址的对象,就是可寻址的(addressable)。
e:\text\test_go\test\math\math.go
package math
import "fmt"
func unit() {
name := "heihie"
fmt.println(&name)
}
e:\text\test_go\test\math\math_test.go
ps e:\text\test_go\test\math> go test . -v
=== run testunit
0xc0000505a0
--- pass: testunit (0.00s)
pass
ok test/math 0.313s
ps e:\text\test_go\test\math>
程序运行不会报错,说明 name
这个变量是可寻址的。
但不能说 “iswbm” 这个字符串是可寻址的。
“iswbm” 是字符串,字符串都是不可变的,是不可寻址的,后面会介绍到。
在开始逐个介绍之前,先说一下结论:
-
指针可以寻址:
&profile{}
-
变量可以寻址:
name := profile{}
-
字面量通通不能寻址:
profile{}
哪些是可以寻址的?
变量:&x
func main() {
name := "iswbm"
fmt.println(&name)
// output: 0xc000010200
}
指针:&*x
type profile struct {
name string
}
func main() {
fmt.println(unsafe.pointer(&profile{name: "iswbm"}))
// output: 0xc000108040
}
数组元素索引: &a[0]
func main() {
s := [...]int{1,2,3}
fmt.println(&s[0])
// output: xc0000b4010
}
切片
func main() {
fmt.println([]int{1, 2, 3}[1:])
}
切片元素索引:&s[1]
func main() {
s := make([]int , 2, 2)
fmt.println(&s[0])
// output: xc0000b4010
}
组合字面量不可寻址字段属性可寻址
所有的组合字面量都是不可寻址的,就像下面这样子
type profile struct {
name string
}
func new() profile {
return profile{name: "iswbm"}
}
func main() {
fmt.println(&new())
// cannot take the address of new()
}
注意上面写法与这个写法的区别,下面这个写法代表不同意思,其中的 &
并不是取地址的操作,而代表实例化一个结构体的指针。
type profile struct {
name string
}
func main() {
fmt.println(&profile{name: "iswbm"}) // ok
}
虽然组合字面量是不可寻址的,但却可以对组合字面量的字段属性进行寻址(直接访问)
type profile struct {
name string
}
func new() profile {
return profile{name: "iswbm"}
}
func main() {
fmt.println(new().name)
}
哪些是不可以寻址的?
常量
import "fmt"
const version = "1.0"
func main() {
fmt.println(&version)
}
字符串
func getstr() string {
return "iswbm"
}
func main() {
fmt.println(&getstr())
// cannot take the address of getstr()
}
函数或方法
func getstr() string {
return "iswbm"
}
func main() {
fmt.println(&getstr)
// cannot take the address of getstr
}
基本类型字面量
字面量分:基本类型字面量 和 复合型字面量。
基本类型字面量,是一个值的文本表示,都是不应该也是不可以被寻址的。
func getint() int {
return 1024
}
func main() {
fmt.println(&getint())
// cannot take the address of getint()
}
map 中的元素
字典比较特殊,可以从两个角度来反向推导,假设字典的元素是可寻址的,会出现 什么问题?
- 如果字典的元素不存在,则返回零值,而零值是不可变对象,如果能寻址问题就大了。
- 而如果字典的元素存在,考虑到 go 中 map 实现中元素的地址是变化的,这意味着寻址的结果也是无意义的。
基于这两点,map 中的元素不可寻址,符合常理。
func main() {
p := map[string]string {
"name": "iswbm",
}
fmt.println(&p["name"])
// cannot take the address of p["name"]
}
搞懂了这点,你应该能够理解下面这段代码为什么会报错啦~
e:\text\test_go\test\math\math.go
package math
import "fmt"
type person struct {
name string
email string
}
func unit() {
m := map[int]person{
1: person{"andy", "1137291867@qq.com"},
2: person{"tiny", "qishuai231@gmail.com"},
3: person{"jack", "qs_edu2009@163.com"},
}
//编译错误:cannot assign to struct field m[1].name in map
// m[1].name = "scrapup"
fmt.println(m[1].name)
}
e:\text\test_go\test\math\math_test.go
package math
import (
"fmt"
"testing"
)
func testunit(t *testing.t) {
unit()
}
func testcc(t *testing.t) {
fmt.println("hello")
}
ps e:\text\test_go\test\math> go test -v -run="testunit"
=== run testunit
andy
--- pass: testunit (0.00s)
pass
ok test/math 0.339s
ps e:\text\test_go\test\math>
解决map修改值的方法
package math
import "fmt"
type person struct {
name string
email string
}
func unit() {
m := map[int]person{
1: person{"andy", "1137291867@qq.com"},
2: person{"tiny", "qishuai231@gmail.com"},
3: person{"jack", "qs_edu2009@163.com"},
}
// 创建一个新的person
newperson := person{name: "scrapup", email: "1137291867@qq.com"}
// 创建一个新的map
newmap := make(map[int]person)
// 遍历原始map
for k, v := range m {
// 如果键值不等于1,则复制元素
if k != 1 {
newmap[k] = v
} else {
// 否则,添加新的person
newmap[k] = newperson
}
}
// 将新的map赋值给原始map
m = newmap
//打印结果
for k, v := range m {
fmt.printf("key is %d, value is %v)\n", k, v)
}
}
数组字面量进行切片操作
数组字面量是不可寻址的,当你对数组字面量进行切片操作,其实就是寻找内部元素的地址,下面这段代码是会报错的。
func main() {
fmt.println([3]int{1, 2, 3}[1:])
// invalid operation [3]int literal[1:] (slice of unaddressable value)
}
发表评论