Cost-effective Go Unit Test thinking and practices

Cost-effective Go Unit Test thinking and practices

Go Conference’19 Summer in Fukuoka で費用対効果の良いテストを目指すための目的・基礎・実践について話したものです

88964b936e864ca7d326272eaa70fa9a?s=128

Kazuki Higashiguchi

July 13, 2019
Tweet

Transcript

  1. © - BASE, Inc. X Cost-effective Go Unit Test thinking

    and practices Go Conference’ Summer in Fukuoka . . - @hgsgtk
  2. © - BASE, Inc. Talk Structure Cost-effective Unit Test Go

    Unit Test Basic Real Practices in Go WHY BASIC PRACTICE
  3. © - BASE, Inc. : @hgsgtk ⾃⼰紹介 東⼝ 和暉 (

    Higashiguchi Kazuki ) Go, PHP, Python BASE BANK, Inc. (BASE, Inc.) / Dev Division / Tech Lead
  4. © - BASE, Inc. Talk Structure Cost-effective Unit Test Go

    Unit Test Basic Real Practices in Go WHY BASIC PRACTICE
  5. © - BASE, Inc. Cost-effective Unit Test

  6. © - BASE, Inc. Unit Testとは • プログラムを構成する⽐較的⼩さな単位(ユニッ ト)が個々の機能を正しく果たしているかどうか を検証する

    • 実⾏⼿段として、⼿動によるテスト‧⾃動化テス トが存在する • このトークでは、⾃動化テストを取り上げる
  7. © - BASE, Inc. WHY? なぜUnit Testを書くのか • ⽋陥混⼊の阻⽌、バグを防ぎたい? •

    テストによるドキュメンテーション? • 設計改善の指標?
  8. © - BASE, Inc. このトークで定義する⽬的 コスト削減

  9. © - BASE, Inc. コスト削減の指標 Unit Test ʹΑΔ ίετ࡟ݮ Unit

    Test ͷ ࡞੒ɾҡ࣋ίετ VS
  10. © - BASE, Inc. • ৽نςετ࡞੒ίετൃੜ • طଘςετҡ࣋ίετൃੜ • ςετ࣮ߦ࣌ؒͷ଴ͪίετൃੜ

    • ࣗಈςετͷͨΊͷCIҡ࣋ίετൃੜ • Ϣχοτςετͷֶशίετൃੜ ユニットテストの両⾯のコスト Unit Test ʹΑΔ ίετ࡟ݮ Unit Test ͷ ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ࡟ݮ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ࡟ݮ • υΩϡϝϯςʔγϣϯίετ࡟ݮ • σόοάίετ࡟ݮ • ઃܭվળʹΑΔϝϯςφϯείετ࡟ݮ
  11. © - BASE, Inc. • ৽نςετ࡞੒ίετൃੜ • طଘςετҡ࣋ίετൃੜ • ςετ࣮ߦ࣌ؒͷ଴ͪίετൃੜ

    • ࣗಈςετͷͨΊͷCIҡ࣋ίετൃੜ • Ϣχοτςετͷֶशίετൃੜ コストへの集約 Unit Test ʹΑΔ ίετ࡟ݮ Unit Test ͷ ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ࡟ݮ •ܽؕͷૣظൃݟʹΑΔ मਖ਼ίετ࡟ݮ • υΩϡϝϯςʔγϣϯίετ࡟ݮ • σόοάίετ࡟ݮ • ઃܭվળʹΑΔϝϯςφϯείετ࡟ݮ ⽋陥混⼊の阻⽌、バグを防ぎたい?
  12. © - BASE, Inc. • ৽نςετ࡞੒ίετൃੜ • طଘςετҡ࣋ίετൃੜ • ςετ࣮ߦ࣌ؒͷ଴ͪίετൃੜ

    • ࣗಈςετͷͨΊͷCIҡ࣋ίετൃੜ • Ϣχοτςετͷֶशίετൃੜ Unit Test ʹΑΔ ίετ࡟ݮ Unit Test ͷ ࡞੒ɾҡ࣋ίετ VS テストによるドキュメンテーション? • खಈϢχοτςετͷίετ࡟ݮ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ࡟ݮ •υΩϡϝϯςʔγϣϯ ίετ࡟ݮ • σόοάίετ࡟ݮ • ઃܭվળʹΑΔϝϯςφϯείετ࡟ݮ コストへの集約
  13. © - BASE, Inc. • ৽نςετ࡞੒ίετൃੜ • طଘςετҡ࣋ίετൃੜ • ςετ࣮ߦ࣌ؒͷ଴ͪίετൃੜ

    • ࣗಈςετͷͨΊͷCIҡ࣋ίετൃੜ • Ϣχοτςετͷֶशίετൃੜ Unit Test ʹΑΔ ίετ࡟ݮ Unit Test ͷ ࡞੒ɾҡ࣋ίετ VS 設計改善の指標? • खಈϢχοτςετͷίετ࡟ݮ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ࡟ݮ • υΩϡϝϯςʔγϣϯίετ࡟ݮ • σόοάίετ࡟ݮ •ઃܭվળʹΑΔ ϝϯςφϯείετ࡟ݮ コストへの集約
  14. © - BASE, Inc. Cost effective Unit Test Unit Test

    ʹΑΔ ίετ࡟ݮ Unit Test ͷ ࡞੒ɾҡ࣋ίετ >
  15. © - BASE, Inc. See also https://speakerdeck.com/hgsgtk/practices-to-write-better-unit-test

  16. © - BASE, Inc. Talk Structure Cost-effective Unit Test Go

    Unit Test Basic Real Practices in Go WHY BASIC PRACTICE
  17. © - BASE, Inc. BASIC Go Unit Test Basic •

    適切なエラーレポート • 適切なエラーハンドリング • テーブル駆動テスト‧サブテスト BASIC
  18. © - BASE, Inc. BASIC Go Unit Test Basic •

    適切なエラーレポート • 適切なエラーハンドリング • テーブル駆動テスト‧サブテスト BASIC
  19. © - BASE, Inc. 例題:不適切なエラーレポート

  20. © - BASE, Inc. 例題:不適切なエラーレポート === RUN TestInStatusListInAppropriateErrorReport --- FAIL:

    TestInStatusListInAppropriateErrorReport ( . s) simple_test.go: : unexpected value true simple_test.go: : unexpected value true FAIL FAIL github.com/hgsgtk/go-snippets/testing-codes/simple . s
  21. © - BASE, Inc. 不適切なエラーレポートの弊害 . 「どこで何を検証して失敗したのか」が読みとり にくい . ⼊⼒‧期待値が結果から読み取れない

    . 問題が起きたか知るためにテストコード‧動作 コードを読む必要がある --- FAIL: TestInStatusListInAppropriateErrorReport ( . s) simple_test.go: : unexpected value true
  22. © - BASE, Inc. 適切なエラーレポートとは Go FAQ “Why does Go

    not have assertions?“ Refs: https://golang.org/doc/faq#assertions “Proper error reporting means that errors are direct and to the point, saving the programmer from interpreting a large crash trace.” 直接的 かつ 適切
  23. © - BASE, Inc. 例題を改善する

  24. © - BASE, Inc. 例題を改善する 直接的かつ適切なエラーレポートを⾏う ex. “f(x) = y,

    want z”という⼀つの形式 f(x): 試みた結果と⼊⼒ y: 得られた結果 z: 期待値
  25. © - BASE, Inc. 例題を改善する === RUN TestInStatusListProperErrorReport --- FAIL:

    TestInStatusListProperErrorReport ( . s) simple_test.go: : InStatusList(`unknown `) = true, want false simple_test.go: : InStatusList(`unknown `) = true, want false FAIL FAIL github.com/hgsgtk/go-snippets/testing-codes/simple
  26. © - BASE, Inc. • ৽نςετ࡞੒ίετൃੜ •طଘςετҡ࣋ίετൃੜ • ςετ࣮ߦ࣌ؒͷ଴ͪίετൃੜ •

    ࣗಈςετͷͨΊͷCIҡ࣋ίετൃੜ • Ϣχοτςετͷֶशίετൃੜ 適切なエラーレポートの効果 Unit Test ʹΑΔ ίετ࡟ݮ Unit Test ͷ ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ࡟ݮ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ࡟ݮ • υΩϡϝϯςʔγϣϯίετ࡟ݮ • σόοάίετ࡟ݮ • ઃܭվળʹΑΔϝϯςφϯείετ࡟ݮ テスト失敗時の 原因調査コストの削減
  27. © - BASE, Inc. BASIC Go Unit Test Basic •

    適切なエラーレポート • 適切なエラーハンドリング • テーブル駆動テスト‧サブテスト BASIC
  28. © - BASE, Inc. 適切なエラーハンドリングとは “Proper error handling means that

    servers continue to operate instead of crashing after a non-fatal error.” 致命的ではないエラーの場合はクラッ シュせずに処理を継続する Go FAQ “Why does Go not have assertions?“ Refs: https://golang.org/doc/faq#assertions
  29. © - BASE, Inc. 適切なエラーハンドリング

  30. © - BASE, Inc. 適切なエラーハンドリング ここで失敗すると後続の検証が不可なので致命的と して、testing.Fatalf()でハンドリング

  31. © - BASE, Inc. 適切なエラーハンドリング 後続の処理可能なため t.Errorf() でハンドリングす る

  32. © - BASE, Inc. • ৽نςετ࡞੒ίετൃੜ •طଘςετҡ࣋ίετൃੜ • ςετ࣮ߦ࣌ؒͷ଴ͪίετൃੜ •

    ࣗಈςετͷͨΊͷCIҡ࣋ίετൃੜ • Ϣχοτςετͷֶशίετൃੜ 適切なエラーハンドリングの効果 Unit Test ʹΑΔ ίετ࡟ݮ Unit Test ͷ ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ࡟ݮ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ࡟ݮ • υΩϡϝϯςʔγϣϯίετ࡟ݮ • σόοάίετ࡟ݮ • ઃܭվળʹΑΔϝϯςφϯείετ࡟ݮ 致命的なもののみをcrashさ せるため、継続可能な限り、 ⼀回の実⾏で多く検証結果を 得られる
  33. © - BASE, Inc. BASIC Go Unit Test Basic •

    適切なエラーレポート • 適切なエラーハンドリング • テーブル駆動テスト‧サブテスト BASIC
  34. © - BASE, Inc. テーブル駆動テストとは “Each table entry is a

    complete test case with inputs and expected results, and sometimes with additional information such as a test name to make the test output easily readable.” ⼊⼒と期待値を含むテストエントリ Refs: https://github.com/golang/go/wiki/TableDrivenTests
  35. © - BASE, Inc. テーブル駆動テスト‧サブテスト

  36. © - BASE, Inc. テーブル駆動テスト‧サブテスト ⼊⼒と期待値を含むテストエントリ

  37. © - BASE, Inc. テーブル駆動テスト‧サブテスト testing.Run()でサブテストとして実⾏ -run で特定ケースを実⾏できる go test

    ./... -v -run TestGetMsgSubTest/ divisible_by_
  38. © - BASE, Inc. •৽نςετ࡞੒ίετൃੜ •طଘςετҡ࣋ίετൃੜ • ςετ࣮ߦ࣌ؒͷ଴ͪίετൃੜ • ࣗಈςετͷͨΊͷCIҡ࣋ίετൃੜ

    • Ϣχοτςετͷֶशίετൃੜ テーブル駆動テスト‧サブテストの効果 Unit Test ʹΑΔ ίετ࡟ݮ Unit Test ͷ ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ࡟ݮ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ࡟ݮ • υΩϡϝϯςʔγϣϯίετ࡟ݮ •σόοάίετ࡟ݮ • ઃܭվળʹΑΔϝϯςφϯείετ࡟ݮ サブテストによる特定テストケースの実⾏
  39. © - BASE, Inc. •৽نςετ࡞੒ίετൃੜ •طଘςετҡ࣋ίετൃੜ • ςετ࣮ߦ࣌ؒͷ଴ͪίετൃੜ • ࣗಈςετͷͨΊͷCIҡ࣋ίετൃੜ

    • Ϣχοτςετͷֶशίετൃੜ テーブル駆動テスト‧サブテストの効果 Unit Test ʹΑΔ ίετ࡟ݮ Unit Test ͷ ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ࡟ݮ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ࡟ݮ • υΩϡϝϯςʔγϣϯίετ࡟ݮ •σόοάίετ࡟ݮ • ઃܭվળʹΑΔϝϯςφϯείετ࡟ݮ テストパターンと検証ロジッ クの分離の効果 同じ検証ロジックを何度も書 かずに済む
  40. © - BASE, Inc. •৽نςετ࡞੒ίετൃੜ •طଘςετҡ࣋ίετൃੜ • ςετ࣮ߦ࣌ؒͷ଴ͪίετൃੜ • ࣗಈςετͷͨΊͷCIҡ࣋ίετൃੜ

    • Ϣχοτςετͷֶशίετൃੜ テーブル駆動テスト‧サブテストの効果 Unit Test ʹΑΔ ίετ࡟ݮ Unit Test ͷ ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ࡟ݮ • ܽؕͷૣظൃݟʹΑΔमਖ਼ίετ࡟ݮ • υΩϡϝϯςʔγϣϯίετ࡟ݮ •σόοάίετ࡟ݮ • ઃܭվળʹΑΔϝϯςφϯείετ࡟ݮ エントリ追加で テストのバリエーションを修 正できる
  41. © - BASE, Inc. Talk Structure Cost-effective Unit Test Go

    Unit Test Basic Real Practices in Go WHY BASIC PRACTICE
  42. © - BASE, Inc. PRACTICE Real Practices in Go PRACTICE

    BASIC WHY カスタムアサーション テストダブルを使うテスト RDBMSを使う処理のテスト
  43. © - BASE, Inc. PRACTICE Real Practices in Go PRACTICE

    BASIC WHY カスタムアサーション テストダブルを使うテスト RDBMSを使う処理のテスト
  44. © - BASE, Inc. 起きがちな課題:Copy & Paste

  45. © - BASE, Inc. 起きがちな課題:Copy & Paste

  46. © - BASE, Inc. テストコードが増えてくると • HTTP Handlerなど関⼼事が領域が近い 処理のテストコードは、同じ関⼼領域を 類似した検証ロジックでテストする

    • コピペが⽣まれると往々にして「適切な エラーレポート」が忘れられる • ⇒ テストの維持コストの増加
  47. © - BASE, Inc. 共通処理を⾏うテストヘルパーの選択肢 • 同様の関⼼‧ユースケースがある処理 は testutil パッケージなどにして共通化

    する • ⇒ 新規テストコストの削減
  48. © - BASE, Inc. 実例:HTTP Handlerのアサーションを共通処理にする • t: *testing.T を求め、t.Helper()をcall

    • testTarget: 「どのHandlerを施⾏するか」を設定 • gotRes: 試⾏結果の *http.Response • wantCode, bodyFile: 期待値
  49. © - BASE, Inc. 実例:HTTP Handlerのアサーションを共通処理にする AssertResponse()をcall JSONのresponse bodyの期待値は別ファイルで配置

  50. © - BASE, Inc. 実例:HTTP Handlerのアサーションを共通処理にする • testdata ディレクトリを作成 •

    ⇒パッケージとしてみなされない、テスト時のみ必要なファ イルであることが明⽰的になる • *.json.golden 拡張⼦として配置 • ⇒ 標準パッケージでも使われている⼿法 • ⇒テストのドキュメントとしての価値の向上
  51. © - BASE, Inc. 実例:HTTP Handlerのアサーションを共通処理にする

  52. © - BASE, Inc. 実例:HTTP Handlerのアサーションを共通処理にする

  53. © - BASE, Inc. 実例:HTTP Handlerのアサーションを共通処理にする

  54. © - BASE, Inc. 実例:HTTP Handlerのアサーションを共通処理にする 別ファイル定義したJSONファイルと⽐較 ⽐較には google/go-cmp を利⽤

  55. © - BASE, Inc. PRACTICE Real Practices in Go PRACTICE

    BASIC WHY カスタムアサーション テストダブルを使うテスト RDBMSを使う処理のテスト
  56. © - BASE, Inc. テストダブル Test Double: テスト固有の同等物 Mock: テスト対象に適切に使⽤されているか検証

    する Refs ॻ੶ʰxUnit Test Patterns: Refactoring Test Codeʱ / Chapter 23. Test Double Patterns
  57. © - BASE, Inc. ※補⾜:このトーク内での “Mock” Refs ॻ੶ʰςετۦಈ։ൃʱ / ෇࿥C

    Ϣχοτςετपลͷ஌ࣝͷ੔ཧͱTDD֦ுͷࢼΈ • xUTPの語彙整理により、「広義のMock Object」‧「狭義のMock Object」を分けて議 論できるようになった • このトークでの “Mock” もTest Doubleの範囲を ざっくりと含める「広義のMock Object」とする
  58. © - BASE, Inc. テストダブルを使うテスト 次のような属性を持つものに依存する場合に、テス ト時にテストダブルに置き換える事がある • 結果が不定 •

    ex: UUID • システム外コンポーネントを利⽤する処理 • ex: API, RDBMS, AWS etc • レイヤ構造をまたぐ依存 • ex: Service->Repository
  59. © - BASE, Inc. テストダブル作成の選択肢としてのgomock • “GoMock is a mocking

    framework for the Go programming language.” • https://github.com/golang/mock
  60. © - BASE, Inc. 例: gomockを利⽤するテスト例

  61. © - BASE, Inc. 例: gomockを利⽤するテスト例

  62. © - BASE, Inc. 例: gomockを利⽤するテスト例 gomockで依存関係のあるInterfaceを実装したモックを作成 Times()をcallして、呼び出される期待を設定する => 狭義のMock

  63. © - BASE, Inc. この例の問題点 • すべての依存関係に呼び出しの期待を設 定している • 外の振る舞いを変えない内部構造の変更

    に敏感に反応する • ⇒ リファクタリングの障害になる
  64. © - BASE, Inc. 的確なエクスペクテーション Refs ॻ੶ʰ࣮ફςετۦಈ։ൃʱ / ୈ20ষ ςετͷ੠Λௌ͘

    “意図を明確にするためには、スタブとエクスペク テーション‧アサーションをきっちり区別するとよ い。”
  65. © - BASE, Inc. 区別するための指標:コマンド/クエリ Refs ॻ੶ʰ࣮ફςετۦಈ։ൃʱ / ୈ20ষ ςετͷ੠Λௌ͘

    ΫΤϦʹ͸ΞϩʔΞϯεɺίϚϯυʹ͸ΤΫεϖΫςʔγϣϯ • コマンドとは、 副作⽤を伴うことのある呼び出し • オブジェクトの外の世界を変化させる • ex. DBのレコード保存‧状態変化 • 複数回実⾏した場合、システム状態は違ったもの になる => エクスペクテーション
  66. © - BASE, Inc. 区別するための指標:コマンド/クエリ • クエリは、外の世界を変化させない • 何回呼んでも呼ばなくても構わない •

    ⇒ アローアンス Refs ॻ੶ʰ࣮ફςετۦಈ։ൃʱ / ୈ20ষ ςετͷ੠Λௌ͘ ΫΤϦʹ͸ΞϩʔΞϯεɺίϚϯυʹ͸ΤΫεϖΫςʔγϣϯ
  67. © - BASE, Inc. 改善する例

  68. © - BASE, Inc. 改善する例 Anytimes()をcallすることで「呼んでも呼ばなくてもいい」 Stubになる

  69. © - BASE, Inc. PRACTICE Real Practices in Go PRACTICE

    BASIC WHY カスタムアサーション テストダブルを使うテスト RDBMSを使う処理のテスト
  70. © - BASE, Inc. RDBMSを使う処理の例

  71. © - BASE, Inc. RDBMSを使う処理のテストの選択肢 • 期待したSQLを発⾏するかを検証する • 実際のDBは含めない、DBとの協調が期待通り か

    • ex: DATA-DOG/go-sqlmockの利⽤ • 実際のDBも検証範囲に含める • データベースを起動しテストに利⽤する • ex: lestrrat-go/test-mysqldの利⽤
  72. © - BASE, Inc. 実感値: 期待したSQLを発⾏するかを検証する⽅法 • Props • テスト速度が速い

    • Cons • ⽋陥の発⽣可能性、期待値のSQLが間違ってい る • SQLの結果⾃体が責務の⼤部分を占める場合に 「SQLが正しい結果を得るものかどうか」を確 かめにくい
  73. © - BASE, Inc. 実感値: 実際のDBも検証範囲に含める • Props • ⽋陥の発⽣可能性は下がる、

    “安⼼感” も⾼い • 単体テストをデバッグに使うスタイルの場合、 DBを使えるのでやりやすい • Cons • テスト速度は遅い • 実際のDBを使えるようにするまでの準備はそ れなりに⼤変
  74. © - BASE, Inc. The Test Automation Pyramid UI Service

    Unit Refs ॻ੶ʰSucceeding with Agileʱ / Chapter.16 Quality スコープの拡⼤ ⾃信の向上 ⾼速 分離性の向上
  75. © - BASE, Inc. • ৽نςετ࡞੒ίετൃੜ • طଘςετҡ࣋ίετൃੜ •ςετ࣮ߦ࣌ؒͷ ଴ͪίετൃੜ

    • ࣗಈςετͷͨΊͷCIҡ࣋ίετൃੜ • Ϣχοτςετͷֶशίετൃੜ ⽐較:RDBMSを使う処理のテストの選択肢 Unit Test ʹΑΔ ίετ࡟ݮ Unit Test ͷ ࡞੒ɾҡ࣋ίετ VS • खಈϢχοτςετͷίετ࡟ݮ •ܽؕͷૣظൃݟʹΑΔ मਖ਼ίετ࡟ݮ • υΩϡϝϯςʔγϣϯίετ࡟ݮ •σόοάίετ࡟ݮ • ઃܭվળʹΑΔϝϯςφϯείετ࡟ݮ 実際のDBも検証範囲に 含める 期待したSQLを発⾏す るかを検証する⽅法
  76. © - BASE, Inc. See also 期待したSQLを発⾏するかを検証する Refs https://devblog.thebase.in/entry/2018/12/04/110000

  77. © - BASE, Inc. See also 実際のDBも検証範囲に含める Refs https://medium.com/@timakin/go-api-testing-173b97fb23ec •

    timakin さんのこちらの記事が参考になります。 • 「GoのAPIのテストにおける共通処理」 • https://medium.com/@timakin/go-api- testing- b fb ec
  78. © - BASE, Inc. まとめ • 費⽤対効果の良いテストを⽬指す • 費⽤対効果のいいテストを⽬指す考えかたとし て、Goの考え⽅は有効

    • 実際の現場での試⾏錯誤も、⽬的‧基礎を腹に 持っておくといい
  79. © - BASE, Inc. fin.