go testの裏側では何をしているのか？Goのtestingパッケージの中身を覗いてみよう！

## :qiitan:はじめに
Goのユニットテストはとても簡単に書くことができ、指定された書式に沿ってコードを書けばテストができます。

```go
func TestSample_Sample(t *testing.T) {
  if got := Sample(); got != "ok" {
    t.Errorf("Sample() = %v, want %v", got, "ok")
  }
}
```
筆者は業務・プライベートでテストを書いているうちに、

- 「Goのテストって簡単に書けるけど、裏側ではどのように処理されているんだろう？」
- 「いつもテスト関数に渡している`*testing.T`ってなんだろう？」

ということが気になりました。

実際の処理は内部に隠されており、調べても「Goのテストの書き方」の情報は見つかるのですが、内部構造の解説をしている情報にはたどり着くことができませんでした。
じゃあ実際にtestingパッケージをコードリーディングしてみよう、と思い立って、調べたことをまとめた内容が本記事になります。

コードは公開されているため、誰でも読むことはできます。ただ、自分なりにわかりやすくまとめ、公開することで他の方に役に立てばいいなという動機で執筆しました。

:::note warn
本記事では説明をしやすくするため、意図的に省いた処理があります（並行実行時の挙動など）。全てを網羅したい方は、実際のコードを読んでいただけると幸いです。
:::

### 想定読者
- Goの構造体型に関して基本的な理解がある方
- Goのテストを書いているが、testingパッケージのコードを読んだことがない方

### 環境
:::note info
本記事のコードリーディングは、Go1.17のtestingパッケージのソースコードを対象としています。
解説する内容は別バージョンのソースコードの中身とは異なる場合がありますのでご注意ください。
:::

## 用語の定義
- **メインテスト**...トップレベルに置かれたテスト関数
- **サブテスト**...メインテストに含まれるテスト関数

```go
// メインテスト
func Test_Sample(t *testing.T) {
	tests := []struct {
		name   string
		want   int
	}{
		{
			name:   "trueの場合、1を返す",
			want:   1,
		},
		{
			name:   "falseの場合、2を返す",
			want:   2,
		},
	}
	for _, tt := range tests {
		// サブテスト
		t.Run(tt.name, func(t *testing.T) {
			if got := Sample(); got != tt.want {
				t.Errorf("Sample() = %v, want %v", got, tt.want)
			}
		})
	}
}
```

## `go test`実行時の裏側

### テスト実行を支える型たち
ここでは、**テスト実行時に活躍する主要な`testing`パッケージの型を紹介します。**
主に関わるのは以下に紹介する2つの型です。

### `testing.T`型
`testing`パッケージには`testing.T`型が存在します。**`testing.T`型はテスト時の設定を保持・管理する構造体**で、テスト関数に必ず引数として渡している型です。
この`testing.T`型を構成する各フィールドについて、さらっと確認します。

```go:testing.go
type T struct {
	common
	isParallel bool // t.Parallel()を呼び出しているとtrue
	isEnvSet   bool // t.SetEnv()を呼び出しているとtrue
	context    *testContext
}
```
> 出典: https://cs.opensource.google/go/go/+/refs/tags/go1.17.5:src/testing/testing.go;l=674

`isParallel`・`isEnvSet`というフィールドがあります。
Goのテストは、テストを並行実行できる機能(`t.Parallel`)や、テストケース内だけで使用できる環境変数を設定できる機能(`t.Setenv`)があります。上記フィールドには、これらの機能のOn/Offの状態が記録されます。

`context`フィールドは、`testing.*testContext`型になっています。本記事では詳しく述べませんが、この型は並行実行時の状態を管理する構造体になります。

最後に、`testing.common`型が埋め込まれています。この構造体については次項で詳しく説明します。


### `testing.common`型
**`testing.common`型は、テストの成功/失敗状態の保持や、`t.Errorf()`メソッドなどの自分達が良く使うユーティリティメソッドを持っています。**前述した`t.Parallel()`や`t.SetEnv()`も、`testing.T`型に埋め込まれた`testing.common`型のメソッドになります。

また、この型はベンチマークで用いる`benchmark.B`と共通のメソッドセットを持つ`testing.TB`インタフェースを満たしています。

```go:testing.go_TB
type TB interface {
	Error(args ...interface{})
	Errorf(format string, args ...interface{})
	Fail()
	Fatal(args ...interface{})
	Fatalf(format string, args ...interface{})
	...
}
```

