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

[インデックス 15391] ファイルの概要

このコミットは、Go言語の標準ライブラリである testing パッケージにおける Skip 関連の機能の移動とリファクタリングに関するものです。具体的には、テスト (*testing.T) とベンチマーク (*testing.B) の両方で Skip 機能が利用できるように、共通の common 構造体へこれらのメソッドを移動しています。

コミット

commit d31dd089e6aae3c4f8001c409657efa7701bca1c
Author: Dave Cheney <dave@cheney.net>
Date:   Sat Feb 23 11:57:51 2013 +1100

    testing: move Skip into *common

    Move Skip and friends into *common so benchmarks can also be skipped.

    R=golang-dev, gustav.paul, rsc
    CC=golang-dev
    https://golang.org/cl/7379046

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d31dd089e6aae3c4f8001c409657efa7701bca1c

元コミット内容

このコミットの元の内容は、testing パッケージ内の Skip および関連するメソッド(Skipf, SkipNow, skip, Skipped)を、*testing.T 型から common 構造体へと移動することです。これにより、ベンチマーク (*testing.B) もこれらのスキップ機能を利用できるようになります。

変更の背景

Go言語の testing パッケージは、ユニットテストとベンチマークテストの両方を提供します。以前は、t.Skip() のようなテストをスキップする機能は *testing.T (テスト) 型にのみ存在し、*testing.B (ベンチマーク) 型では利用できませんでした。

しかし、ベンチマークにおいても、特定の条件(例えば、特定の環境でのみ実行可能、または時間がかかりすぎるため testing.Short() フラグが設定されている場合など)で実行をスキップしたいというニーズがありました。このコミットは、このような共通の機能を *testing.T*testing.B の両方が埋め込んでいる common 構造体に移すことで、コードの重複を避け、機能の再利用性を高めることを目的としています。これにより、ベンチマークもテストと同様に、実行条件に基づいて柔軟にスキップできるようになります。

前提知識の解説

Go言語の testing パッケージ

Go言語には、標準でテストとベンチマークのための testing パッケージが用意されています。

  • テスト関数: func TestXxx(t *testing.T) の形式で定義され、*testing.T 型の引数 t を受け取ります。t オブジェクトは、テストの失敗を報告したり、ログを出力したり、テストをスキップしたりするためのメソッドを提供します。
  • ベンチマーク関数: func BenchmarkXxx(b *testing.B) の形式で定義され、*testing.B 型の引数 b を受け取ります。b オブジェクトは、ベンチマークの実行回数を制御したり、時間を計測したりするためのメソッドを提供します。
  • t.Skip() / b.Skip(): テストまたはベンチマークの実行を途中で中止し、そのテスト/ベンチマークがスキップされたことを報告する機能です。これは、特定の前提条件が満たされない場合にテスト/ベンチマークを実行しないようにするために使用されます。例えば、外部サービスへの接続が必要なテストで、そのサービスが利用できない場合にスキップするといった用途があります。
  • testing.Short(): go test -short コマンドでテストを実行した際に true を返す関数です。時間のかかるテストや外部リソースに依存するテストを、開発中の短いテスト実行ではスキップするために利用されます。

構造体の埋め込み (Embedding)

Go言語では、ある構造体の中に別の構造体をフィールド名なしで宣言することで、その埋め込まれた構造体のメソッドを外側の構造体が直接利用できるようになります。これを「構造体の埋め込み」と呼びます。これは、他の言語における継承に似た概念ですが、Goでは「コンポジション(合成)」を推奨する設計思想に基づいています。

testing パッケージでは、*testing.T*testing.B の両方が common 構造体を埋め込んでいます。これにより、common 構造体に定義されたメソッドは、*testing.T*testing.B のインスタンスから直接呼び出すことができます。

type common struct {
    // ... 共通のフィールドとメソッド
}

type T struct {
    common // common 構造体を埋め込み
    // ... T 固有のフィールド
}

type B struct {
    common // common 構造体を埋め込み
    // ... B 固有のフィールド
}

このコミットの変更は、この構造体の埋め込みの仕組みを最大限に活用し、Skip 関連の機能を common 構造体に移動することで、*testing.T*testing.B の両方で共通のスキップロジックを共有できるようにしています。

技術的詳細

