2017/09/28更新

[Go] テストを実行する(go testの利用)

このエントリーをはてなブックマークに追加      

こんにちは、@yoheiMuneです。
最近Go言語によく触れていますが、新しい言語でもやっぱりテストは書きたい。テストを書くと自信を持ってリファクタリングできたり追加要件を開発できたりするので、僕の中ではプログラミングでの強い支えになっています。そんなテストをGo言語で書く方法をブログに書きたいと思います。
画像


目次




Goでテスト

Go言語では、テストを行う仕組みが提供されていて、それを使うことで簡単にテストができます。

ここでは、以下の実装ファイルに対してテストを作成していきます。
package main

import (
    "math"
)

// 値を2乗する
func Square(x int) int {
    result := math.Pow(float64(x), 2.0)
    return int(result)
}


テストファイルを作成する

Go言語では、テストファイルの命名規則があります。「対象ファイル名_test.go」でテストファイルを作成します。
ここではcalculator.goをテストするので、calculator_test.goとなります。

また、実装ファイルとテストファイルは同一パッケージ内に配置します。
ここではmainパッケージに、どちらのファイルも配置します。

例えば、echoというライブラリだと、以下のような感じで実装ファイルとテストファイルが並んで配置されています。
画像

テストコードを実装する

テストコード実装では、testingパッケージを利用します。calculator.goSquare関数に対するテストは、以下のように実装します。
package main

// testingというパッケージを利用
import (
    "testing"
)

// 入力値(in)と期待値(out)を定義する構造体を作成(mapなどで代替しても良い)
type squareTest struct {
    in, out int
}

// テストをしたい入力値と期待値の一覧を作成
var squareTests = []squareTest {
    squareTest{1, 1},
    squareTest{2, 4},
    squareTest{5, 25},
    squareTest{-2, 4},
}

// テスト関数を作成する
// 関数名は「Test」をプレフィックスにつけ、引数には「t *testing.T」を渡す.
func TestSqure( t *testing.T) {

    // 入力値と期待値を1件ずつテストする.
    for _, st := range squareTests {
        v := Square(st.in)
        if v != st.out {
            // テストNGがあればエラーにする(エラー内容がわかるようにメッセージをちゃんと書く)
            t.Errorf("Square(%d) = %d, want %d.", st.in, v, st.out)
        }
    }
}
テスト用の関数はTestXXXと、Testを関数名のプレフィクスに付与し、引数をt *testing.Tとします。

なお上記のように、入力値と期待値の一覧を作成して、for文でテストしていくのが、Goらしいテストの書き方のようです(個人的には少し慣れないですが、Do as Roman do の思想でまずはやってみようかなと思ってます)。


テストを実行する

テストの実行は、go testにて行います。
$ go test
# PASS
# ok      github.com/yoheiMune/MyGoProject/testtest       0.007s
すべてのテストが通過すれば、上記のようにテストOKト表示されます。

逆にテスト失敗があると、以下のような表示になります。
# テスト失敗の例(入力値=-2, 期待値=-4と間違えた場合)
$ go test
--- FAIL: TestSqure (0.00s)
        main_test.go:22: Square(-2) = -4, want 4.
FAIL
exit status 1
FAIL    github.com/yoheiMune/MyGoProject/testtest       0.008s
と、エラーが表示されます。ここでエラーが分かるように(他の人にも分かるように)、ちゃんとエラーメッセージを書いておくと、メンテナンスしやすいコードになります。


テスト対象を絞る

テスト対象を指定して実行することもできます。
# 指定パッケージのみテスト
$ go test github.com/yoheiMune/MyGoProject/testtest

# カレントディレクトリのみテスト
$ go test -run ""

# 指定ディレクトリ以下を再帰的にテスト
go test -v hoge/...

# 関数名を指定してテスト
$ go test -run Square
と、いろいろな指定で絞り込んでテストが可能です。特定の機能を実装している時とかに便利ですね。



ベンチマークテスト

テスト対象のパフォーマンス計測も、言語ネイティブの機能でテストをすることができます。他の言語には無い特徴で、すごく良いと思います。


ベンチマーク用のテストを追加

関数名はBenchmarkXXXXとし、引数をb *testing.Bにすることでベンチマークテストを実装できます。
// ベンチマーク.
func BenchmarkSquare(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Square(10)
    }
}
b.N回処理を実行して、パフォーマンスを計測します。


ベンチマークテストを実行する

実行するには-benchオプションを付与します。
# ベンチマークテストを実行
$ go test -bench .

BenchmarkSquare-4       50000000                35.3 ns/op
PASS
ok      github.com/yoheiMune/MyGoProject/testtest       1.809s

# ns/op:1オペレーションあたりの処理時間
1回あたりの処理時間が35.3ナノ秒とわかります。単純な関数だけに高速ですね。

さらに-benchmemオプションを付与すると、メモリの使用状況も計測できます。
$ go test -bench . -benchmem
BenchmarkSquare-4       50000000                35.8 ns/op             0 B/op          0 allocs/op
PASS
ok      github.com/yoheiMune/MyGoProject/testtest       1.835s

# ns/op:1オペレーションあたりの処理時間
# B/op:1回あたりのアロケートで確保したメモリ量
# allocs/op:1回あたりのアロケート回数(=メモリ確保した回数)
ここでは処理ごとにメモリ確保していないので高速だとわかります。メモリ確保はコストがかかる処理なので、回数や量は少ない方が良いです。



コンソールへの出力結果をテストする

さらに、go testでは、コンソールへの出力内容もテストすることができます。


コンソール出力のテストの実装方法

関数名はExampleXXXとし、引数なしでテスト関数を定義します。
// Example系.
func ExampleSquare() {
    v := Square(11)
    fmt.Println(v)
    // Output: 121
}
コメントでOutput: 121と書くことで、期待値を定義します。


コンソール出力のテストを実行

テスト実行はgo testで行います。
$ go test
PASS
ok      github.com/yoheiMune/MyGoProject/testtest       0.007s
上記はテスト成功した場合の表示です(いつも通りですね)。

テスト失敗した場合には、以下のような表示になります。
// 「Output: 111」に書き換えてテストを実行
$ go test
--- FAIL: ExampleSquare (0.00s)
got:
121
want:
111
FAIL
exit status 1
FAIL    github.com/yoheiMune/MyGoProject/testtest       0.007s
確かにExample系のテストが行われていることがわかります。



参考資料

Goのテストを学ぶのに、以下の記事を参照しました。ありがとうございます。

Goのテストの基本と開発フロー // Speaker Deck

go標準のbenchmark機能の使い方 - Qiita



最後に

Go言語でもテストがいい感じにかけるのは素敵ですね。ただ、アサート処理が少し面倒で(assertEqualと書きたい)、それに対してtestifyというテストライブラリを使っている場合も多いです(Githubで公開されている他のライブラリのテストでもよく見かけます)。そちらも学んで、そのうちブログに書けたらと思います。

最後になりますが本ブログでは、Go言語・Python・Linux・フロントエンド・Node.js・インフラ・開発環境・Swift・Java・機械学習など雑多に情報発信をしていきます。自分の第2の脳にすべく、情報をブログに貯めています。気になった方は、本ブログのRSSTwitterをフォローして頂けると幸いです ^ ^。

最後までご覧頂きましてありがとうございました!





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。