この構造体は多くのフィールドを持っているため、ここでは著者が主要と判断したフィールドをざっと紹介します。他フィールドの詳細は、[testing.go#390](https://cs.opensource.google/go/go/+/refs/tags/go1.17.5:src/testing/testing.go;l=390)を参照してください。

#### テストの状態に関するフィールド
```go:testing.go
type common struct {
  ...
  name string // テスト名
  ran bool
  failed bool // テストが失敗したかどうか
  finished bool // テストが完了したかどうか
  signal chan bool // テストが終了したかどうかの通知
  ...
}
```

#### 親テスト・サブテストに関するフィールド
```go:testing.go
type common struct {
  ...
  parent *common // 親テストの情報
  sub []*T // 並行実行されるサブテスト情報が入ったキュー
  ...
}
```
テストを構成する型について把握したところで、次章からは実際の処理の流れを追っていきます。
## テスト実行処理の詳細
**この章では、`go test`コマンドを実行したときに、テストコード実行から終了するまでの流れについて扱います。**

まず、処理の流れを大まかにイメージできるよう、図に表してみました。
![Screenshot 2021-12-19 14.10.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/2c4a3863-972c-9afb-5513-cde6d0e53034.png)

ざっくりと流れを把握したところで、コードリーディングを進めていきましょう。

まず、テスト実行時の`testing`パッケージでの処理は、大きく以下のように分けられます。

- **`(t *T).Run()`の呼び出し 〜 テストケース実行までの処理**

- **テストケース実行 〜 テスト終了までの処理**

ここからは、上記のそれぞれの処理についてGoのコードを見ていきます。

### 1. テスト準備〜テストケース実行まで
まずは、 テスト準備〜テストケース実行までの処理を追ってみましょう。

メインテスト・サブテスト共に、テストの実行時には**`T.Run()`**というメソッドを呼び出します。
メインテストは`testing`パッケージの`runtests()`メソッド経由で、サブテストは自分達がソースコード内で直接`t.Run()`を呼び出して実行する形になります。

ここで行われる処理は、以下の２つです。
#### 行われる処理
- 必要な情報をセットする
- テスト実行ゴルーチンを生成する

それぞれについてみていきます。
#### 必要な情報のセット
テストケース実行の前に、テストに必要な情報を`T.common`・`T.context`フィールドにセットする処理が行われます。

```go
func (t *T) Run(name string, f func(t *T)) bool {
...

  t = &T{
      common: common{
        barrier: make(chan bool), // 並行実行時に使われるフィールド初期化
        signal:  make(chan bool, 1),
        name:    testName,
        parent:  &t.common,
        level:   t.level + 1,
        creator: pc[:n], // サブテストの実行時点でのスタックトレースが入るフィールドの初期化
        chatty:  t.chatty,
      },
      context: t.context, // 並行実行時の状態保持に使われるフィールド初期化
	}

...
```
ここで、主要なフィールドをいくつか説明していきます。

- **`name`**
    - `name`フィールドはわかりやすく、名の通りテスト名が入ります。このフィールドは、テスト終了後のログ出力時に参照されます。
    - メインテスト実行時には`テスト関数名`が、サブテスト実行時には`[親テスト関数名+ "/" + 引数に渡した文字列]`が入ります。
    	- 【最初の例の場合】メインテスト：`"Test_Sample"`・サブテスト：`"Test_Sample/trueの場合、1を返す"`

- **`signal`**
    - `signal`フィールドにはテストが終了したかどうかの通知のチャネル型の初期化が行われます。

- **`level`**
    - `level`フィールドには、実行するテストの深さが入ります。上記例だと、メインテスト実行時には`1`、サブテスト実行時には`2`が入ります。

- **`parent`**
    - `parent`フィールドには、親テストの`t.common`のポインタが渡されます。
    - つまり、メインテスト実行時には初期状態のため`nil`になりますが、サブテスト実行時にはメインテストの情報が入った`t.common`のポインタがセットされます。

#### テスト実行ゴルーチンの生成
実際にテストを走らせる処理は、`tRunner()`という関数で行われます。Goのテストは並行実行に対応しており、ここでゴルーチン`tRunner`を生成しています。

```go:testing.go
  ...
	go tRunner(t, f)
	if !<-t.signal {
    ...
		runtime.Goexit()
	}
	return !t.failed
}
```

### 2. テストケース実行〜テスト終了まで
ここからは、テストケース実行〜テスト終了までを追っていきます。
ここでの主役は、ゴルーチン`tRunner`です。

```go:testing.go
func tRunner(t *T, fn func(t *T)) {
	t.runner = callerName(0)
  ...
	defer func() { // ②
    ...
	}()
	defer func() { // ③
		...
	}()

	t.start = time.Now()
	t.raceErrors = -race.Errors()

	fn(t) // ①

	t.mu.Lock()
	t.finished = true
	t.mu.Unlock()
}
```
この`tRunner`には、**2つの`defer`文と、テスト実行処理**が書かれています。
`defer`文は関数がreturnされるまで処理を遅らせるので、まずは①の部分から順に追っていきましょう。

#### （1） テストケース実行

```go:testging.go_tRunner()内
func tRunner(t *T, fn func(t *T)) {
...
	fn(t) // テストケース実行

        // テスト終了フラグ立てる
	t.mu.Lock()
	t.finished = true
	t.mu.Unlock()
}
```
`fn(t)`で、`t.Runner()`に引数として渡した関数が実行されます。
この関数はテストコード記述時にメインテスト関数もしくはサブテストの`t.Run()`の第二引数で渡した、`*testing.T`を引数としてテストケースを実行する無名関数です。
つまり、**自分達が書いたテストケースごとのテスト処理は、この時点で行われます。**

```go:例
    t.Run(tt.name,
    // ↓これ！
    func(t *testing.T) {
      if got := Sample(); got != tt.want {
        t.Errorf("Sample() = %v, want %v", got, tt.want)
      }
    })
```
<details><summary>【寄り道】失敗処理についてもっと詳しく</summary><div>
普段、期待する動作に一致しなかった場合は`t.Errorf`などを使ってテストを失敗させます。

このような`t.Errorxx``系のメソッドは、`common.failed`フィールドを`false`にすることで、テストに失敗した状態を保持しています。
また、このテストが何かのサブテストだった場合は、親の`common.failed`も`false`にセットしています。

```go:testing.go
func (c *common) Errorf(format string, args ...interface{}) {
	c.log(fmt.Sprintf(format, args...))
	c.Fail() // ここで失敗フラグを立てている！
}
```
</div></details>


最後に、テストケースを実行した後は成功・失敗関係なくテストが終了したことを示す`t.finished`フィールドを`true`にしています。
その際、他のゴルーチンの影響を受けないよう`RWMutex.Lock`で排他制御が行われています。

#### （2） テスト結果ログ出力・panic処理・サブテスト並行実行
ここでは、`tRunner()`1つ目の`defer`文で実行される処理を追っていきます。
この部分はかなり多くの処理が記述されているので、大まかに以下のように分類しました。

- **panic処理**
- **テスト結果のログ出力処理**
- **サブテスト並行実行処理**

##### 2.1.panic処理
テストケース実行時にpanicが発生した場合は、プログラムがクラッシュして不整合は起こらないように、`tRunner`内で安全にpanic処理が行われます。

```go:testing.go_tRunner内
err := recover()
```
`err`変数にpanicの詳細が書き込まれ、この値を用いてpanicした理由をログ出力します。そして、親も含めた`common.failed`フィールドが全てfalseになり、テストに失敗している状態になります。
ここで、テストコード内に`t.Cleanup()`で後処理が記述されていた場合、設定された後処理を全て実行します。
最終的には`dopanic()`が呼び出され、その関数内でpanicさせます。

##### 2.2.ログ出力処理
続いてはログ出力処理です。テスト実行時、以下のようなログ出力をよく見ます。

```
--- PASS: Test_Sample (0.00s)
    --- PASS: Test_Sample/trueの場合1、を返す (0.00s)
    --- PASS: Test_Sample/falseの場合、2を返す (0.00s)
PASS
ok      sample/     0.666s
```
これらのログ出力は`t.report()`メソッド内において行われています。ソースコードを読むと、`t.common`の`name`フィールドの値が用いられているのがわかります。

```go:testing.go
func (t *T) report() {
	if t.parent == nil {
		return
	}
	dstr := fmtDuration(t.duration)
	format := "--- %s: %s (%s)\n"
	if t.Failed() {
		t.flushToParent(t.name, format, "FAIL", t.name, dstr)
	} else if t.chatty != nil {
		if t.Skipped() {
			t.flushToParent(t.name, format, "SKIP", t.name, dstr)
		} else {
			t.flushToParent(t.name, format, "PASS", t.name, dstr)
		}
	}
}

```
##### 2.3.サブテスト並行実行処理
テストコード内で`t.Parallel()`を呼び出していた場合は、テストケースが並行実行されます。
`t.Parallel`を呼び出しているテスト関数がどのような順序で実行されるのかは、他の方が書かれた以下の記事で詳しく解説されています。

https://engineering.mercari.com/blog/entry/how_to_use_t_parallel/

ここでは、`testing.testContext`型にある内部的な並行実行を行うためのカウントを操作し、一斉に並行実行させます。
このとき、サブテストに`t.Cleanup()`が呼ばれていた場合は、後述する後処理も行われます。

#### （3） テストケース実行後の後処理
Goのテストでは、テストコード内で`t.Cleanup()`を呼び出し、引数に関数を渡すことによって、テストケース実行終了時に任意の後処理を実行させることができます。

`t.Runner()`では最後に`common.runCleanup()`を呼び出して、このテストケース実行の後処理が行われ、これで全ての処理が終了となります。

```go:testing.go
	defer func() {
		if len(t.sub) == 0 {
			t.runCleanup(normalPanic)
		}
	}()
```

## おわりに
ここまで、`go test`コマンドを実行してから、実際にテストが実行されるまでの処理の流れを追ってきました。
筆者はGoで書き始めて半年ほどですが、Goのコードはシンプルで読みやすく、スムーズに標準パッケージを読み進めることができました。

本記事を読むことで、普段何気なく使っている`testing`パッケージが裏側でどのような処理をしているか、その一片でも把握できるお手伝いができたのであれば幸いです。

間違い等、内容に不備がありましたらご指摘ください。