このコミットの技術的な核心は、testing パッケージの内部構造をリファクタリングし、Skip 関連のロジックを *testing.T から common 構造体へ移動した点にあります。

  1. common 構造体への skipped フィールドの追加: 以前は *testing.T にのみ存在した skipped ブール型フィールドが common 構造体に追加されました。このフィールドは、テストまたはベンチマークがスキップされたかどうかを追跡します。

    type common struct {
        mu      sync.RWMutex // guards output and failed
        output  []byte       // Output generated by test or benchmark.
        failed  bool         // Test or benchmark has failed.
        skipped bool         // Test of benchmark has been skipped. // <-- 追加
    }
    
  2. Skip 関連メソッドの common への移動: Skip, Skipf, SkipNow, skip, Skipped といったメソッドが、*testing.T のレシーバから *common のレシーバを持つように変更されました。

    • Skip(args ...interface{}): ログを出力し、SkipNow() を呼び出します。
    • Skipf(format string, args ...interface{}): フォーマットされたログを出力し、SkipNow() を呼び出します。
    • SkipNow(): テスト/ベンチマークをスキップ済みとしてマークし、現在のゴルーチンの実行を runtime.Goexit() で停止します。これにより、テスト/ベンチマーク関数はそこで終了し、次のテスト/ベンチマークが実行されます。
    • skip(): common 構造体の mu ミューテックスをロックし、skipped フィールドを true に設定します。
    • Skipped() bool: common 構造体の mu ミューテックスを読み込みロックし、skipped フィールドの現在の値を返します。これにより、テスト/ベンチマークがスキップされたかどうかを外部から確認できます。

この変更により、*testing.T*testing.B の両方が common 構造体を埋め込んでいるため、これらのメソッドは t.Skip()b.Skip() のように、それぞれの型から直接呼び出すことが可能になります。これは、Goの構造体の埋め込みとメソッドの昇格(promoted methods)の典型的な利用例です。

コアとなるコードの変更箇所

変更は src/pkg/testing/testing.go ファイルに集中しています。

--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -10,7 +10,7 @@
  // [a-z]) and serves to identify the test routine.
  // These TestXxx routines should be declared within the package they are testing.
  //
-// Tests may be skipped if not applicable like this:
+// Tests and benchmarks may be skipped if not applicable like this:
  //     func TestTimeConsuming(t *testing.T) {
  //         if testing.Short() {
  //             t.Skip("skipping test in short mode.")
