【備忘録】【文法読解】goテストコード文法解読

# account.go

```go
package domain

import "errors"

// エラー定義
var (
	ErrAccountNameRequired  = errors.New("account name is required")
	ErrAccountEmailRequired = errors.New("account email is required")
)

// アカウントID
type AccountID string

// アカウントリポジトaリのインターフェース
type AccountRepository interface {
	Create(Account) (Account, error) // アカウントを作成
	FindAll() ([]Account, error)     // すべてのアカウントを取得
}

// アカウントエンティティ
type Account struct {
	id    AccountID
	name  string
	email string
}

// コンストラクタ
func NewAccount(id AccountID, name, email string) (Account, error) {
	if name == "" {
		return Account{}, ErrAccountNameRequired
	}
	if email == "" {
		return Account{}, ErrAccountEmailRequired
	}
	return Account{id: id, name: name, email: email}, nil
}

// Getter: アカウントIDを取得
func (a Account) ID() AccountID {
	return a.id
}

// Getter: 名前を取得
func (a Account) Name() string {
	return a.name
}

// Getter: メールアドレスを取得
func (a Account) Email() string {
	return a.email
}
```

# account_test.go

```go
package domain

import (
	"testing"
)

// TestNewAccount は NewAccount の動作をテストする
func TestNewAccount(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name        string
		id          AccountID
		accountName string
		email       string
		expectError bool
	}{
		{
			name:        "成功: アカウントが作成される",
			id:          "account1",
			accountName: "Taro",
			email:       "taro@example.com",
			expectError: false,
		},
		{
			name:        "エラー: アカウント名が空",
			id:          "account2",
			accountName: "",
			email:       "hanako@example.com",
			expectError: true,
		},
		{
			name:        "エラー: メールアドレスが空",
			id:          "account3",
			accountName: "Jiro",
			email:       "",
			expectError: true,
		},
	}

	for _, tt := range tests {
		tt := tt // 並列テスト用に変数をキャプチャ
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			account, err := NewAccount(tt.id, tt.accountName, tt.email)

			if tt.expectError {
				// エラーが発生することを期待する場合
				if err == nil {
					t.Errorf("[TestCase '%s'] 期待したエラーが発生しませんでした", tt.name)
				}
			} else {
				// エラーが発生しないことを期待する場合
				if err != nil {
					t.Errorf("[TestCase '%s'] 予期しないエラー: %v", tt.name, err)
				}
				if account.Name() != tt.accountName {
					t.Errorf("[TestCase '%s'] アカウント名が異なります (期待: %s, 実際: %s)", tt.name, tt.accountName, account.Name())
				}
				if account.Email() != tt.email {
					t.Errorf("[TestCase '%s'] メールアドレスが異なります (期待: %s, 実際: %s)", tt.name, tt.email, account.Email())
				}
			}
		})
	}
}
```

# Goのテストコード解説: `TestNewAccount`

この記事では、Go言語の初心者向けに、以下のテストコードの文法と動作を解説します。

```go
package domain

import (
	"testing"
)

// TestNewAccount は NewAccount の動作をテストする
func TestNewAccount(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name        string
		id          AccountID
		accountName string
		email       string
		expectError bool
	}{
		{
			name:        "成功: アカウントが作成される",
			id:          "account1",
			accountName: "Taro",
			email:       "taro@example.com",
			expectError: false,
		},
		{
			name:        "エラー: アカウント名が空",
			id:          "account2",
			accountName: "",
			email:       "hanako@example.com",
			expectError: true,
		},
		{
			name:        "エラー: メールアドレスが空",
			id:          "account3",
			accountName: "Jiro",
			email:       "",
			expectError: true,
		},
	}

	for _, tt := range tests {
		tt := tt // 並列テスト用に変数をキャプチャ
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			account, err := NewAccount(tt.id, tt.accountName, tt.email)

			if tt.expectError {
				if err == nil {
					t.Errorf("[TestCase '%s'] 期待したエラーが発生しませんでした", tt.name)
				}
			} else {
				if err != nil {
					t.Errorf("[TestCase '%s'] 予期しないエラー: %v", tt.name, err)
				}
				if account.Name() != tt.accountName {
					t.Errorf("[TestCase '%s'] アカウント名が異なります (期待: %s, 実際: %s)", tt.name, tt.accountName, account.Name())
				}
				if account.Email() != tt.email {
					t.Errorf("[TestCase '%s'] メールアドレスが異なります (期待: %s, 実際: %s)", tt.name, tt.email, account.Email())
				}
			}
		})
	}
}
```

