Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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 buildgo test といったコマンドが強力であるため、大規模な Makefile が必要ない場合も多いですが、以下のような目的で利用されることがあります。

  • 複数のパッケージのビルド: 複数のGoパッケージを一度にビルドする。
  • テストの実行: 特定のテストや、すべてのテストを実行する。
  • 依存関係の管理: 外部ツールやライブラリのダウンロード・インストール。
  • コード生成: プロトコルバッファやモックなどのコード生成。
  • デプロイメントスクリプト: ビルド成果物のデプロイ。
  • CI/CDパイプラインとの連携: CI/CDシステムが make コマンドを呼び出すことで、ビルドプロセスを標準化する。

このコミットでは、MakefileTEST 変数に新しいテストスイート (once) を追加することで、go test コマンドが once_test.go を自動的に発見し、実行できるようにしています。

技術的詳細

このコミットの技術的詳細は、once.Do の動作保証と、それを検証するためのテスト設計に集約されます。

once.Do の動作原理 (テストの観点から)

once.Do は、内部的にミューテックスとブールフラグを使用して、渡された関数が一度だけ実行されることを保証します。

  1. Do が初めて呼び出されると、内部のフラグがチェックされます。
  2. フラグが「未実行」の場合、ミューテックスをロックし、関数を実行します。
  3. 関数が正常に完了した後、フラグを「実行済み」に設定し、ミューテックスをアンロックします。
  4. それ以降の 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 の変更

MakefileTEST 変数に 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 を引数にとる関数を自動的にテストとして認識します。
    1. ncall = 0;: テストの開始時に ncall を0に初期化します。これにより、以前のテスト実行の影響を受けないようにします。
    2. once.Do(&Call);: once.Do 関数を初めて呼び出し、Call 関数へのポインタを渡します。once.Do の仕様により、この呼び出しで Call 関数が実行され、ncall が1になるはずです。
    3. if ncall != 1 { t.Fatalf("once.Do(&Call) didn't Call(): ncall=%d", ncall); }: once.Do の最初の呼び出し後、ncall が1であることを検証します。もし1でなければ、once.Do が期待通りに Call 関数を呼び出さなかったことを意味し、テストは失敗します。t.Fatalf はエラーメッセージを出力し、テストを即座に終了させます。
    4. once.Do(&Call); (2回目): 再度 once.Do を呼び出します。once.Do の仕様により、Call 関数はすでに一度実行されているため、この呼び出しでは Call 関数は実行されないはずです。したがって、ncall の値は1のまま変わらないはずです。
    5. if ncall != 1 { t.Fatalf("second once.Do(&Call) did Call(): ncall=%d", ncall); }: 2回目の呼び出し後も ncall が1のままであることを検証します。もし1でなければ、once.Do が複数回 Call 関数を実行してしまったことを意味し、テストは失敗します。
    6. once.Do(&Call); (3回目): 3回目の呼び出しも同様に、ncall が1のままであることを検証します。

このテストは、once.Do が「一度だけ」という重要な保証を、複数回の呼び出しに対しても維持していることを、シンプルかつ効果的に確認しています。

関連リンク

参考にした情報源リンク

  • Go言語 sync.Once の概念と使用例に関する一般的な情報源
  • Go言語のテストの書き方に関する一般的な情報源
  • Makefile の基本的な使い方に関する一般的な情報源