Writing Tests in Go

23 Nov 2019

Daigo Ikeda

Knightso, LLC

Profile

Daigo Ikeda
@hogedigo

Knightso, LLC
http://www.knightso.co.jp/
Shizuoka, JAPAN

2

要約

3

基本的なテスト

例)gotest/hello/hello.go

package hello

import "fmt"

// Hello says hello.
func Hello(name string) string {
    return fmt.Sprintf("Hello, %s!", name)
}
4

ファイル名に"_test.go"のサフィックスをつける。

テスト関数フォーマット

func TestXxx(*testing.T)
5

testingパッケージ

* ログ出力

* テストを失敗させる

* テストをスキップする

6

gotest/hello/hello_test.go

func TestHello(t *testing.T) {
    s := Hello("Gopher")
    want := "Hello, Gopher!"
    if s != want {
        t.Fatalf(`Hello("Gopher") = %s"; want "%s"`, s, want)
    }
}
7

go testコマンド

コマンドはGo標準に含まれている。

引数に何も指定しない場合はカレントディレクトリのテストを実行

$ go test

引数を指定した場合はパッケージ実行

$ go test gotest/hello

サブパッケージも再帰的に実行する場合

$ go test ./...
8

go testオプション(抜粋)

9

出力例(失敗):

--- FAIL: TestHello (0.00s)
    hello_test.go:11: Hello("Gopher") = Hello, Gopher!"; want "Hello, Gopher!!"
FAIL
exit status 1
FAIL    gotest/hello    0.005s

出力例(成功):

ok      gotest/hello    0.006s
10

ベンチマーク

func BenchmarkHello(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Hello("Gopher")
    }
}

go testコマンドに-benchオプションで実行する

go test -bench . ./...

出力:

goos: darwin
goarch: amd64
pkg: gotest/hello
BenchmarkHello-8        9722349           107 ns/op
PASS
ok      gotest/hello    1.177s
11

並列実行

func TestHello(t *testing.T) {
    t.Parallel()

    // test
}
12

SkipとShort

testing.T#Skipで、テストを途中でスキップすることができる

func TestHelloShort(t *testing.T) {
    if testing.Short() {
        t.Skip("skipped")
    }

    // 後続の時間のかかるテスト
}

testing.Shortは-shortオプション時にtrueを返す

func TestHelloShort(t *testing.T) {
    if testing.Short() {
        t.Skip("skipped")
    }

    // 後続の時間のかかるテスト
}
13

Examples

テスト関数フォーマット

func ExampleXxx(*testing.T)

標準出力をテストする

func ExampleHello() {
    fmt.Println(Hello("Gopher"))

    // Output: Hello, Gopher!
}

出力(失敗)

--- FAIL: ExampleHello (0.00s)
got:
Hello, Gopher!
want:
Hello, Gopher!!
FAIL
FAIL    gotest/hello    0.007s
14

さらにgo docに使用例表示できる

15

外部テストパッケージ

package hello_test

import (
    "fmt"
    "gotest/hello"
)

func ExampleHello_extpkg() {
    fmt.Println(hello.Hello("Gopher"))

    // Output: Hello, Gopher!
}
16

17

サブテスト

testing.T#Runで、テスト内でサブテストを実行できる

t.Run("name", func(t *testing.T) { ... })
18

func TestHelloSubTest(t *testing.T) {
    // 前処理etc

    t.Run("Hoge", func(t *testing.T) {
        s := Hello("Hoge")
        want := "Hello, Hoge!"
        if s != want {
            t.Fatalf(`Hello("Hoge") = %s"; want "%s"`, s, want)
        }
    })
    t.Run("Moke", func(t *testing.T) {
        // さらにネストできる
        t.Run("Fuga", func(t *testing.T) {
            s := Hello("Fuga")
            want := "Hello, Fuga!"
            if s != want {
                t.Fatalf(`Hello("Fuga") = %s"; want "%s"`, s, want)
            }
        })
    })

    // 後処理etc
}
19

Table Driven Test

20

func TestHelloTableDriven(t *testing.T) {
    tests := []struct {
        in  string
        out string
    }{
        {"Hoge", "Hello, Hoge!"},
        {"Moke", "Hello, Moke!"},
        {"Fuga", "Hello, Fuga!"},
        {"Bosukete", "Hello, Bosukete!"},
    }

    for _, tt := range tests {
        t.Run(tt.in, func(t *testing.T) {
            s := Hello(tt.in)
            if s != tt.out {
                t.Fatalf(`Hello("Fuga") = %s"; want "%s"`, s, tt.out)
            }
        })
    }
}
21

Property Based Test

testing/quick

func TestHelloQuick(t *testing.T) {
    oldHello := func(name string) string {
        return "Hello, " + name + "!"
    }

    f := func(s string) bool {
        return Hello(s) == oldHello(s)
    }

    if err := quick.Check(f, nil); err != nil {
        t.Error(err)
    }
}
22

TestMain

パッケージ全体の前処理、後処理を記述することができる

func TestMain(m *testing.M) {

    // パッケージ前処理

    cd := m.Run()

    // パッケージ後処理

    os.Exit(cd)
}
23

テストヘルパー

テストヘルパー関数(*testing.Tを受け取る関数)を作成した際、
testing.T#Helperメソッドを呼んでおくと結果出力のファイル名、行番号が調整される。

func assertEquals(t *testing.T, actual, want interface{}) {
    t.Helper()

    if !reflect.DeepEqual(actual, want) {
        // t.Helper()を呼ばないと↓のファイル名、行番号が出力されてしまう
        t.Errorf("not equals; actual:%v, want:%v", actual, want)
    }
}

func TestHelloHelper(t *testing.T) {
    assertEquals(t, Hello("Hoge"), "Hello, Hoge!")
    assertEquals(t, Hello("Moke"), "Hello, Moke!")
    assertEquals(t, Hello("Fuga"), "Hello, Fuga!")
}
24

アサーション

標準テストツールにアサーション機能は含まれていない。

意訳↓

25

とは言ってももう少し楽をしたい・・・

26

今日話したこと

27

Thank you

23 Nov 2019

Daigo Ikeda

Knightso, LLC

Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)