Slide 1

Slide 1 text

テストについて Golang編 2022/03/24 山田悠之介

Slide 2

Slide 2 text

テスト テストは大事。 自動テストで担保できる部分に関しては 自動テストをしなくてはいけない。 業務ではフロントエンドのテストについて勉強していたが、 バックエンドのテストが気になったので調べてみた。 2

Slide 3

Slide 3 text

目次 1. 普通のテスト 2. API のテスト 3. DB のテスト 3

Slide 4

Slide 4 text

interface と struct Go では interface を struct で実装することで オブジェクト指向のコードが書ける。 4

Slide 5

Slide 5 text

type Repository interface { FindAllTodos() ([]Todo, error) } type repository struct { db *sql.DB } func (r repository) FindAllTodos() ([]Todo, error) { ... } // 返り値が Repository にできている func NewRepository(db *sql.DB) Repository { return repository{db} } 5

Slide 6

Slide 6 text

interface と struct ただしクラスベースのオブジェクト指向ではない。 struct は継承ができず、委譲を強制する言語設計になっている。 6

Slide 7

Slide 7 text

DB アクセスを Repository へ移譲する type UseCase interface { GetTodos() ([]Todo, error) } type useCase struct { repoitory Repository } func NewTodoUseCase(repoitory Repository) UseCase { return useCase{ repoitory, } } func (u useCase) GetTodos() ([]Todo, error) { return u.repoitory.FindAllTodos() } 7

Slide 8

Slide 8 text

interface のモック Go には interface のモックを生成する仕組みが公式である (gomock)。 DI などと合わせて使えばテストでは委譲先をモックし、 今テストしたい struct だけをテストすることができる。 mockgen -source=repository.go -destination=mock/mock_repository.go 8

Slide 9

Slide 9 text

mock を使ったテスト func TestGetTodos(t *testing.T) { mockRet := []model.Todo{{ID: 1, Content: "Todo1"}} // mock 生成 ctrl := gomock.NewController(t) defer ctrl.Finish() mock := mock_repository.NewMockRepository(ctrl) // 期待する振る舞いを設定 mock.EXPECT().FindAllTodos().Return(mockRet, nil) // 注入 usecase := NewUseCase(mock) actual, _ := usecase.GetTodos() assert.Equal(t, mockRet, actual) } 9

Slide 10

Slide 10 text

API のテスト 10

Slide 11

Slide 11 text

DB のテスト Go に限った話ではないがいくつか方法がある mock を使う方法 比較的簡単だが、DB を使ったときに本当に動くかは分からない。 ORM を使う場合は生成される SQL を再現しないといけないかも。 軽量な DB を使う(割愛) 実際の DB を使う方法 mock の逆で、初期化と後処理の方法を考える必要がある。 11

Slide 12

Slide 12 text

go-txdb 後処理はテスト中の DB 操作をトランザクションにして、 テストケースが完了したらロールバックすれば良い。 go-txdb というライブラリを使うとコネクションを Close するだけで Open してからの操作をロールバック してくれる。 12

Slide 13

Slide 13 text

func TestCreateTodo(t *testing.T) { txdb.Register("find_all_todos", "mysql", "dsn") db, _ := sql.Open("find_all_todos", "dsn") defer db.Close() // 最後に閉じてロールバック repo := NewRepository(db) actual, _ := repo.CreateTodo("todo4") assert.Equal(t, &model.Todo{ID: 4, Content: "todo4"}, actual) todos, _ := repo.FindAllTodos() assert.Equal(t, 4, len(todos)) } 13