You are currently viewing Go言語の単体テスト入門

Go言語の単体テスト入門

最近はバイブコーディングばっかり行っていて、中身の理解が進まないまま、でもテストでは動作に問題ないプログラムが出来上がっております。(個人開発)

本当にこれでいいのかという思いもあるため、最近は
・AIにコードを作ってもらう
・中身をレビューしながら既存コードにマージ
みたいなことを行っております

さて、最近作成したプログラムで「単体テストするから、項目を箇条書きで出力して」とAIにお願いしたら、ご丁寧にGoのテストコードが出力された

「そうか、単体テストはもう手動で行う時代では無いのか」と思いつつ、いままでの経験上、ほとんどテストコードを書いたことが無いので、Go言語で単体テストをどうやって記述するのか、改めて学んでみることにした

2段階認証の認証コードを生成するロジックを例に、内容を紐解く

まずはAIが生成したテストコードは以下になります

package service

import (
	"testing" // Goの標準テストパッケージ
)

// テスト関数は必ず "Test" で始まる
// 引数は *testing.T を受け取る
func TestGenerateVerificationCode(t *testing.T) {
	// テスト実行
	code, err := GenerateVerificationCode()
	
	// ========================================
	// アサーション(検証)
	// ========================================
	
	// 1. エラーがないことを確認
	if err != nil {
		t.Errorf("エラーが発生しました: %v", err)
		return // エラーがあったらここで終了
	}
	
	// 2. コードが6桁であることを確認
	if len(code) != 6 {
		t.Errorf("コードの長さが不正です。期待値: 6, 実際: %d", len(code))
	}
	
	// 3. コードが数字のみであることを確認
	for _, char := range code {
		if char < '0' || char > '9' {
			t.Errorf("数字以外の文字が含まれています: %s", code)
			break
		}
	}
	
	// ここまで来たらテスト成功!
	t.Logf("生成されたコード: %s", code) // ログ出力(-v オプションで表示)
}

テストコードファイル名

Goのテストコードはテスト対象のファイル名 + _test.goとなる。大文字小文字の区別あり
これは必須条件で、Goでは_test.goのファイルを特別に扱う
・_test.goは本番バイナリに含まれない
・_test.goはgo buildでビルドされない
・_test.goはgo testでのみコンパイルされる
という特徴がある

引数「t *testing.T」について

tオブジェクトはテスト制御用オブジェクト
go testコマンドの実行で自動で設定される

以下のメソッドが呼び出された場合、テストは失敗となる

    t.Error("失敗")      
    t.Errorf("失敗")     
    t.Fatal("失敗")      
    t.Fatalf("失敗")     
    t.Fail()            
    t.FailNow()         

逆にテスト関数が終了するまで上記関数が呼ばれなければ、テストは成功扱い
単純に情報を出力するだけであれば、Log(またはLogf)関数を利用する

テストの実行方法

テストの実行にはgo testコマンドを利用する

現在のフォルダ配下にあるテストすべてを実行
go test ./...

指定のパッケージのテストを実行
go test ./internal/service/ -v

指定のテストのみ実行
go test ./internal/service/ -v -run TestGenerateVerificationCode

基本的なアサーション

基本的にはGo標準のlen関数や<>などの比較演算子を利用してアサーション(検証)を実行する

// エラーチェック
if err != nil {
    t.Errorf("エラーが発生: %v", err)
}

// 値の比較
if actual != expected {
    t.Errorf("期待値: %v, 実際: %v", expected, actual)
}

// 文字列の長さチェック
if len(str) != 6 {
    t.Errorf("長さが不正: 期待 6, 実際 %d", len(str))
}

// nilチェック
if value == nil {
    t.Error("値がnilです")
}

// 範囲チェック
if count < 0 || count > 100 {
    t.Errorf("範囲外: %d", count)
}

以上の知識を得たうえで最初のテストコードを眺めてみると、実行していることは至ってシンプルで
・認証コード生成関数を実行
・実行結果を検証:エラーが発生していないか
・実行結果を検証:コードが6桁か
・実行結果を検証:すべて数字か
の検証を行っているだけの内容となる

今後は積極的に使っていきたいテストコード

以前勤務していた会社ではJava(JTest)を利用して単体テストコードを作成していたけど、モックの使い方などが独特で馴染めなかった記憶があります

Goに関しても、DBやキャッシュと行った部分にはモック的なものを利用する事になりそうですが、それらについては、また次の課題として取り組んでいこうと思います。

GitHubにCIの仕組みが導入されているので、単体テストコードの整備ができればプルリクのたびに自動でテストを実行し、危険なコードはマージを行わないようなワークフローが構築できますね

※記事中のプログラムには生成AIを利用したコードが含まれます

コメントを残す