🏑
Go言語でオニオンアーキテクチャ:モック作成に関数フィールドを使おう
オニオンアーキテクチャは、アプリケーションを層(レイヤー)に分けて依存関係を整理する設計手法です。特に、ビジネスロジックを中心に据え、その周辺に外部との接続層を配置することで、テスト可能性や保守性を向上させる利点があります。本記事では、Go言語でこのアーキテクチャを採用する際に、リポジトリをモックする方法として「関数フィールド」を活用するアイデアを紹介します。
オニオンアーキテクチャの基本構造
オニオンアーキテクチャの構造は以下の通りです:
- ドメイン層: ビジネスロジックやエンティティを含む。
- ユースケース層: アプリケーションのユースケースを実現するロジック。
- インフラ層: データベースやAPIなど外部リソースとの接続を扱う。
このアーキテクチャでは、インフラ層がドメイン層に依存しない設計が推奨されます。これにより、ドメインロジックを中心とした変更が容易になり、テスト性も向上します。
リポジトリのモック化と関数フィールドの活用
テスト時には、インフラ層に実際のデータベース接続を使用するのではなく、リポジトリのモックを使用します。以下のようなシナリオが考えられます:
- 正常に処理した場合
- 不正なパラメータによるエラー発生時
- データベース接続エラーが発生した場合
Go言語では、構造体に関数フィールドを持たせることで、これらのシナリオに対応したモックを簡単に作成できます。
サンプルコード
以下に、リポジトリのモックを関数フィールドで実現する方法を示します。
// ドメインのエンティティ
package domain
type User struct {
ID int
Name string
}
// リポジトリインターフェース
package domain
import "example.com/project/domain"
type UserRepository interface {
GetUserByID(id int) (*domain.User, error)
SaveUser(user *domain.User) error
}
// モックリポジトリ
package infra
import (
"example.com/project/domain"
)
type MockUserRepository struct {
GetUserByIDFunc func(id int) (*domain.User, error)
SaveUserFunc func(user *domain.User) error
}
func (m *MockUserRepository) GetUserByID(id int) (*domain.User, error) {
return m.GetUserByIDFunc(id)
}
func (m *MockUserRepository) SaveUser(user *domain.User) error {
return m.SaveUserFunc(user)
}
テストコード例
以下に、関数フィールドを活用したテストコードの例を示します。
package usecase
import (
"errors"
"testing"
"example.com/project/domain"
"example.com/project/usecase"
)
func TestGetUserByID(t *testing.T) {
t.Run("指定したIDのユーザーが存在している場合", func(t *testing.T) {
mockRepo := &infra.MockUserRepository{
GetUserByIDFunc: func(id int) (*domain.User, error) {
return &domain.User{ID: 1, Name: "Alice"}, nil
},
}
uc := usecase.NewUserUseCase(mockRepo)
user, err := uc.GetUser(1)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if user.Name != "Alice" {
t.Errorf("expected user name to be Alice, got %s", user.Name)
}
})
t.Run("指定したIDのユーザーが存在しない場合", func(t *testing.T) {
mockRepo := &infra.MockUserRepository{
GetUserByIDFunc: func(id int) (*domain.User, error) {
return nil, errors.New("user not found")
},
}
uc := usecase.NewUserUseCase(mockRepo)
_, err := uc.GetUser(1)
if err == nil {
t.Error("expected error, got nil")
}
if err.Error() != "user not found" {
t.Errorf("expected error message to be 'user not found', got %s", err.Error())
}
})
t.Run("予期せぬエラーが発生した場合", func(t *testing.T) {
mockRepo := &infra.MockUserRepository{
GetUserByIDFunc: func(id int) (*domain.User, error) {
return nil, errors.New("something went wrong")
},
}
uc := usecase.NewUserUseCase(mockRepo)
_, err := uc.GetUser(1)
if err == nil {
t.Error("expected error, got nil")
}
if err.Error() != "something went wrong" {
t.Errorf("expected error message to be 'something went wrong', got %s", err.Error())
}
})
}
関数フィールドを使う利点
- 簡潔さ: 必要な部分のみをモック化できる。
- 柔軟性: 複数のテストケースで異なる動作を設定可能。
- テストの明確化: テストケースごとにモックの挙動を個別に定義することで、テスト対象の動作が分かりやすくなる。
まとめ
Go言語でオニオンアーキテクチャを採用する際、リポジトリのモックを関数フィールドで実装することで、テスト効率を劇的に向上させることができます。柔軟で簡潔なモックを活用して、より堅牢で保守性の高いアプリケーションを目指しましょう。
Discussion