开始使用Golang进行测试
首先
在Go语言中进行测试非常简单,不需要像Ruby的rspec一样去学习新的DSL。关于测试的文章有很多,但我在其中选择了一些我认为最初应该记住的重要信息。
基本测试的编写方法
-
- 例えばcalc.go のテストならば同じディレクトリ内にcalc_test.goという名前で作成する。
-
- テストファイル内ではtestingパッケージをインポートする。
-
- テストファイル内では、TestXXXという名前でテストメソッドを作成する。
-
- DSLは特に無いので普通にテストコードを書く。
- パラメータと期待値の組み合わせの配列を用意して、ループで検証していく形が推奨されている(Table Driven Test)
package calc
func Add(a,b int) int {
return a + b
}
package calc
import (
"testing"
)
func TestAdd(t *testing.T) {
patterns := []struct {
a int
b int
expected int
}{
{1, 2, 3},
{10, -2, 8},
{-10, -2, -12},
}
for idx, pattern := range patterns {
actual := Add(pattern.a, pattern.b)
if pattern.expected != actual {
t.Errorf("pattern %d: want %d, actual %d", idx, pattern.expected, actual)
}
}
}
如何执行测试
-
- カレントディレクトリ以下すべてを再帰的にテストgo test -v ./…
特定のパッケージをテストgo test -v ./hogehoge(パッケージディレクトリを相対パスで指定する)
特定のメソッドのみテストするgo test -run TestAdd ./…
在执行时添加“-v”选项会附带详细的执行结果,基本上最好加上这个选项。
在执行测试前后添加处理的方法是
定义TestMain方法。
当执行code := m.Run()时,测试方法会运行,因此可以在其前后加入数据库初始化等处理。
package calc
import (
"fmt"
"os"
"testing"
)
func TestMain(m *testing.M) {
fmt.Println("before test")
code := m.Run()
fmt.Println("after test")
os.Exit(code)
}
func TestAdd(t *testing.T) {
// 以下省略
}
运行这个会变成以下这样。
可以看到在测试的前后加了fmt.Println。
$ go test -v ./...
before test
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
after test
ok
使用模拟测试需要
使用接口进行模拟化
如果使用接口的对象,在实际代码和测试代码中使用不同的对象来定义接口,就可以更改测试时的行为。在这里,我们介绍一种使用模拟对象来切换在somefunc包的Client对象的Run方法内调用的call方法行为的方法。
package somefunc
type Caller interface {
call(val int) int
}
type Client struct {
FuncCaller Caller
}
type ExampleCaller struct{}
func (c *Client) Run(val int) int {
return c.FuncCaller.call(val)
}
func (f *ExampleCaller) call(val int) int {
return val
}
要运行上述代码,请按如下方式进行调用。
c := somefunc.Client{&somefunc.ExampleCaller{}}
c.Run(1)
在这里,要在测试期间创建ExampleCaller的模拟,并改变call方法的行为,可以使用以下测试代码。
package somefunc
import (
"testing"
)
func TestRun(t *testing.T) {
patterns := []struct {
val int
expected int
}{
{2, 2},
{8, 8},
{-10, -10},
}
for idx, pattern := range patterns {
// Clientのnewの際に、モックオブジェクトを引数にする
c := Client{&mockCaller{}}
actual := c.Run(pattern.val)
if pattern.expected != actual {
t.Errorf("pattern %d: want %d, actual %d", idx, pattern.expected, actual)
}
}
}
// callメソッドのレシーバをmockCallerとして宣言する。
type mockCaller struct{}
// 通常のコードではcallメソッドは引数の値をそのまま返却するが、
// モックでは、引数 + 10した値を返却するようにする。
func (s *mockCaller) call(val int) int {
return val + 10
}
使用变量重新赋值的方式
在这里我们正在进行someprocess包中Run函数的测试,但在Run函数内部调用了一个名为call的函数。
为了在测试时仅切换call函数的行为,我们可以将call函数存储在一个变量中,并在测试内部重新赋值该变量即可。
package someprocess
func Run(val int) int {
return call(val)
}
var call = func(val int) int {
return val
}
package someprocess
import (
"testing"
)
func TestRun(t *testing.T) {
call = func(val int) int {
return val + 10
}
patterns := []struct {
val int
expected int
}{
{2, 12},
{8, 18},
{-10, 0},
}
for idx, pattern := range patterns {
actual := Run(pattern.val)
if pattern.expected != actual {
t.Errorf("pattern %d: want %d, actual %d", idx, pattern.expected, actual)
}
}
}