## 1. `package domain`
このコードは `domain` というパッケージに属しています。Goでは、パッケージごとにコードを管理します。

## 2. `import "testing"`
Goの標準ライブラリに含まれる `testing` パッケージをインポートして、テスト機能を利用します。

## 3. `func TestNewAccount(t *testing.T)`
- `TestNewAccount` は `t *testing.T` を引数に取る関数で、`go test` コマンドによって実行されます。
- 関数名は `Test` で始める必要があります。

## 4. `t.Parallel()`
このテストを **並列実行** 可能にします。並列実行すると、複数のテストを並行して動作させることができます。

## 5. テストケースのリスト (`tests`)
```go
	tests := []struct {
		name        string
		id          AccountID
		accountName string
		email       string
		expectError bool
	}{ ... }
```
- `tests` という **スライス（リスト）** を作成し、3つのテストケースを定義しています。
- 各テストケースは、
  - `name`（テストの名前）
  - `id`（アカウントID）
  - `accountName`（アカウント名）
  - `email`（メールアドレス）
  - `expectError`（エラーが発生するか）
  を持つ構造体です。

より詳しい解説はこちら

https://qiita.com/koichi2426/items/1260d3a5ef05aeaac5cc


## 6. `t.Run()` によるサブテスト
```go
		t.Run(tt.name, func(t *testing.T) {
```
- `t.Run()` を使うと、各テストケースを **個別に実行** できます。
- `tt.name` を渡すことで、テスト結果に名前が表示されます。

## 7. `NewAccount` の実行
```go
account, err := NewAccount(tt.id, tt.accountName, tt.email)
```
- `NewAccount` 関数を実行し、`account`（生成されたアカウント）と `err`（エラー）を取得します。

## 8. `expectError` に応じたエラーチェック
エラーが **発生することを期待** する場合：
```go
if tt.expectError {
	if err == nil {
		t.Errorf("[TestCase '%s'] 期待したエラーが発生しませんでした", tt.name)
	}
}
```
エラーが **発生しないことを期待** する場合：
```go
if !tt.expectError {
	if err != nil {
		t.Errorf("[TestCase '%s'] 予期しないエラー: %v", tt.name, err)
	}
}
```

## 9. 期待値と実際の値の比較
```go
if account.Name() != tt.accountName {
	t.Errorf("[TestCase '%s'] アカウント名が異なります (期待: %s, 実際: %s)", tt.name, tt.accountName, account.Name())
}
```
- `account.Name()` の値が `tt.accountName` と一致するか確認
- もし一致しなければ `t.Errorf` でエラーメッセージを出力

# **出力**
```
koichi@koxo JobAtlas_go % go test -v ./domain -run TestNewAccount

=== RUN   TestNewAccount
=== PAUSE TestNewAccount
=== CONT  TestNewAccount
=== RUN   TestNewAccount/成功:_アカウントが作成される
=== PAUSE TestNewAccount/成功:_アカウントが作成される
=== RUN   TestNewAccount/エラー:_アカウント名が空
=== PAUSE TestNewAccount/エラー:_アカウント名が空
=== RUN   TestNewAccount/エラー:_メールアドレスが空
=== PAUSE TestNewAccount/エラー:_メールアドレスが空
=== CONT  TestNewAccount/成功:_アカウントが作成される
=== CONT  TestNewAccount/エラー:_メールアドレスが空
=== CONT  TestNewAccount/エラー:_アカウント名が空
--- PASS: TestNewAccount (0.00s)
    --- PASS: TestNewAccount/成功:_アカウントが作成される (0.00s)
    --- PASS: TestNewAccount/エラー:_メールアドレスが空 (0.00s)
    --- PASS: TestNewAccount/エラー:_アカウント名が空 (0.00s)
PASS
ok      JobAtlas_go/domain      (cached)
```
## **Goのテスト出力の読み方解説**
テストを実行した際の以下の出力結果について、どのように読み解けば良いのかを解説します。

---

### **テスト実行コマンド**
```
koichi@koxo JobAtlas_go % go test -v ./domain -run TestNewAccount
```
- `go test` : Goのテストを実行するコマンド
- `-v` : 詳細なログを表示（`verbose` モード）
- `./domain` : `domain` パッケージのテストを実行
- `-run TestNewAccount` : `TestNewAccount` に一致するテスト関数のみを実行

---