@@ -130,9 +130,10 @@ var (
  // common holds the elements common between T and B and
  // captures common methods such as Errorf.
  type common struct {
-	mu     sync.RWMutex // guards output and failed
-	output []byte       // Output generated by test or benchmark.
-	failed bool         // Test or benchmark has failed.
+	mu      sync.RWMutex // guards output and failed
+	output  []byte       // Output generated by test or benchmark.
+	failed  bool         // Test or benchmark has failed.
+	skipped bool         // Test of benchmark has been skipped. // 追加
  
  	start    time.Time // Time test or benchmark started
  	duration time.Duration
@@ -190,7 +191,6 @@ type T struct {
  	common
  	name          string    // Name of test.
  	startParallel chan bool // Parallel tests will wait on this.
-\tskipped       bool      // Test has been skipped. // 削除
  }\n
  // Fail marks the function as having failed but continues execution.\n
@@ -277,6 +277,41 @@ func (c *common) Fatalf(format string, args ...interface{}) {
  	c.FailNow()\n
  }\n
  \n+// Skip is equivalent to Log followed by SkipNow.\n+func (c *common) Skip(args ...interface{}) {\n+\tc.log(fmt.Sprintln(args...))\n+\tc.SkipNow()\n+}\n+\n+// Skipf is equivalent to Logf followed by SkipNow.\n+func (c *common) Skipf(format string, args ...interface{}) {\n+\tc.log(fmt.Sprintf(format, args...))\n+\tc.SkipNow()\n+}\n+\n+// SkipNow marks the test as having been skipped and stops its execution.\n+// Execution will continue at the next test or benchmark. See also FailNow.\n+// SkipNow must be called from the goroutine running the test, not from\n+// other goroutines created during the test. Calling SkipNow does not stop\n+// those other goroutines.\n+func (c *common) SkipNow() {\n+\tc.skip()\n+\truntime.Goexit()\n+}\n+\n+func (c *common) skip() {\n+\tc.mu.Lock()\n+\tdefer c.mu.Unlock()\n+\tc.skipped = true\n+}\n+\n+// Skipped reports whether the test was skipped.\n+func (c *common) Skipped() bool {\n+\tc.mu.RLock()\n+\tdefer c.mu.RUnlock()\n+\treturn c.skipped\n+}\n+\n // Parallel signals that this test is to be run in parallel with (and only with)\n // other parallel tests.\n func (t *T) Parallel() {\n@@ -346,41 +381,6 @@ func (t *T) report() {\n  \t}\n  }\n  \n-// Skip is equivalent to Log followed by SkipNow.\n-func (t *T) Skip(args ...interface{}) {\n-\tt.log(fmt.Sprintln(args...))\n-\tt.SkipNow()\n-}\n-\n-// Skipf is equivalent to Logf followed by SkipNow.\n-func (t *T) Skipf(format string, args ...interface{}) {\n-\tt.log(fmt.Sprintf(format, args...))\n-\tt.SkipNow()\n-}\n-\n-// SkipNow marks the test as having been skipped and stops its execution.\n-// Execution will continue at the next test or benchmark. See also FailNow.\n-// SkipNow must be called from the goroutine running the test, not from\n-// other goroutines created during the test. Calling SkipNow does not stop\n-// those other goroutines.\n-func (t *T) SkipNow() {\n-\tt.skip()\n-\truntime.Goexit()\n-}\n-\n-func (t *T) skip() {\n-\tt.mu.Lock()\n-\tdefer t.mu.Unlock()\n-\tt.skipped = true\n-}\n-\n-// Skipped reports whether the test was skipped.\n-func (t *T) Skipped() bool {\n-\tt.mu.RLock()\n-\tdefer t.mu.RUnlock()\n-\treturn t.skipped\n-}\n-\n func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) {\n  \tok = true\n  \tif len(tests) == 0 && !haveExamples {\n```

## コアとなるコードの解説

このコミットの主要な変更は、`testing` パッケージの `common` 構造体に `skipped` フィールドと、`Skip`, `Skipf`, `SkipNow`, `skip`, `Skipped` メソッドが追加されたことです。

*   **`type common struct { ... skipped bool ... }`**:
    `common` 構造体は、`*testing.T` (テスト) と `*testing.B` (ベンチマーク) の両方が埋め込んでいる基底となる構造体です。ここに `skipped` というブール型のフィールドが追加されました。このフィールドは、現在のテストまたはベンチマークの実行がスキップされた状態にあるかどうかを示すフラグとして機能します。これにより、スキップ状態の管理が `T` と `B` の間で共通化されます。

*   **`func (c *common) Skip(...)`, `func (c *common) Skipf(...)`, `func (c *common) SkipNow()`**:
    これらのメソッドは、以前は `*testing.T` にのみ存在していましたが、`common` 構造体のメソッドとして再定義されました。
    *   `Skip` と `Skipf` は、ログメッセージを出力した後、最終的に `SkipNow()` を呼び出します。
    *   `SkipNow()` は、内部的に `c.skip()` を呼び出して `skipped` フラグを `true` に設定し、その後 `runtime.Goexit()` を呼び出します。`runtime.Goexit()` は、現在のゴルーチン(この場合はテストまたはベンチマークを実行しているゴルーチン)を即座に終了させます。これにより、テストまたはベンチマークの残りのコードは実行されず、次のテストまたはベンチマークの実行に移ります。

*   **`func (c *common) skip()`**:
    このプライベートメソッドは、`common` 構造体の `mu` (ミューテックス) をロックし、`c.skipped` フィールドを `true` に設定します。ミューテックスを使用することで、複数のゴルーチンからのアクセスがあった場合でも、`skipped` フィールドの変更が安全に行われることが保証されます。

*   **`func (c *common) Skipped() bool`**:
    このメソッドは、`common` 構造体の `mu` を読み込みロックし、`c.skipped` フィールドの現在の値を返します。これにより、テストまたはベンチマークがスキップされたかどうかを外部から(例えば、テストのレポート生成時などに)確認することができます。

これらの変更により、`*testing.T` と `*testing.B` の両方が `common` 構造体を埋め込んでいるため、`t.Skip()` や `b.Skip()` のように、それぞれの型から直接 `Skip` 関連のメソッドを呼び出すことが可能になりました。これは、コードの重複を排除し、`testing` パッケージの設計をより一貫性のあるものにするための重要なリファクタリングです。

## 関連リンク

*   Go言語の `testing` パッケージのドキュメント: [https://pkg.go.dev/testing](https://pkg.go.dev/testing)
*   このコミットが参照している Go CL (Change List): [https://golang.org/cl/7379046](https://golang.org/cl/7379046)

## 参考にした情報源リンク

*   Go言語の公式ドキュメント
*   Go言語の `testing` パッケージのソースコード
*   Go言語における構造体の埋め込みに関する一般的な情報源 (例: Go by Example - Struct Embedding)