Upgrade to Pro — share decks privately, control downloads, hide ads and more …

テスタビリティの高いGoのAPIサーバを開発しよう

 テスタビリティの高いGoのAPIサーバを開発しよう

9a3693c0c3d12f2fbae51792dd6a1344?s=128

Akito0107

March 06, 2021
Tweet

Transcript

  1. テスタビリティの高い
 GoのAPIサーバを開発しよう
 SWET Automation Test Go Team

  2. ハンズオンのゴール • このハンズオンではTestabilityの低いGoのAPI Serverから、 Testabilityの高い設計へのリファクタリングを行います
 • その過程でTestabilityの高い設計や、Test double等のテクニックな どを習得することができます
 •

    講義だけではなく、*Codelabを用いることにより、実際にコードを書 きながら体験することができます。
 *Codelab https://codelabs.developers.google.com/
  3. 目次 • Chapter 1: 
 • Testabilityについて
 • アプリケーションをを動かしてみよう (Codelab)


    • Chapter 2
 • アーキテクチャとTestabilityについて
 • アプリケーションをリファクタしてみよう (Codelab)
 • Chapter 3
 • Test Doubleについて 
 • テストのサンプルを書いてみよう (Codelab)
 • まとめ

  4. 注意事項 • ハンズオンに参加するには以下の環境が構築されたPCが必要で す
 • Go1.13 / Docker1.19 / git

    / make
 • 休憩は演習中に各自自由にとってください

  5. 講師紹介 • 氏名:伊藤 瑛(@akito0107)
 • 所属:DeNA SWETグループ
 
 • 業務内容


    • Go言語プロダクトの開発効率化と品質向上
 • ゲーム基盤の運用開発
 • etc...

  6. 講師紹介 • 氏名:金子淳貴(@theoden9014)
 • 所属:DeNA SWETグループ
 
 • 業務内容:
 •

    Go言語プロダクトの開発効率化と品質向上
 • ゲーム開発プロセス改善
 • etc...

  7. ハンズオンの流れ • このハンズオンでは、スライドによる講義とCodelabによる実習を繰 り返す形式で行います。
 • Codelabでは、手元でアプリケーションを実装しながらリファクタやテ ストの手法を学んでいただきます。


  8. Chapter 1. Testabilityとは

  9. Chapter 1 • はじめに
 • Testabilityの高いコードとは
 • リファクタリングについて
 • Codelab


  10. はじめに • このチャプターでは、Testabilityとはなにかということについて、少し 掘り下げて考えてみます
 
 • Codelabでは、サンプルアプリケーションのビルドと、次のチャプター 以降で行うリファクタリングの準備を行います
 


  11. Testabilityとは • 「テスト容易性とは可視性と操作性である」
 「ソフトウェアの動作を監視したり,状態を操作したりする機能を組 み込めば,よりテストが容易になる。」
 セム ケイナー,ジャームズ バック,ブレット ペティコード. ソフトウェアテスト293の鉄則

    (Japanese Edition) より
 
 => システムの「状態」を操作・シュミレートできるかがTestabilityの1 つの要素
 
 
 *ISO/IEC 25010などの他の定義もあります また、「試験性」と訳されることもあります。
  12. API Serverで考えてみる • API Server本体はStateless(=状態を持たない)な実装にすることが Best Practice
 
 • API

    Serverにおける状態は
 DBや外部Serviceに保存・依存する

  13. 例) • ユーザを登録するAPI
 • email addressの重複は許さない仕様
 
 • Testの際に再現すべき状態
 •

    email addressが重複するような状態を作り出してtestする必要 がある
 • Test時にこの状態をどれだけ簡単に安定して
 作り出せるか

  14. ハンズオンサンプルアプリケーション • ハンズオンではユーザ登録のendpointを持つAPIを題材にします
 
 • 最初は全てのlogicが1つのhandler関数に書かれているversionで す
 
 • リファクタリングを通じ、Test上でeasyに内部状態を操作できるよう

    にしていきましょう

  15. ハンズオンのゴール (1) • 一番最初のコード
 • すべての機能が1つの関数に
 • Testを書くときはすべての機能を結合してTestを書かなければい けない
 •

    Testに必要な状態をDBに
 作り出す必要がある

  16. ハンズオンのゴール (2) • それぞれを機能ごとにコンポーネントに分割
 • Testを書くときは機能にフォーカスしてTestを書ける
 • 「解像度の高いテスト」を書ける
 • Testに必要な状態をコード上で簡単にシュミレートできる


  17. リファクタリングについて • 「ソフトウェアの外部の振る舞いを保ったままで、内部の構造を改 善していく作業」
 Martin Fawler. 新装版 リファクタリング 既存のコードを安全に改善する より


    • APIの外側からの振る舞いを担保する仕組みが必要
 (Testが必須)
 • 今回は簡単なAPI Testを実装し、振る舞いの担保を行ってみます
 • API Testの実装の際に、Data(=状態)をどう扱うかを観察してみ てください。

  18. Codelab • このCodelabでは、サンプルアプリケーションのダウンロード・ビルド を行います。
 
 • アプリケーションの仕様把握と、次のChapterで行うリファクタリング のために、簡単なE2Eテストを実装してみましょう。
 
 •

    https://dena.github.io/codelabs/testable-architecture-with-go-pa rt1/#0

  19. Chapter 2. Testabilityとアーキテクチャ

  20. Chapter 2 • はじめに
 • WebAPIのアーキテクチャについて
 • 依存の管理について
 • Codelab


  21. はじめに • Chapter1ではすべての処理が一つの関数にまとまっているAPI サーバを見ました。
 • このChapterでは、Testabilityが高いアーキテクチャやその実装ポ イントを紹介します。
 • CodelabではTestabilityが高いアーキテクチャへのリファクタを行い ます。


  22. ソフトウェアにおける問題点 • ソフトウェアは作って終わりではない
 • 再現テストがやりやすい


  23. ソフトウェアアーキテクチャ 目的:
 「ソフトウェアアーキテクチャの目的は、求められるシステムを構築・保 守するために必要な人材を最小限に抑えること」
 Robert C.Martin,角 征典,高木 正弘. Clean Architecture 達人に学ぶソフトウェアの構造と設計

    (Japanese Edition) 
 
 Testabilityに着目してWeb APIのアーキテクチャを見てみる

  24. Web APIのアーキテクチャ • 3層アーキテクチャ
 • (Rails的) MVC
 • DDD
 


  25. Web APIのアーキテクチャ • 3層アーキテクチャ
 • (Rails的) MVC
 • DDD
 


    ⬅ 今日はこれを紹介

  26. 3層アーキテクチャ • webシステムを責務により3つの層に分けて実装するアーキテク チャ
 • プレゼンテーション層
 • ビジネスロジック層
 • データアクセス層


  27. 3層アーキテクチャ実装の2つのポイント • (1) ひとつ下の層にのみ依存する
 • 例えば、presentationはBusiness Logicにのみ
 依存する。
 presentationが直接Data Access


    を呼び出してはいけない
 • (2) それぞれの層は具象的な実装ではなく、
 抽象に依存する
 • Goの場合はinterfaceを使う

  28. Dependency Inversion • 依存関係逆転(の原則)
 • Testabilityを担保する上で重要な概念の1つ
 • 「ソースコードの依存関係が抽象だけを参照しているもの」
 (Clean Architecture

    11章より)
 • Goのsample codeを見てみよう

  29. Dependency Inversion func main() { str, err := PrependHello(os.Stdin) if

    err != nil { log.Fatal(err) } fmt.Println(str) } // os.Fileという具体的な型に依存している func PrependHello(f *os.File) (string, error) { var buf bytes.Buffer if _, err := io.Copy(&buf, f); err != nil { return "", err } return "hello " + buf.String(), nil } • Keyboard入力に対して helloを追加するコード • Keyboard以外にはどう対 応する? • PrependHelloをどうテスト するか?
  30. Dependency Inversion func PrependHello(r io.Reader) (string, error) { var buf

    bytes.Buffer if _, err := io.Copy(&buf, r); err != nil { return "", err } return "hello " + buf.String(), nil } • 具体的な実装ではなく抽象(interface)に依存する
  31. Dependency Inversion func TestPrependHello(t *testing.T) { actual, err := PrependHello(bytes.NewBufferString("test"))

    if err != nil { t.Fatal(err) } if actual != "hello test" { t.Errorf("expected 'hello test' but actual %s", actual) } } • Test時にはos.Stdinの代わりにbytes.Bufferを使える • (もちろんTest以外にもいろいろなioに対応できるというメリットがある)
  32. Dependency Inversion • interfaceに依存するようにしよう
 


  33. 3層アーキテクチャ • それぞれの層がinterfaceを介して
 依存するように
 


  34. プレゼンテーション層(コントローラー) • クライアントからの呼び出しを吸収し、適切なビジネスロジックを呼 び出し、クライアントへ適切なデータ形式で応答する層
 • APIクライアントとのやり取りを実装し隠蔽する
 • ex) Httpの場合、request /

    responseの加工など
 • 通常interfaceは用意しない
 (依存相手がいない)

  35. ビジネスロジック層(ユースケース) • APIサーバのメインとなるロジックが実装されるアプリケーションの メインの層
 • ビジネスロジックを実装し隠蔽する
 • テストを重点的に書きたい層


  36. データアクセス層(リポジトリ) • データの操作(SQL等)や外部APIの連携を行う層
 • データへのアクセスを実装し隠蔽する


  37. Codelab • 紹介したアーキテクチャにrefactoringしましょう
 • https://dena.github.io/codelabs/testable-architecture-with-go-pa rt2/#1


  38. Chapter 3. Test Double

  39. Chapter 3 • はじめに
 • Test Doubleについて
 • Codelab


  40. はじめに • Chapter2で依存を切り離したことにより、test時に自由に
 ”モック”を使うことができるようになった
 • ここでは”モック”の種類とGoでの実装パターンを紹介します
 • Codelabでは”モック”を用いてtestを実装してみます


  41. テストダブル • テスト対象の依存しているコンポーネントと置き換わり、本物そっく りに振る舞う代役(=double)
 • いわゆるmock, stubはテストダブルの一種
 • 間接入力と間接出力の制御および可視化を実現する


  42. (参考) 間接入力・間接出力 • 間接入力
 • 依存コンポーネントからテスト対象への入力
 • 例: RepositoryからDBのデータ読み出し
 •

    間接出力
 • テスト対象から依存コンポーネントへの出力
 • 例: RepositoryからDBへのデータ書き込み

  43. 間接入力・間接出力とTest double Test対象コード
 間接入力・間接出 力をコントロールす るためにはDBを操 作する必要がある


  44. 間接入力・間接出力とTest double Test対象コード
 ソースコード上で間 接入力・出力をコン トロール可能
 特に細かいエラー ケースの制御など が楽


  45. TestにおけるDBについて • 「When there is any way to test without

    a database, test without the database!」
 Meszaros, Gerard. xUnit Test Patterns: Refactoring Test Code (Addison-Wesley Signature Series (Fowler)) . Pearson Education. より
 • Test Dataのsetup / cleanupや並列化の問題など...
 • Docker / Libraryの充実により、上記の問題の一部は解決されつ つあるが、細かいロジックのハンドリングなどはDBを使わずにTest できると楽
 • 詳しくはTechCon2021の「自動テストのないプロダクトの開発効 率化への道」を見よう

  46. テストダブル5つのパターン • スタブ
 • モック
 • スパイ
 • フェイク
 •

    ダミー

  47. スタブ • テスト対象への間接入力を、事前に定義した任意の値に置き換え る
 • 依存コンポーネントをスタブに差し替えることで、特定の要件を満た したテストを実現することができる


  48. スタブ実装例: 現在時刻の差し替え // 差し替え対象をtestからアクセス可能な変数として宣言する var Now func() time.Time = time.Now

    func AddDurationToNow(h time.Duration) time.Time { return Now().Add(h) } Test対象コード
  49. スタブ実装例: 現在時刻の差し替え Testコード func TestAddDurationToNow(t *testing.T) { currentNow := Now

    Now = func() time.Time { tm, err := time.Parse(time.RFC3339, "2021-03-06T15:00:00Z") if err != nil { t.Fatal(err) } return tm } defer func() { Now = currentNow }() expected, err := time.Parse(time.RFC3339, "2021-03-06T16:00:00Z") if err != nil { t.Fatal(err) } actual := AddDurationToNow(1 * time.Hour) if !actual.Equal(expected) { t.Errorf("expected: %s but actual: %s", expected, actual) } }
  50. モック • 事前に間接出力の期待値を設定し、テスト実行時に実際の出力と 期待値を検証する
 • ex) gomock
 • https://github.com/golang/mock


  51. モック例: gomockのREADMEから抜粋 mockが期待する変数の値 を定義する。期待する値以 外で呼ばれたらtestがfailす る

  52. スパイ • テスト対象の間接出力を記録し、後からテストコードでその出力を 検証できるようにする
 • スパイではテストコード側で間接出力の検証を行うが、モックはモッ クオブジェクト自身で期待値との検証を行う点が異なる


  53. スパイ実装例: httptest.ResponseRecorder func main() { handler := func(w http.ResponseWriter, r

    *http.Request) { io.WriteString(w, "<html><body>Hello World!</body></html>") } req := httptest.NewRequest("GET", "http://example.com/foo", nil) w := httptest.NewRecorder() handler(w, req) resp := w.Result() body, _ := ioutil.ReadAll(resp.Body) fmt.Println(resp.StatusCode) fmt.Println(resp.Header.Get("Content-Type")) fmt.Println(string(body)) } https://play.golang.org/p/y_M3RA0YUDB
 

  54. フェイク • 実際のコンポーネントと同等か、それに極めて近い挙動を持つ
 • go-cloudのin-memoryモードなど
 • https://github.com/google/go-cloud 
 • https://gocloud.dev/howto/blob/#local-ctor


    • https://gocloud.dev/howto/docstore/#mem

  55. ダミー • テストに影響を与えない代替オブジェクト
 • テストには関係ないが、テスト対象の生成時やメソッドのパラメータ としてオブジェクトが必要なときに使用する
 • 例: nullオブジェクト


  56. Codelab • Chapter2で用意したそれぞれのBusiness Logicのlayerにtestを追 加しよう
 • repositoryをtest doubleにする
 • https://dena.github.io/codelabs/testable-architecture-with-go-pa

    rt3/#1

  57. (参考) maintainableなtest codeへ • 今回Codelabで実装しcodeは重複が多く、保守性(=maintainability) が高いとは言い切れない
 • 実際にはTable Drive TestingやTest

    Helperの切り出しなどを行っ て、Maintainabilityの高いTest Codeへ修正していく作業がある
 • Test CodeのMaintainabilityも気にするようにしよう

  58. 実際の現場では • 依存が複雑になるので、test doubleは自動生成しちゃうことが多い です
 • DBアクセスのモジュールがGlobalなscopeで宣言されていて、test 時に差し替え不可能みたいな実装になっていることもまちまち...
 • 逆に簡単なやつは特にlayerなどを分けないで、httptestを使って

    API Testだけで済ませることもあります
 • フェイクが提供されていることは稀。信頼されるフェイクがあったら 使ったほうが良い。

  59. 本日のまとめ • Testabilityとは
 • リファクタリングについて
 • Testabilityを担保したアーキテクチャについて
 • Test Doubleについて


  60. References • セム ケイナー,ジャームズ バック,ブレット ペティコード. ソフトウェア テスト293の鉄則 (Japanese Edition)


    • Martin Fawler. 新装版 リファクタリング 既存のコードを安全に改善 する
 • Robert C.Martin,角 征典,高木 正弘. Clean Architecture 達人 に学ぶソフトウェアの構造と設計 (Japanese Edition) 
 • Meszaros, Gerard. xUnit Test Patterns: Refactoring Test Code (Addison-Wesley Signature Series (Fowler)) . 

  61. ご清聴ありがとうございました