Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tour of testing in 2018

Tour of testing in 2018

golang.tokyo #17の発表資料です。
https://golangtokyo.connpass.com/event/96200/

Go1.10までのGoのテストの仕様、ノウハウをまとめました。

口頭で説明する内容や、文中のリンクは以下のブログに記載されています。
https://budougumi0617.github.io/2018/08/19/go-testing2018/

Yoichiro Shimizu

August 21, 2018
Tweet

More Decks by Yoichiro Shimizu

Other Decks in Technology

Transcript

  1. Tour Of Testing
    in 2018
    golang.tokyo #17, AUG 21 2018
    Yoichiro Shimizu
    freee k.k.
    @budougumi0617

    View Slide

  2. ● 清水 陽一郎 @budougumi0617
    ● freee 株式会社
    ○ Backend / Desktop-App Engineer
    ○ Go / Ruby on Rails / .NET
    ● golang.tokyo 運営
    ● Blog
    ○ https://budougumi0617.github.io/
    ● 趣味 : アウトプット駆動学習
    自己紹介

    View Slide

  3. About
    freee
    SECTION ZERO

    View Slide

  4. 4
    スモールビジネスを、
    世界の主役に。
    Mission

    View Slide

  5. 5
    PRODUCTS

    View Slide

  6. Today’s contents
    About freee 00
    Introduction
    Start and Executed test
    How to write test
    01
    02
    03
    04
    Get coverage
    Today’s summary 05

    View Slide

  7. Introduction
    SECTION ONE

    View Slide

  8. How do you studying test in Go?
    ● テストの書き方どう覚える?
    ● 毎リリースそれなりに変更・追加がある
    ○ https://golang.org/doc/go1.7#testing
    ○ https://golang.org/doc/go1.8#testing
    ○ https://golang.org/doc/go1.9#test-helper
    ○ https://golang.org/doc/go1.10#test
    01. How to study Go

    View Slide

  9. Today's goal
    ● go testコマンド /testing.T を改めて確認
    ○ Go1.10までの仕様を復習
    ● ベタープラクティスをまとめる
    ● ひとつでも多く新しい発見をしてもらう
    01. How to study Go

    View Slide

  10. 今回の発表では触れないこと
    ● テストユーティリティ
    ○ testing/{quick, iotest}, net/http/httptest
    ● ベンチマーク
    ○ go test -bench
    ○ testing.B
    ● Examples
    05. Today’s summary

    View Slide

  11. Start and
    Execute test
    SECTION TWO

    View Slide

  12. Basic and naming
    02. Start and Execute test
    package main
    import "testing"
    func Sum(a, b int) int {
    return a + b
    }
    func TestSum(t *testing.T) {
    a, b, want := 1, 2, 3
    if got := Sum(a, b); got != want {
    t.Fatalf("want = %d, got = %d", want, got)
    }
    }
    ● import “testing”
    ● ファイル名はxxx_test.go
    ○ go testのときだけビルド
    ● メソッド名はTestXxx
    ○ Test”s”umは実行されない
    ○ TestFunction
    ○ TestSturct
    ○ TestSturct_Method
    ○ TestSturct_Method_cond
    ● 引数は *testing.T型

    View Slide

  13. Test dataを用意する
    var update = flag.Bool("update", false, "update .golden
    files")
    func TestToJSON(t *testing.T) {
    var b bytes.Buffer
    // Load JSON into b...
    g, err := ioutil.ReadFile(
    filepath.Join("testdata", t.Name()+".golden"))
    if !bytes.Equal(b.Bytes(), g) {
    t.Errorf("Does not match .golden file")
    }
    }
    ● testdataディレクトリに置く
    ○ pkgとして認識されない
    ● *.golden 拡張子
    ○ バイナリやJSONなど
    ● 参考
    ○ cmd/gofmt/testdata/
    02. Start and Execute test

    View Slide

  14. Execute test
    # カレントパッケージのテストを実行する
    $ go test
    # パスで指定されたパッケージのテストを実行する(相対パス指定が可能)
    $ go test ./table
    # サブパッケージを含めてテストを全て実行する
    $ go test ./...
    # 特定のテストコードだけ実行する(テスト対象も含めて指定する)
    $ go test sum_test.go sum.go
    # "Minus"というサブテスト(後述)だけを実行する
    $ go test ./... -v -run /Minus
    # Race conditionの検出
    $ go test ./... -race
    ● go testコマンド
    ● サブパッケージを含めて実行
    ● 引数に応じて実行対象を絞れる
    ● レースコンディションの検出
    02. Start and Execute test

    View Slide

  15. ● Test - Go 1.10 Release Notes
    ○ https://golang.org/doc/go1.10#test
    ● Go 1.10からテストの実行前にgo vetが実行される
    ● go vetを抑制する / go test -vet=off
    go vetを抑制する
    02. Start and Execute test
    $ go test
    github.com/budougumi0617/hogehoge
    ./foo_test.go:10:2: x declared and not used

    View Slide

  16. ● Build and test caching
    ○ https://golang.org/cmd/go/#hdr-Build_and_test_caching
    ● Go 1.10からgo build/testの結果がキャッシュされる
    ○ ベンチマークなど一部のオプション付与時は除外
    ● キャッシュを削除する / go clean -testcache
    ● キャッシュなしで実行 / go test -count=1
    Test Cache
    02. Start and Execute test

    View Slide

  17. Test Cache
    # t/parallel パッケージの結果だけキャッシュされている
    $ go test ./...
    ok github.com/budougumi0617/go-testing/t 0.009s
    ok github.com/budougumi0617/go-testing/t/parallel (cached)
    ok github.com/budougumi0617/go-testing/t/table 0.009s
    # 全ての結果がキャッシュされている(つまり何も実行していない)
    $ go test ./...
    ok github.com/budougumi0617/go-testing/t (cached)
    ok github.com/budougumi0617/go-testing/t/parallel (cached)
    ok github.com/budougumi0617/go-testing/t/table (cached)
    # キャッシュを使わずに全て実行する
    $ go test ./... -count=1
    ok github.com/budougumi0617/go-testing/t 0.011s
    ok github.com/budougumi0617/go-testing/t/parallel 0.011s
    ok github.com/budougumi0617/go-testing/t/table 0.010s
    02. Start and Execute test

    View Slide

  18. ● T.Parallel()メソッドを呼ぶテストケースは並行実行
    ● -parallel nオプションのnで同時実行数を制御
    ● デフォルトの同時実行数は $GOMAXPROCS
    -parallel nオプションで最大並行実行数を制御する
    02. Start and Execute test

    View Slide

  19. ● 実行時の$GOMAXPROCSを変更できる。
    ● オプション引数(list)は数字の羅列
    ● Ex: -cpu 1,2,4
    ○ $GOMAXPROCSを変えながら都合3回実行
    -cpu listで$GOMAXPROCSを変更しながらテストする
    02. Start and Execute test

    View Slide

  20. ● 普段は”-run”だが正式には”-test.run”
    ○ 他のオプションも”-test.xxx”
    ● dlv test -- -test.run TestCreateTempFile
    デバッガ(delve)でテストを実行する
    02. Start and Execute test

    View Slide

  21. ● https://play.golang.org/
    ● 実はそのまま動かせるようになったのは最近
    ● func main()を宣言せずにコードを書けばよい
    playgroundでテストを実行する
    02. Start and Execute test

    View Slide

  22. CI上でテストを実行する
    $ trap "go-junit-report ${TEST_RESULTS}/go-test-report.xml" EXIT
    $ go test -v ./... | tee ${TEST_RESULTS}/go-test.out
    ● JUnit形式に変換する
    ○ https://github.com/jstemmer/go-junit-report
    ● 例えばCircle CIは公式ガイドどおりに
    ○ https://circleci.com/docs/2.0/language-go/
    02. Start and Execute test

    View Slide

  23. How to
    write test
    SECTION THREE

    View Slide

  24. ● 同ディレクトリに複数パッケージは通常許されない
    ● 例外的に”xxx”と”xxx_test”は共存可能
    ● 非公開メソッドなどは参照できなくなるが、循環参照を回避
    できる
    ● export_test.goを使うと…
    テストコードのパッケージ名について
    03. How to write test

    View Slide

  25. ● type testing.TB interface
    ○ testing.T / testing.Bの共通メソッド
    ● Goのテストはこのinterfaceに定義されているメソッドを
    使ってテストの正否を判定する。
    testing.TBインターフェースの主なメソッド
    03. How to write test

    View Slide

  26. ● TB.Error / TB.Errorf
    ○ テストの失敗が記録される
    ○ 後続処理は継続される
    ● TB.Fatal / TB.Fatalf
    ○ 失敗が記録され、テストケースが即時終了する。
    ○ その場で宣言済みのdefer処理も開始される
    testing.TBインターフェースの主なメソッド
    03. How to write test

    View Slide

  27. ● TB.Fail
    ○ TB.Error / TB.Errorf が内部的に呼んでいる
    ● TB.FailNow
    ○ TB.Fatal / TB.Fatalf が内部的に呼んでいる
    testing.TBインターフェースの主なメソッド
    03. How to write test

    View Slide

  28. ● TB.SkipNow / TB.Skip
    ○ テストケースを無効化する
    ● TB.Log / TB.Logf
    ○ -vオプション、あるいはFailしたときにログ出力
    ○ ベンチマーク時は常に出力
    testing.TBインターフェースの主なメソッド
    03. How to write test

    View Slide

  29. ● 検証・前処理などを共通化したいときがしばしばある
    ○ Ex: func ValidateModel( t *testing.T, a, b Model )
    ● サブメソッド内でHelper()メソッドを呼んでおく
    ● サブメソッド内でFailしても呼び出し元の情報が出力
    testing.TB.Helper メソッド (Go 1.9~)
    03. How to write test

    View Slide

  30. testing.TB.Helper メソッド (Go 1.9~)
    03. How to write test
    // in helper_test.go
    func errorf(tb testing.TB, want, got int) {
    tb.Errorf("want = %d, got = %d", want, got)
    }
    // in helper_test.go
    func errorfHelper(tb testing.TB, want, got int) {
    tb.Helper()
    tb.Errorf("want = %d, got = %d", want, got)
    }
    ------
    $ go test
    --- FAIL: TestSum (0.00s)
    helper_test.go:8: want = 4, got = 3
    simple_test.go:16: want = 4, got = 3
    FAIL

    View Slide

  31. Table Driven Test
    func TestFizzBuzz(t *testing.T) {
    tests := []struct {
    name string
    input int
    want string
    }{
    {name: "Num", input: 2, want: "2"},
    {name: "Multi3", input: 3, want: "Fizz"},
    {name: "Multi5", input: 5, want: "Buzz"},
    ...
    }
    for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
    got, err := FizzBuzz(tt.input)
    if got != tt.want {
    t.Errorf("want %d, but %s:", tt.want, got)
    }
    }
    }
    ● テストケースをテーブル化
    ● 再利用性が高い
    ● エンバグ時にデグレしにくい
    ○ テストケースを追加
    ○ リグレッションと同時に実装
    ● “tests”で回して”tt”で受ける
    ● T.Runでサブテストにする
    03. How to write test

    View Slide

  32. ● func (t *T) Run(name string, f func(t *T)) bool
    ● テスト成否を各テストケースで出力できる
    ● T.Parallel()メソッドで各ケースごとに並行実行できる
    ● -test.runオプションでケースを指定して実行
    ● テストケースそれぞれに名前をつけられる
    testing.T.Runでサブテストにする(Go 1.7~)
    03. How to write test

    View Slide

  33. -test.run オプションで個別ケース実行
    $ go test ./... -v
    --- PASS: TestFizBuz (0.00s)
    --- PASS: TestFizBuz/Num (0.00s)
    --- PASS: TestFizBuz/Multi3 (0.00s)
    --- PASS: TestFizBuz/Multi5(0.00s)
    PASS
    $ go test ./... -v -run FizBuz/Multi5
    --- PASS: TestFizBuz (0.00s)
    --- PASS: TestFizBuz/Multi5(0.00s)
    PASS
    ● -runオプションでケースを指定
    ● 「何確かめてるの?」問題
    ○ ドメイン知識がいるテスト
    ケースはケースごとに名前
    が付いてるほうが良い
    ○ Fail時のログも良好
    03. How to write test

    View Slide

  34. エラーは無視しない
    tests := []struct {
    name string
    in int
    want int
    wantError bool
    err error
    }{
    {
    "Basic",
    4, 4,
    false, nil
    },
    {
    "HasError",
    -1, 0,
    true, errors.New("Negative value")
    },
    }
    ● 正常ケースだけ?
    ● エラーが出ることもテストする
    ○ 通常ケース
    ○ エラーケース
    ○ 期待通りのerrorか?
    03. How to write test

    View Slide

  35. エラーは無視しない
    for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
    pt := we.PositiveInt(tt.in)
    got, err := pt.Value()
    if !tt.wantError && err != nil {
    t.Fatalf("want no err, but %#v", err)
    }
    if tt.wantError &&
    !reflect.DeepEqual(err, tt.err) {
    t.Fatalf("want %#v, but %#v", tt.err, err)
    }
    if !tt.wantError && got != tt.want {
    t.Fatalf("want %q, but %q", tt.want, got)
    }
    })
    }
    ● 正常ケースだけ?
    ● エラーが出ることもテストする
    ○ 通常ケース
    ○ エラーケース
    ○ 期待通りのerrorか?
    03. How to write test

    View Slide

  36. ● expected? NO!
    ● actual? NO!
    ● 変数名は短いほうがGo way
    want / got
    03. How to write test

    View Slide

  37. %#vで出力差異をわかりやすく
    spaces := " \n "
    // spaces =
    //
    fmt.Printf("spaces = %v\n", spaces)
    // spaces = " \n "
    fmt.Printf("spaces = %#v\n", spaces)
    ● “%v”より”%#v”
    ○ (がいいときもある)
    03. How to write test

    View Slide

  38. testing.T.Parallelメソッドで並行実行
    for _, tt := range tests {
    tt := tt // Don't forget when parallel test
    t.Run(tt.name, func(t *testing.T) {
    t.Parallel()
    if got := Sum(tt.a, tt.b); got != tt.want {
    t.Fatalf("want = %d, got = %d", tt.want, got)
    }
    })
    }
    ● 通常テストは逐次実行
    ● T.Parallel()が呼ばれているテスト
    ケースのみが並行に実行される。
    ● T.Parallel()を呼ぶときはループ変
    数をローカル変数で補足するのを
    忘れないこと。
    03. How to write test

    View Slide

  39. Get
    coverage
    SECTION FOUR

    View Slide

  40. ● Goは標準機能でテストカバレッジを計測できる
    ● Go1.10からは複数パッケージのカバレッジも簡単に取得でき
    るようになった
    ○ $ go test -cover ./…
    go test -cover
    04. Get coverage

    View Slide

  41. ● go toolでカバレッジの計測結果をHTMLに出力できる
    ● -covermode=count(atomic)オプションをつけると各コード
    行の実行回数も計測できる。
    ○ $ go test ./... -covermode=count -coverprofile=c.out
    ○ $ go tool cover -html=c.out -o coverage.html
    HTMLにカバレッジを保存する
    04. Get coverage

    View Slide

  42. CIと組み合わせる
    04. Get coverage
    - store_artifacts:
    path: /code/test-results
    destination: prefix
    ● Circle CIならばCI中の成果物を保
    存する設定があるので、これで前
    述のHTMLファイルを保存してお
    く。
    $ go test ./... -coverprofile=coverage.txt \
    -covermode=count
    $ bash ● CodecovならCI中にスクリプトを
    呼ぶだけで結果を送信できる

    View Slide

  43. Today’s
    Summary
    SECTION FIVE

    View Slide

  44. Today's goal
    ● go testコマンド /testing.T を改めて確認
    ○ Go1.10までの仕様を復習
    ● ベタープラクティスをまとめる
    ● ひとつでも多く新しい発見をしてもらう
    05. Today’s summary

    View Slide

  45. スモールビジネスを、
    世界の主役に。

    View Slide