[インデックス 1342] ファイルの概要
このコミットは、Go言語の標準ライブラリの一部である once パッケージ(または同様の機能を提供するメカニズム)の動作を検証するためのテストを追加するものです。具体的には、once.Do 関数が指定された関数を一度だけ実行することを保証するかどうかを確認するテストケースが src/lib/once_test.go に追加されました。また、この新しいテストファイルがビルドシステムに認識されるように、src/lib/Makefile も更新されています。
コミット
commit 8fb837d96dfee662580b5247072fe0a599fc8ae0
Author: Russ Cox <rsc@golang.org>
Date: Mon Dec 15 08:56:17 2008 -0800
add test for once
R=r
DELTA=31 (31 added, 0 deleted, 0 changed)
OCL=21043
CL=21175
---
src/lib/Makefile | 1 +
src/lib/once_test.go | 31 +++++++++++++++++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/src/lib/Makefile b/src/lib/Makefile
index 32e2918307..7221a9c3e6 100644
--- a/src/lib/Makefile
+++ b/src/lib/Makefile
@@ -37,6 +37,7 @@ FILES=\
TEST=\
bignum\
bufio\
+ once\
sort\
strings\
utf8\
diff --git a/src/lib/once_test.go b/src/lib/once_test.go
new file mode 100644
index 0000000000..21a889dd3c
--- /dev/null
+++ b/src/lib/once_test.go
@@ -0,0 +1,31 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package once
+
+import (
+ "once";
+ "testing";
+)
+
+var ncall int;
+func Call() {
+ ncall++
+}
+
+export func TestOnce(t *testing.T) {
+ ncall = 0;
+ once.Do(&Call);
+ if ncall != 1 {
+ t.Fatalf("once.Do(&Call) didn't Call(): ncall=%d", ncall);
+ }
+ once.Do(&Call);
+ if ncall != 1 {
+ t.Fatalf("second once.Do(&Call) did Call(): ncall=%d", ncall);
+ }
+ once.Do(&Call);
+ if ncall != 1 {
+ t.Fatalf("third once.Do(&Call) did Call(): ncall=%d", ncall);
+ }
+}
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8fb837d96dfee662580b5247072fe0a599fc8ae0
元コミット内容
このコミットの目的は、Go言語の once パッケージ(またはその機能)のテストを追加することです。once は、特定の操作がプログラムの実行中に一度だけ行われることを保証するためのメカニズムです。このコミットでは、once.Do 関数が実際に一度だけ関数を実行するかどうかを検証するテストケースが実装されています。
変更の背景
ソフトウェア開発において、特定の初期化処理やリソースのロードなど、一度だけ実行されるべき操作は頻繁に発生します。例えば、データベース接続の確立、設定ファイルの読み込み、シングルトンインスタンスの生成などが挙げられます。これらの操作が複数回実行されると、リソースの無駄遣い、競合状態、または不正なプログラム状態を引き起こす可能性があります。
Go言語の sync.Once (このコミット時点では once パッケージとして存在していた可能性が高い) は、このような「一度だけ実行」という要件を満たすための強力なプリミティブを提供します。この機能が正しく動作することを保証するためには、堅牢なテストが不可欠です。特に、並行処理環境下での正確な動作は非常に重要であり、テストによってその保証がなされます。
このコミットは、once 機能の信頼性を確保し、将来的な変更やリファクタリングが行われた際にもその動作が損なわれないようにするための、品質保証の一環として追加されました。
前提知識の解説
Go言語の sync.Once (または once パッケージ)
Go言語の sync パッケージには、Once という型が提供されています。これは、特定の関数が一度だけ実行されることを保証するためのメカニズムです。Once 型のインスタンスは、Do メソッドを持ち、このメソッドに渡された関数は、Do が最初に呼び出されたときに一度だけ実行されます。それ以降の Do の呼び出しでは、同じ関数が再度実行されることはありません。これは、複数のGoroutineから同時に Do が呼び出された場合でも保証されます。
主な用途:
- 遅延初期化 (Lazy Initialization): 必要になったときに初めてリソースを初期化する。
- シングルトンパターンの実装: アプリケーション全体で唯一のインスタンスを保証する。
- グローバルな設定の読み込み: アプリケーション起動時に一度だけ設定を読み込む。
Go言語のテストフレームワーク (testing パッケージ)
Go言語には、標準で testing パッケージが提供されており、ユニットテストやベンチマークテストを簡単に記述できます。
- テストファイルの命名規則: テストファイルは通常、テスト対象のソースファイルと同じディレクトリに配置され、ファイル名の末尾に
_test.goを付けます(例:my_package.goのテストはmy_package_test.go)。 - テスト関数の命名規則: テスト関数は
Testで始まり、その後に続く名前の最初の文字は大文字である必要があります(例:func TestMyFunction(t *testing.T))。 *testing.T: テスト関数には*testing.T型の引数が渡されます。これを通じて、テストの失敗を報告したり、ログを出力したりできます。t.Fatalf(format string, args ...interface{}): テストを失敗としてマークし、メッセージを出力してテストを即座に終了します。t.Errorf(format string, args ...interface{}): テストを失敗としてマークし、メッセージを出力しますが、テストは続行されます。
go testコマンド: テストはgo testコマンドを実行することで実行されます。
Makefile と Goプロジェクトにおける役割
Makefile は、make コマンドによって実行されるビルド自動化ツールです。プロジェクトのコンパイル、テスト実行、クリーンアップなどのタスクを定義するために使用されます。
Goプロジェクトでは、go build や go test といったコマンドが強力であるため、大規模な Makefile が必要ない場合も多いですが、以下のような目的で利用されることがあります。
- 複数のパッケージのビルド: 複数のGoパッケージを一度にビルドする。
- テストの実行: 特定のテストや、すべてのテストを実行する。
- 依存関係の管理: 外部ツールやライブラリのダウンロード・インストール。
- コード生成: プロトコルバッファやモックなどのコード生成。
- デプロイメントスクリプト: ビルド成果物のデプロイ。
- CI/CDパイプラインとの連携: CI/CDシステムが
makeコマンドを呼び出すことで、ビルドプロセスを標準化する。
このコミットでは、Makefile が TEST 変数に新しいテストスイート (once) を追加することで、go test コマンドが once_test.go を自動的に発見し、実行できるようにしています。
技術的詳細
このコミットの技術的詳細は、once.Do の動作保証と、それを検証するためのテスト設計に集約されます。
once.Do の動作原理 (テストの観点から)
once.Do は、内部的にミューテックスとブールフラグを使用して、渡された関数が一度だけ実行されることを保証します。
Doが初めて呼び出されると、内部のフラグがチェックされます。- フラグが「未実行」の場合、ミューテックスをロックし、関数を実行します。
- 関数が正常に完了した後、フラグを「実行済み」に設定し、ミューテックスをアンロックします。
- それ以降の
Doの呼び出しでは、フラグが「実行済み」であるため、ミューテックスのロックや関数の実行は行われず、すぐにリターンします。
このテストでは、この「一度だけ実行される」という特性を ncall というグローバル変数を使って検証しています。
テストコードの構造と意図
src/lib/once_test.go に追加されたテストコードは、以下の要素で構成されています。
package once: テスト対象のパッケージがonceであることを示します。import ("once"; "testing";):onceパッケージと、Goの標準テストパッケージtestingをインポートしています。var ncall int;:Call関数が呼び出された回数をカウントするためのグローバル変数です。テストのたびにリセットされます。func Call() { ncall++ }:once.Doに渡される関数です。この関数が実行されるたびにncallをインクリメントします。export func TestOnce(t *testing.T): メインのテスト関数です。ncall = 0;: テスト開始時にncallを0にリセットし、テストの独立性を保ちます。once.Do(&Call);:Call関数をonce.Doに渡して実行します。if ncall != 1 { t.Fatalf(...) }:once.Doが初めて呼び出された後、ncallが正確に1であることを検証します。もし1でなければ、once.Doが関数を呼び出さなかったか、複数回呼び出したかのいずれかであり、テストは失敗します。once.Do(&Call);(2回目): 再度once.Doを呼び出します。if ncall != 1 { t.Fatalf(...) }: 2回目の呼び出し後もncallが1のままであることを検証します。これはonce.Doが関数を二度と実行しないことを保証します。もし1でなければ、once.Doが複数回関数を実行してしまったことになり、テストは失敗します。once.Do(&Call);(3回目): 3回目の呼び出しも同様に検証します。
このテストは、once.Do が「一度だけ」という約束を厳密に守っていることを、シンプルなカウンター変数を使って効果的に証明しています。
Makefile の変更
src/lib/Makefile の変更は非常にシンプルです。
--- a/src/lib/Makefile
+++ b/src/lib/Makefile
@@ -37,6 +37,7 @@ FILES=\
TEST=\
bignum\
bufio\
+ once\
sort\
strings\
utf8\
TEST 変数に once が追加されています。これは、make test のようなコマンドが実行された際に、once パッケージのテスト(つまり once_test.go)も実行対象に含めることを意味します。これにより、新しいテストがビルドプロセスに統合され、自動的に実行されるようになります。
コアとなるコードの変更箇所
src/lib/Makefile
--- a/src/lib/Makefile
+++ b/src/lib/Makefile
@@ -37,6 +37,7 @@ FILES=\
TEST=\
bignum\
bufio\
+ once\
sort\
strings\
utf8\
src/lib/once_test.go
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package once
import (
"once";
"testing";
)
var ncall int;
func Call() {
ncall++
}
export func TestOnce(t *testing.T) {
ncall = 0;
once.Do(&Call);
if ncall != 1 {
t.Fatalf("once.Do(&Call) didn't Call(): ncall=%d", ncall);
}
once.Do(&Call);
if ncall != 1 {
t.Fatalf("second once.Do(&Call) did Call(): ncall=%d", ncall);
}
once.Do(&Call);
if ncall != 1 {
t.Fatalf("third once.Do(&Call) did Call(): ncall=%d", ncall);
}
}
コアとなるコードの解説
src/lib/Makefile の変更
Makefile の TEST 変数に once を追加することで、ビルドシステムが once パッケージのテストを認識し、make test コマンドなどで実行されるテストスイートに含めるようになります。これは、新しいテストがプロジェクトの自動テストプロセスに組み込まれるための標準的な手順です。
src/lib/once_test.go の新規追加
このファイルは、once パッケージの Do 関数の動作を検証するためのユニットテストを定義しています。
package once: このテストファイルがonceパッケージの一部であることを示します。import ("once"; "testing";):onceパッケージ自体と、Goの標準テストライブラリであるtestingパッケージをインポートしています。var ncall int;:Call関数が呼び出された回数を追跡するためのグローバル変数です。テストのたびにこの変数をリセットすることで、各テストケースが独立して実行されることを保証します。func Call() { ncall++ }: この関数はonce.Doに渡されるコールバック関数です。この関数が実行されるたびにncallの値が1増加します。once.Doの目的は、この関数が一度だけ実行されることを保証することなので、ncallが最終的に1になることを期待します。export func TestOnce(t *testing.T): これが実際のテスト関数です。Goのテストフレームワークは、Testで始まり*testing.Tを引数にとる関数を自動的にテストとして認識します。ncall = 0;: テストの開始時にncallを0に初期化します。これにより、以前のテスト実行の影響を受けないようにします。once.Do(&Call);:once.Do関数を初めて呼び出し、Call関数へのポインタを渡します。once.Doの仕様により、この呼び出しでCall関数が実行され、ncallが1になるはずです。if ncall != 1 { t.Fatalf("once.Do(&Call) didn't Call(): ncall=%d", ncall); }:once.Doの最初の呼び出し後、ncallが1であることを検証します。もし1でなければ、once.Doが期待通りにCall関数を呼び出さなかったことを意味し、テストは失敗します。t.Fatalfはエラーメッセージを出力し、テストを即座に終了させます。once.Do(&Call);(2回目): 再度once.Doを呼び出します。once.Doの仕様により、Call関数はすでに一度実行されているため、この呼び出しではCall関数は実行されないはずです。したがって、ncallの値は1のまま変わらないはずです。if ncall != 1 { t.Fatalf("second once.Do(&Call) did Call(): ncall=%d", ncall); }: 2回目の呼び出し後もncallが1のままであることを検証します。もし1でなければ、once.Doが複数回Call関数を実行してしまったことを意味し、テストは失敗します。once.Do(&Call);(3回目): 3回目の呼び出しも同様に、ncallが1のままであることを検証します。
このテストは、once.Do が「一度だけ」という重要な保証を、複数回の呼び出しに対しても維持していることを、シンプルかつ効果的に確認しています。
関連リンク
- Go言語
sync.Onceの公式ドキュメント: https://pkg.go.dev/sync#Once - Go言語
testingパッケージの公式ドキュメント: https://pkg.go.dev/testing - Go言語のテストに関する公式ブログ記事 (A Tour of Go Testing): https://go.dev/blog/testing
参考にした情報源リンク
- Go言語
sync.Onceの概念と使用例に関する一般的な情報源 - Go言語のテストの書き方に関する一般的な情報源
Makefileの基本的な使い方に関する一般的な情報源