11.3 Goでどのようにテストを書くか
プログラムの開発においてテストはとても重要です。どのようにコードの質を保証するか、どのように各関数が実行できることを保証するか、また書いたコードの性能が良いことをどのように保証するかです。我々はユニットテストは主にプログラムの設計や実装のロジックエラーを発見することであると知っています。問題を早期に発見し、問題を特定し解決せしめ、性能をテストするにはプログラム設計上の問題のいくつかを発見することで、オンラインのプログラムがマルチプロセッシングしている状況でも安定を保てるようにします。この節ではこの一連の問題からGo言語でどのようにユニットテストと性能テストを実現するかご紹介します。
Go言語はあらかじめ用意されている軽量なテストフレームワークtesting
とgo test
コマンドを使ってユニットテストと性能テストを実現します。testing
フレームワークとその他の言語でのテストフレームワークはよく似ています。このフレームワークに基いて対応する関数に対してテストを書くことができます。またこのフレームワークに基づいて対応する耐久テストを書くこともできます。ではどのように書くのか一つ一つ見ていくことにしましょう。
どのようにテストを書くか
go test
コマンドでは対応するディレクトリ下の全てのファイルを実行するしかできません。そのため、gotest
というディレクトリを新規に作成することで、すべてのコードとテストコードをこのディレクトリの中に配置することにします。
次にこのディレクトリの下に2つのファイルを新規に作成します:gotest.goとgotest_test.go
gotest.go:このファイルにはパッケージを一つ書きます。中身は除算を行う関数がひとつあります:
package gotest import ( "errors" ) func Division(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("除数は0以外でなければなりません") } return a / b, nil }
gotest_test.go:これはユニットテストのファイルですが、以下の原則を覚えておいてください:
- ファイル名は必ず
_test.go
が最後につくようにしてください。これによってgo test
を実行した時に対応するコードが実行されるようになります。 testing
というパッケージをimportする必要があります。- すべてのテスト関数名は
Test
から始まります。 - テストはソースコードに書かれた順番に実行されます。
- テスト関数
TestXxx()
のパラメータはtesting.T
です。この型を使ってエラーやテストの状態を記録することができます。 - テストフォーマット:
func TestXxx (t *testing.T)
、Xxx
の部分は任意の英数字の組み合わせです。ただし頭文字は小文字[a-z]ではいけません、例えばTestintdiv
というのは間違った関数名です。 関数では
testing.T
のError
、Errorf
、FailNow
、Fatal
、FatalIf
メソッドをコールすることでテストがパスしないことを説明します。Log
メソッドをコールすることでテストの情報を記録します。以下は我々のテストコードです:
package gotest
import (
"testing"
)
func Test_Division_1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function t.Error("除算関数のテストが通りません") // もし予定されたものでなければエラーを発生させます。 } else { t.Log("はじめのテストがパスしました") //記録したい情報を記録します }
}
func Test_Division_2(t *testing.T) {
t.Error("パスしません")
}
プロジェクトのディレクトリにおいて
go test
を実行すると以下のような情報が表示されます:--- FAIL: Test_Division_2 (0.00 seconds)
gotest_test.go:16: パスしません
FAIL exit status 1 FAIL gotest 0.013s この結果が示すようにテストをパスしないのは、2つ目のテスト関数でテストが通らないコード
t.Error
を書いていたからです。では1つ目の関数が実行した状況はどうでしょうか?デフォルトではgo test
を実行するとテストがパスする情報は表示されません。go test -v
というオプションを追加する必要があります。このようにすると以下の情報が表示されます:=== RUN Test_Division_1 --- PASS: Test_Division_1 (0.00 seconds)
gotest_test.go:11: 1つ目のテストがパス
=== RUN Test_Division_2 --- FAIL: Test_Division_2 (0.00 seconds)
gotest_test.go:16: パスしません
FAIL exit status 1 FAIL gotest 0.012s 上の出力はこのテストのプロセスを詳細に表示しています。テスト関数1
Test_Division_1
ではテストが通りました。しかし関数2Test_Division_2
のテストは失敗しました。最後にテストが通らないという結論を得ました。以降ではテスト関数2を以下のようなコードに修正します:func Test_Division_2(t *testing.T) {
if _, e := Division(6, 0); e == nil { //try a unit test on function t.Error("Division did not work as expected.") // 予期したものでなければエラーを発生 } else { t.Log("one test passed.", e) //記録したい情報を記録 }
}
その後go test -v
を実行すると以下のような情報を表示してテストがパスします:=== RUN Test_Division_1 --- PASS: Test_Division_1 (0.00 seconds)
gotest_test.go:11: 1つ目のテストがパス
=== RUN Test_Division_2 --- PASS: Test_Division_2 (0.00 seconds)
gotest_test.go:20: one test passed. 除数は0以外
PASS ok gotest 0.013s
- ファイル名は必ず
どのようにして耐久テストを書くか
耐久テストは関数(メソッド)の性能を測るために用いられます。ここでは再掲しませんが、ユニットテストを書くのと同じようなものです。ただし以下のいくつかに注意しなければなりません:
耐久テストは以下のループの形式で行われなければなりません。この中でXXXは任意の英数字の組み合わせです。ただし、頭文字は小文字ではいけません。
func BenchmarkXXX(b *testing.B) { ... }
go test
はデフォルトで耐久テストの関数を実行しません。もし耐久テストを実行したい場合はオプション-test.bench
を追加します。文法:-test.bench="test_name_regex"
。例えばgo test -test.bench=".*"
はすべての耐久テスト関数をテストすることを表します- 耐久テストではテストが正常に実行されるよう、ループの中において
testing.B.N
を使用することを覚えておいてください - ファイル名はかならず
_test.go
で終わります
以下ではwebbench_test.goという名前の耐久テストファイルを作成します。コードは以下の通り:
package gotest
import (
"testing"
)
func Benchmark_Division(b *testing.B) {
for i := 0; i < b.N; i++ { //use b.N for looping
Division(4, 5)
}
}
func Benchmark_TimeConsumingFunction(b *testing.B) {
b.StopTimer() //调用该函数停止压力测试的时间计数
//做一些初始化的工作,例如读取文件数据,数据库连接之类的,
//这样这些时间不影响我们测试函数本身的性能
b.StartTimer() //重新开始时间
for i := 0; i < b.N; i++ {
Division(4, 5)
}
}
go test -file webbench_test.go -test.bench=".*"
というコマンドを実行すると、以下のような結果が現れます:
PASS
Benchmark_Division 500000000 7.76 ns/op
Benchmark_TimeConsumingFunction 500000000 7.80 ns/op
ok gotest 9.364s
上の結果は我々がどのようなTestXXX
なユニットテスト関数も実行していないことを示しています。表示される結果は耐久テスト関数のみを実行しただけです。第一行にはBenchmark_Division
が500000000回実行され示し、毎回の実行が平均で7.76ミリ秒であったことを示しています。第二行はBenchmark_TimeConsumingFunctin
が500000000回実行され、毎回の平均実行時間が7.80ミリ秒であったことを示しています。最後の1行は全体の実行時間を示しています。
まとめ
上のユニットテストと耐久テストの学習を通じて、testing
パッケージが非常に軽量で、ユニットテストと耐久テストを書くのは非常に簡単であるとわかりました。ビルトインのgo test
コマンドを組み合わせることで、非常に便利にテストを行うことができます。このように我々が毎回コードを修正し終わる度に、go testを実行するだけで簡単に回帰テストを行うことができます。
links
- 目次
- 前へ: GDBを使用したデバッグ
- 次へ: まとめ