こんにちは。よっしーです(^^)
今日は、Golangにおけるテストコードの作成についてご紹介します。
前提条件
前回の記事では、複数の人の挨拶に対応できるように修正しました。
今回の記事では、Hello関数のテストを追加しています。開発中にコードをテストすることで、変更に伴って混入したバグを発見することができます。
下記の記事が実行できる環境にあることを前提にしています。
利用方法
作業ディレクトリを作成して、移動する。
mkdir 10_learn-golang-test
cd 10_learn-golang-test
asdfコマンドで使用するGolangのバージョンを指定します。
すでにGolangが実行できる状態にある方は、このコマンドをスキップしても問題ありません。
asdf local golang 1.20.5
前回の記事のソースコードを作業ディレクトリにコピーします。
cp -r ../09_learn-golang-change-parameter/greetings .
cp -r ../09_learn-golang-change-parameter/hello .
Goに組み込まれたユニットテストのサポートにより、実行しながらテストを行うことが容易になります。具体的には、命名規則、Goのテストパッケージ、go testコマンドを使用することで、テストを書いて実行することができます。
greetingsディレクトリに、greetings_test.goというファイルを作成します。
touch greetings/greetings_test.go
ファイル名の最後を_test.goとすることで、go testコマンドに「このファイルにはテスト関数が含まれている」と伝えることができます。
greetings_test.goに以下のコードを貼り付け、ファイルを保存してください。
package greetings
import (
"testing"
"regexp"
)
// TestHelloName calls greetings.Hello with a name, checking
// for a valid return value.
func TestHelloName(t *testing.T) {
name := "Gladys"
want := regexp.MustCompile(`\b`+name+`\b`)
msg, err := Hello("Gladys")
if !want.MatchString(msg) || err != nil {
t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want)
}
}
// TestHelloEmpty calls greetings.Hello with an empty string,
// checking for an error.
func TestHelloEmpty(t *testing.T) {
msg, err := Hello("")
if msg != "" || err == nil {
t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
}
}
greetingsディレクトリのコマンドラインで、go testコマンドを実行し、テストを実行します。
cd greetings
go test
go testコマンドは、テストファイル(名前は_test.goで終わる)内のテスト関数(名前はTestで始まる)を実行する。vフラグを追加すると、すべてのテストとその結果を一覧表示する冗長な出力を得ることができます。
下記のようにテストは合格するはずです。
% go test
PASS
ok example.com/greetings 0.226s
% go test -v
=== RUN TestHelloName
--- PASS: TestHelloName (0.00s)
=== RUN TestHelloEmpty
--- PASS: TestHelloEmpty (0.00s)
PASS
ok example.com/greetings 0.343s
greetings.Hello関数を壊して、失敗したテストを表示してみます。
TestHelloName テスト関数は、Hello 関数のパラメータとして指定した名前の戻り値をチェックします。失敗したテスト結果を表示するには、greetings.Hello関数を変更して、名前を含まないようにします。
greetings/greetings.goで、Hello関数の代わりに次のコードを貼り付けます。name引数が誤って削除されたかのように、関数が返す値を変更することに注意してください。
// Hello returns a greeting for the named person.
func Hello(name string) (string, error) {
// If no name was given, return an error with a message.
if name == "" {
return name, errors.New("empty name")
}
// Create a message using a random format.
// message := fmt.Sprintf(randomFormat(), name)
message := fmt.Sprint(randomFormat())
return message, nil
}
greetingsディレクトリのコマンドラインで、go testを実行し、テストを実行します。
go test
今回は、-vフラグを付けずにgo testを実行してください。失敗したテストだけの結果が出力されるので、テストがたくさんある場合に便利です。
下記のようにTestHelloNameのテストは失敗するはずですが、TestHelloEmptyのテストはまだパスしています。
% go test
--- FAIL: TestHelloName (0.00s)
greetings_test.go:15: Hello("Gladys") = "Great to see you, %v!", <nil>, want match for `\bGladys\b`, nil
FAIL
exit status 1
FAIL example.com/greetings 0.273s
解説
greetings_test.go
package greetings
import (
"testing"
"regexp"
)
// TestHelloName calls greetings.Hello with a name, checking
// for a valid return value.
func TestHelloName(t *testing.T) {
name := "Gladys"
want := regexp.MustCompile(`\b`+name+`\b`)
msg, err := Hello("Gladys")
if !want.MatchString(msg) || err != nil {
t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want)
}
}
// TestHelloEmpty calls greetings.Hello with an empty string,
// checking for an error.
func TestHelloEmpty(t *testing.T) {
msg, err := Hello("")
if msg != "" || err == nil {
t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
}
}
このコードは、greetings
パッケージ内のHello
関数をテストするためのテストケースを含んでいます。
まず、import
文でtesting
とregexp
パッケージをインポートしています。
TestHelloName
関数は、name
変数に”Gladys”を設定し、regexp.MustCompile
関数を使用してname
を含む正規表現を作成しています。次に、Hello
関数を"Gladys"
の引数で呼び出し、戻り値のmsg
とerr
を取得します。その後、msg
がwant
の正規表現にマッチしない場合やerr
がnil
でない場合には、テストを失敗させるためにt.Fatalf
関数を呼び出しています。
TestHelloEmpty
関数は、空の文字列を引数としてHello
関数を呼び出し、戻り値のmsg
とerr
を取得します。その後、msg
が空ではない場合やerr
がnil
である場合には、テストを失敗させるためにt.Fatalf
関数を呼び出しています。
これらのテスト関数は、testing.T
型の引数を受け取り、テストが失敗した場合にエラーメッセージを出力します。テストが成功すると、何も表示されずに終了します。
これにより、greetings
パッケージ内のHello
関数が期待どおりに動作するかどうかを確認するための自動化されたテストが提供されます。
testingパッケージについて
testing
パッケージは、Go言語の標準ライブラリの一部であり、ユニットテストを作成するためのフレームワークとツールを提供します。このパッケージには、テストの実行、アサーション、エラーハンドリングなどの機能が含まれています。
testing
パッケージの主な要素と機能には以下のようなものがあります:
testing.T
型: テストケースを表すための型です。テスト関数はこの型の引数を受け取り、テストの実行やアサーション、エラーメッセージの出力などを行います。- テスト関数:
func TestXxx(t *testing.T)
という形式の関数です。関数名はTest
で始まり、テスト対象のコードや機能を示す名前になります。テスト関数内でアサーションを行い、結果を判定します。 t.Run
関数: サブテストを作成するための関数です。大規模なテストスイートを作成し、複数のテストケースをまとめて管理する際に使用します。- アサーション関数: テスト結果を検証するための関数です。
t.Fatalf
、t.Errorf
、t.Logf
など、さまざまなレベルのエラーメッセージを出力する関数が用意されています。これらの関数はテストを失敗させることがあります。 - ベンチマークテスト:
testing.B
型を利用して、コードの性能測定やベンチマークテストを行うこともできます。func BenchmarkXxx(b *testing.B)
という形式の関数を作成します。 - テストカバレッジ:
go test
コマンドと組み合わせて、テストカバレッジのレポートを取得することができます。これにより、どれだけのコードがテストされているかを確認することができます。
テストの実行は、コマンドラインからgo test
コマンドを実行するか、開発環境のテストランナーから実行します。テスト結果は出力され、成功したテストと失敗したテストが表示されます。
testing
パッケージは、Goの標準ライブラリの一部であるため、追加の依存関係やセットアップは必要ありません。パッケージはテスト対象のコードと同じパッケージに含めることで、テスト対象のコードに対して簡単にアクセスできます。
テストの作成や実行に関する詳細な情報は、Goの公式ドキュメントの「testing」パッケージのセクションを参照してください。そこには、テストの作成方法やアサーションの使い方、ベンチマークテストの実行方法などが詳細に記載されています。
また、testing
パッケージにはさまざまなヘルパー関数やユーティリティが用意されており、より複雑なテストケースやモックの作成、テストの並列実行などをサポートしています。さらに、サードパーティのライブラリやフレームワークも利用することで、さらに高度なテスト機能を追加できます。
Go言語のtesting
パッケージは、堅牢なユニットテストを作成するための強力なツールセットを提供しています。テスト駆動開発(TDD)や継続的インテグレーション(CI)などのソフトウェア開発プラクティスと組み合わせて使用することで、品質管理と信頼性の向上に役立ちます。
おわりに
この記事では、テストコードを追加する方法についてご紹介しました。テストコードを追加して、変更に強いアプリやバグの早期発見を通じて品質の高いアプリの作成をしていきましょう。
本記事で使用したコードは下記のリポジトリにあります。
何か質問や相談があれば、遠慮なくコメントしてください。また、エンジニア案件についても、いつでも相談にのっていますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント