LoginSignup
9
9

More than 5 years have passed since last update.

Go1.7~ subTestsとTableDrivenTest

Last updated at Posted at 2017-04-13

ReadMeFirst

go1.7からSubTestsが使えるようになったのだけど、
使っていなかったので、調べがてら、その紹介とメモ。
あと、ついでにTableDrivenTestについて書いておく。

Subtestsとは

Subtestsの追加によって、テストに階層を作ることができるようになりました。
Runメソッドを使ってテスト内に小さなテストを書いてみます。

コード例

  • example.go
package main

import "fmt"

func Example(name string) string {
    if name == "" {
        return "blank name"
    }
    return fmt.Sprintf("Hello %s", name)
}
  • example_test.go
func TestExample(t *testing.T) {
    t.Run("case Empty", func(t *testing.T) {
        result := Example("")
        expected := "blank name"
        if expected != result {
            t.Fatalf("failed Test")
        }
    })
    t.Run("case Normal", func(t *testing.T) {
        result := Example("John")
        expected := "Hello John"
        if expected != result {
            t.Fatalf("failed Test")
        }
    })
}

t.Run内でPararell()を使った並行テストも可能です。

実行の仕方

サブテストの含まれるテスト関数を実行すれば良いだけです。
ドキュメントを見る限り、以下のような、オプションによるマッチングによって特定のサブテスト
だけを実行することも可能です。

go test -run Foo/A=  # For top-level tests matching "Foo", run subtests matching "A=".

Fooを含むトップレベルテスト(通常のテスト関数)内でA=を含むサブテストを実行する。

go test -run /A=1    # For all top-level tests, run subtests matching "A=1".

A=1を含むサブテストを実行する。

実行結果

$ go test ./...
--- FAIL: TestExample (0.00s)
    --- FAIL: TestExample/case_Empty (0.00s)
        hoge_test.go:12: failed Test
    --- FAIL: TestExample/case_Normal (0.00s)
        hoge_test.go:19: failed Test

失敗させると。第1引数で指定した名前を出してくれますね。

Benchmarkでも使える。

testing.Bでも使えますので、Benchmarkについても階層化が可能です。
こちらはまぁ便利な気もする。

SubTestの使いどころ

正直、個人的にはこれを待っていたよ!って感動を覚えるものではありませんでした。

というのも、階層が必要なほど複雑なテストが必要な場合はテスト対象コードの仕事が多すぎる気がしますし
1Test1Assertionで育ったこともあり、テスト関数自体を分けてしまうスタンスでしたので。
GoではErrorのような、処理自体は継続する事ができるので、Assertion Roulete のような事はないと
思うのですが、一つのテスト関数に複数を盛り込んで長くするのが、そもそも苦手なのもあり。

うーん。すごく複雑なテストケースが必要な場合もあるのかもしれないし、
見てみたさがあります。求む、エレガントな使用ケース (切実)

TableDrivenTestで大体は十分

上の例程度のケースならgolangの推奨するTableDrivenTestでも可能です。
TableDrivenTestは入力値出力値で構成するデータを用意して
iterateでテストを回す感じがコードも短く済むので、推奨されてます。

go公式から引用ですが。

var flagtests = []struct {
    in  string
    out string
}{
    {"%a", "[%a]"},
    {"%-a", "[%-a]"},
    {"%+a", "[%+a]"},
    {"%#a", "[%#a]"},
    {"% a", "[% a]"},
    {"%0a", "[%0a]"},
    {"%1.2a", "[%1.2a]"},
    {"%-1.2a", "[%-1.2a]"},
    {"%+1.2a", "[%+1.2a]"},
    {"%-+1.2a", "[%+-1.2a]"},
    {"%-+1.2abc", "[%+-1.2a]bc"},
    {"%-1.2abc", "[%-1.2a]bc"},
}

func TestFlagParser(t *testing.T) {
    var flagprinter flagPrinter
    for _, tt := range flagtests {
        s := Sprintf(tt.in, &flagprinter)
        if s != tt.out {
            t.Errorf("Sprintf(%q, &flagprinter) => %q, want %q", tt.in, s, tt.out)
        }
    }
}

要するにphpunitにあったDataProviderをfor文とErrorfで自力でやる感じです。
個人的にはメッセージも各パターン別にある方が親切かな。。。と。

{"input-a","out-a","message at failed"},
{"input-b","out-b","message at failed"},
{"input-c","out-c","message at failed"},
{"input-d","out-d","message at failed"},

とか。

参考

TableDrivenTests
testing package

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9