0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-03-16

account.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

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言語の初心者向けに、以下のテストコードの文法と動作を解説します。

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)

  • TestNewAccountt *testing.T を引数に取る関数で、go test コマンドによって実行されます。
  • 関数名は Test で始める必要があります。

4. t.Parallel()

このテストを 並列実行 可能にします。並列実行すると、複数のテストを並行して動作させることができます。

5. テストケースのリスト (tests)

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

より詳しい解説はこちら

6. t.Run() によるサブテスト

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

7. NewAccount の実行

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

8. expectError に応じたエラーチェック

エラーが 発生することを期待 する場合:

if tt.expectError {
	if err == nil {
		t.Errorf("[TestCase '%s'] 期待したエラーが発生しませんでした", tt.name)
	}
}

エラーが 発生しないことを期待 する場合:

if !tt.expectError {
	if err != nil {
		t.Errorf("[TestCase '%s'] 予期しないエラー: %v", tt.name, err)
	}
}

9. 期待値と実際の値の比較

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/成功:_アカウントが作成される
    → 並列処理が再開され、テスト実行。

ポイント

  • 並列実行のため、RUNPAUSECONT の流れがある。

--- 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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?