[インデックス 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
の基本的な使い方に関する一般的な情報源