### **テスト出力の各行の意味**
```
=== RUN   TestNewAccount
=== PAUSE TestNewAccount
=== CONT  TestNewAccount
```
- `=== RUN   TestNewAccount`  
  → `TestNewAccount` の実行が開始されたことを示します。
- `=== PAUSE TestNewAccount`  
  → `t.Parallel()` の影響で、一旦テストの実行が保留されたことを示します。
- `=== CONT  TestNewAccount`  
  → `TestNewAccount` の実行が再開されたことを示します。

---

```
=== RUN   TestNewAccount/成功:_アカウントが作成される
=== PAUSE TestNewAccount/成功:_アカウントが作成される
=== RUN   TestNewAccount/エラー:_アカウント名が空
=== PAUSE TestNewAccount/エラー:_アカウント名が空
=== RUN   TestNewAccount/エラー:_メールアドレスが空
=== PAUSE TestNewAccount/エラー:_メールアドレスが空
=== CONT  TestNewAccount/成功:_アカウントが作成される
=== CONT  TestNewAccount/エラー:_メールアドレスが空
=== CONT  TestNewAccount/エラー:_アカウント名が空
```
- `=== RUN   TestNewAccount/成功:_アカウントが作成される`  
  → `TestNewAccount` のサブテスト `"成功: アカウントが作成される"` が開始。
- `=== PAUSE TestNewAccount/成功:_アカウントが作成される`  
  → `t.Parallel()` によって、一旦テストが保留。
- `=== CONT  TestNewAccount/成功:_アカウントが作成される`  
  → 並列処理が再開され、テスト実行。

**ポイント**
- 並列実行のため、`RUN` → `PAUSE` → `CONT` の流れがある。

---

```
--- PASS: TestNewAccount (0.00s)
    --- PASS: TestNewAccount/成功:_アカウントが作成される (0.00s)
    --- PASS: TestNewAccount/エラー:_メールアドレスが空 (0.00s)
    --- PASS: TestNewAccount/エラー:_アカウント名が空 (0.00s)
PASS
ok      JobAtlas_go/domain      (cached)
```
- `--- PASS: TestNewAccount (0.00s)`  
  → `TestNewAccount` のテストが成功 (`PASS`) し、実行時間は `0.00s` 。
- `--- PASS: TestNewAccount/成功:_アカウントが作成される (0.00s)`  
  → `"成功: アカウントが作成される"` のサブテストが成功 (`PASS`) し、実行時間は `0.00s` 。
- `--- PASS: TestNewAccount/エラー:_メールアドレスが空 (0.00s)`  
  → `"エラー: メールアドレスが空"` のサブテストが成功 (`PASS`) し、実行時間は `0.00s` 。
- `--- PASS: TestNewAccount/エラー:_アカウント名が空 (0.00s)`  
  → `"エラー: アカウント名が空"` のサブテストが成功 (`PASS`) し、実行時間は `0.00s` 。
- `PASS`  
  → `TestNewAccount` のすべてのサブテストが成功したため、全体として `PASS` 。
- `ok      JobAtlas_go/domain      (cached)`  
  → `domain` パッケージのテストが問題なく成功 (`ok`) し、前回のキャッシュが利用された (`cached`) 。

---

### **テストが失敗した場合の例**
例えば、`NewAccount("", "", "test@example.com")` で `AccountID` が空でも通ってしまうバグがあると仮定します。

その場合、以下のような出力になります。

```
=== RUN   TestNewAccount/エラー:_アカウントIDが空
    account_test.go:45: [TestCase 'エラー:_アカウントIDが空'] 期待したエラーが発生しませんでした
--- FAIL: TestNewAccount (0.00s)
    --- FAIL: TestNewAccount/エラー:_アカウントIDが空 (0.00s)
FAIL
exit status 1
FAIL    JobAtlas_go/domain      0.005s
```

- `account_test.go:45: [TestCase 'エラー:_アカウントIDが空'] 期待したエラーが発生しませんでした`  
  → `NewAccount("", "", "test@example.com")` に対してエラーが発生しなかったため、テストが失敗 (`FAIL`) 。
- `--- FAIL: TestNewAccount (0.00s)`  
  → `TestNewAccount` の実行全体が失敗 (`FAIL`) 。
- `exit status 1`  
  → プログラムがエラーを伴って終了。
- `FAIL    JobAtlas_go/domain      0.005s`  
  → `domain` パッケージのテストが失敗 (`FAIL`) し、実行時間は `0.005s` 。

---