Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Goプロダクトにおけるテスト改善の軌跡
Search
kiyoshiro
December 12, 2024
2
680
Goプロダクトにおけるテスト改善の軌跡
https://dmm.connpass.com/event/335033/
のDMM.goのトラックで発表した内容
kiyoshiro
December 12, 2024
Tweet
Share
More Decks by kiyoshiro
See All by kiyoshiro
純SPAでNext.jsに対抗する 〜ページによってmetaタグを切り替える編〜
kiyoshiro
0
810
Featured
See All Featured
GitHub's CSS Performance
jonrohan
1030
460k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
6
530
Unsuck your backbone
ammeep
669
57k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
49k
Typedesign – Prime Four
hannesfritz
40
2.5k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
27
1.5k
Art, The Web, and Tiny UX
lynnandtonic
298
20k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
330
21k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
132
33k
Fashionably flexible responsive web design (full day workshop)
malarkey
406
66k
KATA
mclloyd
29
14k
Transcript
© DMM.com Goプロダクトにおける テスト改善の軌跡 DMMポイントクラブグループ 清川航一
Goプロダクトにおけるテスト改善の軌跡 自己紹介 名前 清川航一 (𝕏 @kiyoshiro944) おしごと ・おもにフロントエンド(React/TypeScript 6年ほど) 👈のちの伏線
・バックエンドもぼちぼち書く(Go歴 3年ほど) ・分析クエリ書いたりも 所属 ポイントクラブチーム 新卒3年目 2
Goプロダクトにおけるテスト改善の軌跡 お話すること 3 • 新卒でチームにジョインして、生まれてはじめてGoのプロダクト に触れた自分が、3年の間にテストでどのような課題を発見し て、解決し、学びを得たか • テスト:おもにユニットテスト
Goプロダクトにおけるテスト改善の軌跡 もくじ • 課題と改善 • ゴールデンテスト導入 • モックの引数検証 • 過剰なテーブル駆動をやめる
• まとめ • 今後やりたいこと 4
Goプロダクトにおけるテスト改善の軌跡 もくじ • 課題と改善 • ゴールデンテスト導入 • モックの引数検証 • 過剰なテーブル駆動をやめる
• まとめ • 今後やりたいこと 5
Goプロダクトにおけるテスト改善の軌跡 課題:出力値(actual)のコピペがつらい • 改善1. ゴールデンテスト導入 - 課題 🤔「どんなエラー文が返ってくるかわからない」
💪「一旦落ちる前提でテスト実行してみて、 actualの値 をコピってくるか」 ※今回の例ではそもそも完全一致でアサーションしているのが問題では あります。実際はもっと事情が複雑でしたが割愛。 そしてある日、仕様変更によりテストが 20〜30箇所落ちた 😇「これ全部出力値のコピペで修正するのか…」 👉 6
Goプロダクトにおけるテスト改善の軌跡 自分🤔「フロントエンドだったらスナップショットテスト使うかもな」 スナップショットテスト(Go界隈ではゴールデンテスト ) 改善1. ゴールデンテスト導入 - 改善 7
テスト対象 入力 出力 テスト1回目 { “a”: 1, “b”: 2 } ファイルに保存 テスト対象 入力 出力 テスト2回目以降 ゴールデンファイル 一致するか比較
Goプロダクトにおけるテスト改善の軌跡 改善1. ゴールデンテスト導入 - ライブラリの選定 8 cupaloy goldie 自前でヘルパー関数つくる
⭐300ちょっと ⭐200ちょっと ー 最終コミット3ヶ月前 その前のコミットは1年前 最終コミット4ヶ月前 その前のコミットは2年前 ー ・どちらも開発が活発とは言えない状態だった 😢 ・機能・APIに大差はなかった 現実的な工数で作れそうでは あるが大変 ・ゴールデンファイルがなければ作成。あれば 比較する処理を実装 ・ゴールデンファイルを書き出すディレクトリ名、 ファイル名を決定 ※選定を行った2022/12当時のデータ。他にもライブラリはたくさんあったが当時は3つのみ比較した ※納期が迫っており、選定にそこまで工数をかけられなかった 😢
Goプロダクトにおけるテスト改善の軌跡 • 良かった点 👍ゴールデンテストの導入により、出力値のコピペをしなくてよくなった 👍ライブラリ更新などで出力値が変わったときもゴールデンファイルを コマンド一発で更新 できる • 注意点
• 更新が簡単な分、ゴールデンファイルは思考停止で更新されがち。 コードレビューで、差分が妥当かも確認する 必要あり • ゴールデンファイルが大きくなりすぎると、この差分の確認が大変なので、入力 値を調整する必要あり 出力が1000件のスライス 出力が5件のスライス 改善1. ゴールデンテスト導入 - 導入してみて 9
Goプロダクトにおけるテスト改善の軌跡 • 課題 テスト出力値のコピペが辛い • 解決法 フロントエンドで使われるスナップショットテストから着想を得て、cupaloyというライブ ラリを採用しゴールデンテストを導入 • 学び
フロントエンドのテストの知識がGoプロダクトのテストでも活かせる 改善1. ゴールデンテスト導入 - まとめ 10
Goプロダクトにおけるテスト改善の軌跡 もくじ • 課題と改善 • ゴールデンテスト導入 • モックの引数検証 • 過剰なテーブル駆動をやめる
• まとめ 11
Goプロダクトにおけるテスト改善の軌跡 課題:テストカバレッジは高いのに 本番反映前にステージング環境で 動作確認するとバグがみつかる 改善2. モックの引数検証 - 課題 12
Goプロダクトにおけるテスト改善の軌跡 13 • 外部の人にも伝わるように仕様をシンプル にしたもの(※実サービスの仕様とは異なります) • ゲームクリア時に新規ユーザーには100pt, それ以外には1pt付与する実装
• 正常系テストでエラーを返さないことだけ 検 証 課題:検証がゆるい 何ポイント付与したか検証できておらず、バグ を検知できない ※Repositoryなら集約の単位で保存しろなどツッコミもあると思いますが、本筋と関係ないので一旦スルーを 🙏
Goプロダクトにおけるテスト改善の軌跡 改善2. モックの引数検証 モックについて 検証する際に重要なことは次の2つのことです • 想定する呼び出しが行われていること • 想定しない呼び出しは行われていないこと
『単体テストの考え方/使い方』 p.321より 14
Goプロダクトにおけるテスト改善の軌跡 15 Q. 引数の検証できないって、どんなモック使ってたの?? A. 当時は手書き
Goプロダクトにおけるテスト改善の軌跡 A. 実装が👆でもテストが✅になってしまう😇 ポイント付与メソッドを呼んでいないため、assert.Equa point)が呼ばれない 🤔手書きのモックでもいけるのでは? 16 Q. このアサーションだと問題があります。
さて何でしょう?
Goプロダクトにおけるテスト改善の軌跡 17 😩モックを手書き+呼ばれた回数を管理 はめんどい 😩メソッドをリネームしたときに修正するの大変 👉モックを自動生成するライブラリを導入しよう
Goプロダクトにおけるテスト改善の軌跡 モック自動生成ライブラリの選定 1⃣ moq 2⃣ gomock or mockery 18
Goプロダクトにおけるテスト改善の軌跡 moqは生成されるコードが非常にシンプル で、 標準パッケージしか使っていない 👍ドキュメントを読まずとも使い方がわかる 👋万が一moqのメンテが終わったとしても、 moqはプロダクトから消しつつ、生成していた コードは使い続けるという選択もできる (gomockやmockeryの生成コードは自身のライブラリを
importしている) 1⃣ moq vs 2⃣ gomock or mockery 19
Goプロダクトにおけるテスト改善の軌跡 生成されたモックの使い方 - 1⃣ moq 20 👈呼び出し回数の検証。前述の通り大事 👍手書きしていたモックと使い方が一 緒。移行が楽
Goプロダクトにおけるテスト改善の軌跡 生成されたモックの使い方 - 2⃣gomock or mockery 21 ※使い方がほぼ一緒のためgomockのみ記載 👍引数の検証、呼び出し回数の検証がシンプルに書ける
(Issueメソッドの呼び出し回数がきっかり1回であることも検証できている) gomockとmockeryの細かい違いは“Comparison of golang mocking libraries”のGistを見ると良い https://gist.github.com/maratori/8772fe158ff705ca543a0620863977c2
Goプロダクトにおけるテスト改善の軌跡 1⃣ moq 2⃣ gomock or mockery 👉手書きしていたモックからの移行の楽さ と生成されるコード のシンプルさ
から、自分たちのプロダクトではmoqを採用 モック自動生成ライブラリの選定 22
Goプロダクトにおけるテスト改善の軌跡 • 課題 返り値がerrorのみのメソッドでモックの引数が検証できておらず検証がゆるかった。 またモックの手書きは変更が大変だった。 • 解決法 モック自動生成ライブラリmoqを導入 • 学び
カバレッジは「高いから安心」ではない 取得以外の処理(Saveとか)のモックで以下の2つを検証するのが大事 • 想定通りの引数で呼ばれたか • 想定通りの回数呼ばれたか 改善2. モックの引数検証 - まとめ 23
Goプロダクトにおけるテスト改善の軌跡 もくじ • 課題と改善 • ゴールデンテスト導入 • モックの引数検証 • 過剰なテーブル駆動をやめる
• まとめ 24
Goプロダクトにおけるテスト改善の軌跡 改善3. 過剰なテーブル駆動をやめる - 注意 • この章は今までよりも長いです • とくに「課題→改善方法→まとめ」のうち「課題」のパートが長い です
• Go Conference 2024でも似た発表があったので、気になるかた はそちらもチェックを! • 『Table-driven testing に縛られないGoのテストパターン』 (https://gocon.jp/2024/sessions/19/) 25
Goプロダクトにおけるテスト改善の軌跡 改善3. 過剰なテーブル駆動をやめる - 課題感 • 課題:テストケースが読みにくい & 追加しにくい🤔 26
Goプロダクトにおけるテスト改善の軌跡 実際に書かれていたテストのイメージ 27 まずはテスト対象の実装を チェック
Goプロダクトにおけるテスト改善の軌跡 実際に書かれていたテストのイメージ 28 👈 1⃣モックする必要あり まずはテスト対象の実装を チェック
Goプロダクトにおけるテスト改善の軌跡 実際に書かれていたテストのイメージ 29 👈 2⃣条件分岐を網羅的にテストしたい まずはテスト対象の実装を チェック
Goプロダクトにおけるテスト改善の軌跡 実際に書かれていたテストのイメージ 30 👈 3⃣異常系もテストしたい まずはテスト対象の実装を チェック
Goプロダクトにおけるテスト改善の軌跡 31 次に、テストをチェック
Goプロダクトにおけるテスト改善の軌跡 32 1⃣モック
Goプロダクトにおけるテスト改善の軌跡 33 2⃣条件分岐を網羅的に チェック 🤔条件分岐のチェック、 コード量多いな…(伏線)
Goプロダクトにおけるテスト改善の軌跡 34 3⃣異常系のテスト
Goプロダクトにおけるテスト改善の軌跡 改善3. 過剰なテーブル駆動をやめる - 課題感 35 • プロジェクトのテストコードはこのように1メソッドにつき1テーブル で書かれて いた
• 自分🤔「なんか読みづらい&書きづらい」 👉言語化
Goプロダクトにおけるテスト改善の軌跡 読みづらいポイント1: 上から下の順に読めない 36 例えば 🤔「このwantErrってなんだ?」 と疑問を持ったとして…
Goプロダクトにおけるテスト改善の軌跡 読みづらいポイント1: 上から下の順に読めない 37 下までスクロールして、for文の中を見 るとようやく理解できる
Goプロダクトにおけるテスト改善の軌跡 読みづらいポイント1: 上から下の順に読めない 38 フィールドの意味が理解できたので、 ようやくテストケースを読み始められる
Goプロダクトにおけるテスト改善の軌跡 読みづらいポイント1: 上から下の順に読めない 39 👀視線 🤞スクロールする 指の負担大
Goプロダクトにおけるテスト改善の軌跡 🤨いや、フィールド名をわかりやすくすれば、for文 の中は読み飛ばせるでしょ?? 40
Goプロダクトにおけるテスト改善の軌跡 読みづらいポイント2: しれっと書いてある共通処理 41 👈❗❗ ❗ (※ログインユーザーのIDに1をセットしている) 全テストケースに共通して発動する処
理がfor文の中 に書かれていることが あるので、読み飛ばせない ※たまにこの共通処理がテストのwant値に影響す ることがある。油断ならない。
Goプロダクトにおけるテスト改善の軌跡 読みづらいポイント3: もはや間違い探し 42 🤔「あれ、この2つのテストケース、モックの部分ほぼ 一緒では??なんでwantが違うんだ??」 👆ここ 👆ここ
Goプロダクトにおけるテスト改善の軌跡 1メソッドのテストにつき1テーブルにしているせいで 読みづらいポイント 1.上から下の順に読めない 2.しれっと書いてある共通処理 3.もはや間違い探し 改善3. 過剰なテーブル駆動をやめる -
課題感 43
Goプロダクトにおけるテスト改善の軌跡 💪今からテスト書くぞ〜 書きづらいポイント1: まずはテーブルを考える必要あり 44
Goプロダクトにおけるテスト改善の軌跡 💪今からテスト書くぞ〜。書くべきテストは3パターンか 書きづらいポイント1: まずはテーブルを考える必要あり 45 😩まずはこの3パターンを書くためのテー ブルを考える必要がある (具体的なテストを書く前に抽象化が必要)
Goプロダクトにおけるテスト改善の軌跡 ※エラーを完全一致 で検証してる 🤔実装が追加されて、エラーを部分一致で 検証するテストケースも追加したくなったな… 書きづらいポイント2: 毛色の違うアサーションを足しづらい 46
😇こうして、一部のケースでしか使わないフィー ルドが増え、テストがif文だらけになっていく
Goプロダクトにおけるテスト改善の軌跡 1メソッドのテストにつき1テーブルにしているせいで 読みづらいポイント 1.上から下の順に読めない 2.しれっとfor文の中に書いてある共通処理 3.もはや間違い探し 書きづらいポイント 1.まずはテーブルを考える必要あり
2.毛色の違うアサーションを足しづらい 改善3. 過剰なテーブル駆動をやめる - 課題感 47
Goプロダクトにおけるテスト改善の軌跡 • 課題の説明おしまい • さて、この課題をどう解決していくか… ここからは改善方法のお話 改善3. 過剰なテーブル駆動をやめる - 課題感
48
Goプロダクトにおけるテスト改善の軌跡 • 困ったときは原点に立ち返ってGo Wikiのテーブル駆動の説明を読もう 改善3. 過剰なテーブル駆動をやめる - 改善方法の模索 良いテストを書くことは簡単ではありませんが、多くの状況ではテーブ ル駆動テストでカバーできます。(...中略...)
テストを書くときにコピペしてる場合は、テーブル駆動テストにリファクタ リングするか、コピーしたコードをヘルパー関数に切り出すかを検討し てください (https://go.dev/wiki/TableDrivenTests より) 49
Goプロダクトにおけるテスト改善の軌跡 気づき • 実は「すべてのテストをテーブル駆動で書け」とまでは言われていない • 「1メソッドにつき1テーブルにまとめろ」とも言われていない 改善3. 過剰なテーブル駆動をやめる 良いテストを書くことは簡単ではありませんが、多くの状況では テーブ
ル駆動テストでカバーできます。(...中略...) テストを書くときにコピペしてる場合は、テーブル駆動テストにリファクタ リングするか、コピーしたコードをヘルパー関数に切り出すかを検討し てください (https://go.dev/wiki/TableDrivenTests より) 50
Goプロダクトにおけるテスト改善の軌跡 • フロントエンドでもテーブル駆動のような書き方はあるが、同じような検証 を複数の入力値 で行うときにしか使わない。
• 『単体テストの考え方/使い方』ではテーブル駆動に近いものとして「パラメータ化テスト」が 紹介されている(p. 83)。ここでも「同じようなテストケースを 1つのグループにまとめる」と書 かれている 他の界隈も見てみると… 51 (Jest公式ドキュメント より引用)
Goプロダクトにおけるテスト改善の軌跡 あらためて自分たちのテストを見てみると… 52 (...中略...) 1⃣正常系:登録日に応じて想定通りのポイントが返ること 2⃣異常系:想定通りのエラーが返ること 😭2パターンが無理やり1つの テーブルに 押し込まれている
Goプロダクトにおけるテスト改善の軌跡 53 同じパターンだけを テーブル駆動テストす るように修正
Goプロダクトにおけるテスト改善の軌跡 👍検証内容はそのまま、全体の行数 が2/3に減少 ※後でコードは拡大するので詳細は見なくてOK 54
Goプロダクトにおけるテスト改善の軌跡 55 ポイント:検証したい項目ごとにt.Run を書く
Goプロダクトにおけるテスト改善の軌跡 56 👍t.Runがテーブルの説明になって いるので理解しやすい 👍テーブルが小さいので ・新しいケースを追加しやすい ・既存のケースも修正しやすい
Goプロダクトにおけるテスト改善の軌跡 57 🤔とはいえ、重複コードができてしまう (サンプルコードでは2行だけだが、実コード だともっと多くなることも)
Goプロダクトにおけるテスト改善の軌跡 • 重複をどうするか… • もう一回Go Wikiを読むと… 改善3. 過剰なテーブル駆動をやめる 良いテストを書くことは簡単ではありませんが、多くの状況ではテーブル駆動テストでカ バーできます。(...中略...)
テストを書くときにコピペしてる場合は、テーブル駆動テストに リファクタリングするか、コピーしたコードをヘルパー関数に 切り出すか を検討してください (https://go.dev/wiki/TableDrivenTests より) 58
Goプロダクトにおけるテスト改善の軌跡 😤ヘルパー関数に切り出して解決 (スコープを小さくするためにTest関数の中に宣言する のが個人的なおすすめ) 59
Goプロダクトにおけるテスト改善の軌跡 テスト対象の実行部分がまだ重複しているが、これ は必要経費として割り切る ※フロントエンドでも『単体テストの考え方/使い方』 のサンプルコードでも、この重複はよく見かける。 60
Goプロダクトにおけるテスト改善の軌跡 • テストケースを追加するときのルール 1.まずはテーブル駆動なし で検証したい項目ごとにt.Runを書く 2.同じパターンのみ テーブル駆動でまとめる 3.テストコードの重複を抑えたい場合はヘルパー関数 に切り出す
👉今までは、1テストメソッドにつきテーブルは1個だったのが、基本的には0個に なった(テストによっては2個以上になることもある) 改善3. 過剰なテーブル駆動をやめる - 現状の方針 61
Goプロダクトにおけるテスト改善の軌跡 1メソッドのテストにつき1テーブルにしているせいで 読みづらいポイント 1.上から下の順に読めない 2.しれっとfor文の中に書いてある共通処理 3.同じようなテストコードが連続して、もはや間違い探し
書きづらいポイント 1.まずはテーブルを考える必要あり 2.毛色の違うアサーションを足しづらい 改善3. 過剰なテーブル駆動をやめる - 課題感(再掲) 62
Goプロダクトにおけるテスト改善の軌跡 1メソッドのテストにつき1テーブルにしているせいで 読みづらいポイント 1.上から下の順に読めない 2.しれっとfor文の中に書いてある共通処理 3.同じようなテストコードが連続して、もはや間違い探し
👈※ルール1だけだとより悪化する場合がある 書きづらいポイント 1.まずはテーブルを考える必要あり 2.毛色の違うアサーションを足しづらい 改善3. 過剰なテーブル駆動をやめる - 課題感(再掲) 63 ルール1.まずはテーブル駆動なし で検証したい項目ごとにt.Runを書く
Goプロダクトにおけるテスト改善の軌跡 1メソッドのテストにつき1テーブルにしているせいで 読みづらいポイント 1.上から下の順に読めない 2.しれっとfor文の中に書いてある共通処理 👈※want値に影響を与える共通処理はfor文の中に書かないように 3.同じようなテストコードが連続して、もはや間違い探し
書きづらいポイント 1.まずはテーブルを考える必要あり 2.毛色の違うアサーションを足しづらい 改善3. 過剰なテーブル駆動をやめる - 課題感(再掲) 64 ルール2.同じパターンのみ テーブル駆動でまとめる ルール3.テストコードの重複を抑えたい場合はヘルパー関数 に切り出す
Goプロダクトにおけるテスト改善の軌跡 • 課題 同一ではないパターンを1テーブルに無理やり押し込んでおり、読みづらい・書きづら い問題が起きていた。 • 改善方法 基本的にはテーブル駆動を使わずにテストを書き、同一パターンだけ小さなテーブル 駆動に切り出す。 •
学び まずは有名な手法に従うのが大切。ただし、盲目的に受け入れるのではなく、一度先 入観をなくしてより良い方法を模索するのも大切。 改善3. 過剰なテーブル駆動をやめる - まとめ 65
Goプロダクトにおけるテスト改善の軌跡 全体のまとめ 66 課題 改善方法 1 テスト出力値のコピペが辛かった。
cupaloyというライブラリを採用しゴールデンテストを 導入した。 2 モックの引数が検証できておらずバグを見逃す&手書き したモックの変更が大変だった。 モック自動生成ライブラリmoqを導入した。 3 同一ではないパターンを1テーブルに無理やり押し込ん でおり、読みづらい・書きづらい問題が起きていた。 基本的にはテーブル駆動を使わずにテストを書き、 同一パターンだけ小さなテーブル駆動に切り出す。
Goプロダクトにおけるテスト改善の軌跡 • 「カバレッジ高いけど検証が甘い」を炙り出せる仕組みがほしい • Mutationテストの導入:わざと実装をバグらせて、テストが「ちゃんと落ちる」ことを確 認。これにより、バグを検出できないテストが炙り出せる • zimmski/go-mutesting など 今後やりたいこと
67
Goプロダクトにおけるテスト改善の軌跡 • 自分にとって人生はじめてのGoプロダクトだったので、最初は目にするコードすべて が正解に見えた。それでも課題を見つけられるようになったのは、 • フロントエンドなど他の界隈の知識と比較 したから • 言語に関係ない汎用的な知識 をつけたから
(『単体テストの考え方/使い方』めっちゃおすすめです!!!) • 今回のようになにか改善を加えるときは、メンバーに少しずつ試してもらってフィード バックをもらう のが大事 • 複数人が良さを実感したものをチームに広げていく • 評判が良くなかったらもとのやり方に切り戻す あとがき 68
ご清聴ありがとうございました 69
Goプロダクトにおけるテスト改善の軌跡 • まだイケてないテストが残ってるので改善 • 月末にのみ落ちるのでプルリクの段階では 気がつけずマージされてしまう 👉テストケースでは固定値を使うように チームでマインドを共有していく 今後やりたいこと1 70
Goプロダクトにおけるテスト改善の軌跡 改善3. 過剰なテーブル駆動をやめる • 自分たちも含め1メソッドにつき1つのテーブル駆動で書いているプロダクトは DMMの中でも結構見かける。なぜ🤔 • Go Wikiではヘルパー関数は解説がほぼない一方でテーブル駆動は1ページまる まる使って丁寧に解説されている
• 「テーブル駆動」という覚えやすい名前もあいまって認知しやすく使われやす くなった 『ハンマーを持つ人にはすべてが釘に見える』 その他、認知科学分野で、名前がついていると認知しやすくなる現象などを 引用 71