$30 off During Our Annual Pro Sale. View Details »

テーブル駆動テストと状態

Hazumi Ichijo
September 28, 2023

 テーブル駆動テストと状態

Hazumi Ichijo

September 28, 2023
Tweet

More Decks by Hazumi Ichijo

Other Decks in Programming

Transcript

  1. © 2023 Wantedly, Inc.
    テーブル駆動テストと状態
    golang.tokyo #33
    Sep. 28 2023 - Hazumi Ichijo

    View Slide

  2. 自己紹介
    © 2023 Wantedly, Inc.
    一條 端澄 @hazumirr/@rerost
    略歴:
    2018~ : ウォンテッドリー株式会社
    ● 2018/04~ 推薦基盤チーム エンジニア
    ● 2021/09~ 推薦チーム プロジェクトマネージャー
    ● 2022/06~ 推薦チーム リーダー
    趣味: テトリス・旅行

    View Slide

  3. テーブル駆動テストで悩ましい点
    © 2023 Wantedly, Inc.
    testCase := []struct{
    in string
    out int
    }{
    {
    in: "engineer",
    out: 2,
    },
    }
    単語を受け取りその検索結果の件数を返す関数のテ
    ストの一部

    View Slide

  4. テーブル駆動テストで悩ましい点
    © 2023 Wantedly, Inc.
    testCase := []struct{
    in string
    out int
    }{
    {
    in: "engineer",
    out: 2,
    },
    }
    単語を受け取りその検索結果の件数を返す関数のテ
    ストの一部
    テストケースごとにデータを用意しようとすると...

    View Slide

  5. テーブル駆動テストで悩ましい点
    © 2023 Wantedly, Inc.
    testCase := []struct{
    in string
    before func(sql.DB)
    out int
    }{
    {
    in: "engineer",
    before: func(db sql.DB) error {
    // ここでテストデータを DBに準備
    },
    out: 2,
    },
    }
    よくあるケースとしてはこんな実装
    データを生成するのもテストケースごと
    に管理する

    View Slide

  6. テーブル駆動テストで悩ましい点
    テストコードが負債になる
    ● すでにあるテストをどう修正して良いかわからない
    ● かなり似ているが微妙に異なるものが多い
    ● …
    © 2023 Wantedly, Inc.

    View Slide

  7. テーブル駆動テストで悩ましい点
    テストコードが負債になる
    ● すでにあるテストをどう修正して良いかわからない
    ● かなり似ているが微妙に異なるものが多い
    ● …
    テストケースの記述では「どう作るか」の関心を排除し、「何が状
    態として欲しいか」だけに絞りたい
    © 2023 Wantedly, Inc.

    View Slide

  8. 解決策
    © 2023 Wantedly, Inc.
    testCase := []struct{
    in string
    state State
    out int
    }{
    {
    in: "engineer",
    state: State{
    users: []User{
    {
    OccupationType: "engineer",
    },
    テストケースはここだけ読めばやりたいこと
    が見えやすくなる
    更に、データベースへの接続などのインター
    フェース変更が容易に

    View Slide

  9. Tips
    © 2023 Wantedly, Inc.
    // 文字列を受け取ってUUIDに変換する
    func GenerateID(id string) string {
    return uuid.NewSHA1(uuid.NameSpaceDNS, []byte(id)).String()
    }
    func TestSearchCount(t *testing.T) {
    ...
    state: State{...
    {
    Uid: generateId("User1"),
    FriendIDs: []string{generateId("User2")},
    },
    {
    Uid: generateId("User2"),
    FriendIDs: []string{generateId("User1")},

    依存関係を管理したくなった時

    View Slide

  10. Tips
    © 2023 Wantedly, Inc.
    デフォルト値を作りたくなった時
    // デフォルト値をpropで受け取ったnon null値で上書きしたものを返す関数
    func Merge[T any](t *testing.T, defaultValue, prop T) T {
    t.Helper()
    res := new(T)
    if err := copier.CopyWithOption(&res, defaultValue, copier.Option{...}); err != nil {
    t.Errorf("Failed to copy default value. err: %v", err)
    }
    if err := copier.CopyWithOption(&res, prop, copier.Option{..}); err != nil {
    t.Errorf("Failed to copy prop value. err: %v", err)
    }
    return *res
    }

    View Slide

  11. まとめ
    1. テストケースには「何が状態として欲しいか」のみ記述
    2. (自分の観測範囲では)うまく行っている
    © 2023 Wantedly, Inc.

    View Slide

  12. © 2023 Wantedly, Inc.

    View Slide