开始使用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)
        }
    }
}
广告
将在 10 秒后关闭
bannerAds