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 full-size slide

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

    View full-size slide

  3. About
    freee
    SECTION ZERO

    View full-size slide

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

    View full-size slide

  5. 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 full-size slide

  6. Introduction
    SECTION ONE

    View full-size slide

  7. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  10. Start and
    Execute test
    SECTION TWO

    View full-size slide

  11. 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 full-size slide

  12. 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 full-size slide

  13. 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 full-size slide

  14. ● 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 full-size slide

  15. ● 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 full-size slide

  16. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  21. CI上でテストを実行する
    $ trap "go-junit-report <${TEST_RESULTS}/go-test.out > ${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 full-size slide

  22. How to
    write test
    SECTION THREE

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  29. 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 full-size slide

  30. 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 full-size slide

  31. ● 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 full-size slide

  32. -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 full-size slide

  33. エラーは無視しない
    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 full-size slide

  34. エラーは無視しない
    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 full-size slide

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

    View full-size slide

  36. %#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 full-size slide

  37. 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 full-size slide

  38. Get
    coverage
    SECTION FOUR

    View full-size slide

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

    View full-size slide

  40. ● 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 full-size slide

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

    View full-size slide

  42. Today’s
    Summary
    SECTION FIVE

    View full-size slide

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

    View full-size slide

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

    View full-size slide