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

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

Akito0107
March 06, 2021

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

Akito0107

March 06, 2021
Tweet

More Decks by Akito0107

Other Decks in Programming

Transcript

  1. テスタビリティの高い

    GoのAPIサーバを開発しよう

    SWET Automation Test Go Team

    View Slide

  2. ハンズオンのゴール
    • このハンズオンではTestabilityの低いGoのAPI Serverから、
    Testabilityの高い設計へのリファクタリングを行います

    • その過程でTestabilityの高い設計や、Test double等のテクニックな
    どを習得することができます

    • 講義だけではなく、*Codelabを用いることにより、実際にコードを書
    きながら体験することができます。

    *Codelab
    https://codelabs.developers.google.com/

    View Slide

  3. 目次
    • Chapter 1: 

    • Testabilityについて

    • アプリケーションをを動かしてみよう (Codelab)

    • Chapter 2

    • アーキテクチャとTestabilityについて

    • アプリケーションをリファクタしてみよう (Codelab)

    • Chapter 3

    • Test Doubleについて 

    • テストのサンプルを書いてみよう (Codelab)

    • まとめ


    View Slide

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

    • Go1.13 / Docker1.19 / git / make

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


    View Slide

  5. 講師紹介
    • 氏名:伊藤 瑛(@akito0107)

    • 所属:DeNA SWETグループ


    • 業務内容

    • Go言語プロダクトの開発効率化と品質向上

    • ゲーム基盤の運用開発

    • etc...


    View Slide

  6. 講師紹介
    • 氏名:金子淳貴(@theoden9014)

    • 所属:DeNA SWETグループ


    • 業務内容:

    • Go言語プロダクトの開発効率化と品質向上

    • ゲーム開発プロセス改善

    • etc...


    View Slide

  7. ハンズオンの流れ
    • このハンズオンでは、スライドによる講義とCodelabによる実習を繰
    り返す形式で行います。

    • Codelabでは、手元でアプリケーションを実装しながらリファクタやテ
    ストの手法を学んでいただきます。


    View Slide

  8. Chapter 1. Testabilityとは

    View Slide

  9. Chapter 1
    • はじめに

    • Testabilityの高いコードとは

    • リファクタリングについて

    • Codelab


    View Slide

  10. はじめに
    • このチャプターでは、Testabilityとはなにかということについて、少し
    掘り下げて考えてみます


    • Codelabでは、サンプルアプリケーションのビルドと、次のチャプター
    以降で行うリファクタリングの準備を行います


    View Slide

  11. Testabilityとは
    • 「テスト容易性とは可視性と操作性である」

    「ソフトウェアの動作を監視したり,状態を操作したりする機能を組
    み込めば,よりテストが容易になる。」

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


    => システムの「状態」を操作・シュミレートできるかがTestabilityの1
    つの要素



    *ISO/IEC 25010などの他の定義もあります
    また、「試験性」と訳されることもあります。

    View Slide

  12. API Serverで考えてみる
    • API Server本体はStateless(=状態を持たない)な実装にすることが
    Best Practice


    • API Serverにおける状態は

    DBや外部Serviceに保存・依存する


    View Slide

  13. 例)
    • ユーザを登録するAPI

    • email addressの重複は許さない仕様


    • Testの際に再現すべき状態

    • email addressが重複するような状態を作り出してtestする必要
    がある

    • Test時にこの状態をどれだけ簡単に安定して

    作り出せるか


    View Slide

  14. ハンズオンサンプルアプリケーション
    • ハンズオンではユーザ登録のendpointを持つAPIを題材にします


    • 最初は全てのlogicが1つのhandler関数に書かれているversionで
    す


    • リファクタリングを通じ、Test上でeasyに内部状態を操作できるよう
    にしていきましょう


    View Slide

  15. ハンズオンのゴール (1)
    • 一番最初のコード

    • すべての機能が1つの関数に

    • Testを書くときはすべての機能を結合してTestを書かなければい
    けない

    • Testに必要な状態をDBに

    作り出す必要がある


    View Slide

  16. ハンズオンのゴール (2)
    • それぞれを機能ごとにコンポーネントに分割

    • Testを書くときは機能にフォーカスしてTestを書ける

    • 「解像度の高いテスト」を書ける

    • Testに必要な状態をコード上で簡単にシュミレートできる


    View Slide

  17. リファクタリングについて
    • 「ソフトウェアの外部の振る舞いを保ったままで、内部の構造を改
    善していく作業」

    Martin Fawler. 新装版 リファクタリング 既存のコードを安全に改善する より

    • APIの外側からの振る舞いを担保する仕組みが必要

    (Testが必須)

    • 今回は簡単なAPI Testを実装し、振る舞いの担保を行ってみます

    • API Testの実装の際に、Data(=状態)をどう扱うかを観察してみ
    てください。


    View Slide

  18. Codelab
    • このCodelabでは、サンプルアプリケーションのダウンロード・ビルド
    を行います。


    • アプリケーションの仕様把握と、次のChapterで行うリファクタリング
    のために、簡単なE2Eテストを実装してみましょう。


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


    View Slide

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

    View Slide

  20. Chapter 2
    • はじめに

    • WebAPIのアーキテクチャについて

    • 依存の管理について

    • Codelab


    View Slide

  21. はじめに
    • Chapter1ではすべての処理が一つの関数にまとまっているAPI
    サーバを見ました。

    • このChapterでは、Testabilityが高いアーキテクチャやその実装ポ
    イントを紹介します。

    • CodelabではTestabilityが高いアーキテクチャへのリファクタを行い
    ます。


    View Slide

  22. ソフトウェアにおける問題点
    ● ソフトウェアは作って終わりではない

    ● 再現テストがやりやすい


    View Slide

  23. ソフトウェアアーキテクチャ
    目的:

    「ソフトウェアアーキテクチャの目的は、求められるシステムを構築・保
    守するために必要な人材を最小限に抑えること」

    Robert C.Martin,角 征典,高木 正弘. Clean Architecture 達人に学ぶソフトウェアの構造と設計
    (Japanese Edition) 


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


    View Slide

  24. Web APIのアーキテクチャ
    • 3層アーキテクチャ

    • (Rails的) MVC

    • DDD


    View Slide

  25. Web APIのアーキテクチャ
    • 3層アーキテクチャ

    • (Rails的) MVC

    • DDD


    ⬅ 今日はこれを紹介


    View Slide

  26. 3層アーキテクチャ
    • webシステムを責務により3つの層に分けて実装するアーキテク
    チャ

    • プレゼンテーション層

    • ビジネスロジック層

    • データアクセス層


    View Slide

  27. 3層アーキテクチャ実装の2つのポイント
    • (1) ひとつ下の層にのみ依存する

    • 例えば、presentationはBusiness Logicにのみ

    依存する。

    presentationが直接Data Access

    を呼び出してはいけない

    • (2) それぞれの層は具象的な実装ではなく、

    抽象に依存する

    • Goの場合はinterfaceを使う


    View Slide

  28. Dependency Inversion
    • 依存関係逆転(の原則)

    • Testabilityを担保する上で重要な概念の1つ

    • 「ソースコードの依存関係が抽象だけを参照しているもの」

    (Clean Architecture 11章より)

    • Goのsample codeを見てみよう


    View Slide

  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をどうテスト
    するか?

    View Slide

  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)に依存する

    View Slide

  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に対応できるというメリットがある)

    View Slide

  32. Dependency Inversion
    • interfaceに依存するようにしよう


    View Slide

  33. 3層アーキテクチャ
    • それぞれの層がinterfaceを介して

    依存するように


    View Slide

  34. プレゼンテーション層(コントローラー)
    • クライアントからの呼び出しを吸収し、適切なビジネスロジックを呼
    び出し、クライアントへ適切なデータ形式で応答する層

    • APIクライアントとのやり取りを実装し隠蔽する

    • ex) Httpの場合、request / responseの加工など

    • 通常interfaceは用意しない

    (依存相手がいない)


    View Slide

  35. ビジネスロジック層(ユースケース)
    ● APIサーバのメインとなるロジックが実装されるアプリケーションの
    メインの層

    ● ビジネスロジックを実装し隠蔽する

    ● テストを重点的に書きたい層


    View Slide

  36. データアクセス層(リポジトリ)
    ● データの操作(SQL等)や外部APIの連携を行う層

    ● データへのアクセスを実装し隠蔽する


    View Slide

  37. Codelab
    • 紹介したアーキテクチャにrefactoringしましょう

    • https://dena.github.io/codelabs/testable-architecture-with-go-pa
    rt2/#1


    View Slide

  38. Chapter 3. Test Double

    View Slide

  39. Chapter 3
    • はじめに

    • Test Doubleについて

    • Codelab


    View Slide

  40. はじめに
    • Chapter2で依存を切り離したことにより、test時に自由に

    ”モック”を使うことができるようになった

    • ここでは”モック”の種類とGoでの実装パターンを紹介します

    • Codelabでは”モック”を用いてtestを実装してみます


    View Slide

  41. テストダブル
    • テスト対象の依存しているコンポーネントと置き換わり、本物そっく
    りに振る舞う代役(=double)

    • いわゆるmock, stubはテストダブルの一種

    • 間接入力と間接出力の制御および可視化を実現する


    View Slide

  42. (参考) 間接入力・間接出力
    • 間接入力

    • 依存コンポーネントからテスト対象への入力

    • 例: RepositoryからDBのデータ読み出し

    • 間接出力

    • テスト対象から依存コンポーネントへの出力

    • 例: RepositoryからDBへのデータ書き込み


    View Slide

  43. 間接入力・間接出力とTest double
    Test対象コード

    間接入力・間接出
    力をコントロールす
    るためにはDBを操
    作する必要がある


    View Slide

  44. 間接入力・間接出力とTest double
    Test対象コード

    ソースコード上で間
    接入力・出力をコン
    トロール可能

    特に細かいエラー
    ケースの制御など
    が楽


    View Slide

  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の「自動テストのないプロダクトの開発効
    率化への道」を見よう


    View Slide

  46. テストダブル5つのパターン
    • スタブ

    • モック

    • スパイ

    • フェイク

    • ダミー


    View Slide

  47. スタブ
    • テスト対象への間接入力を、事前に定義した任意の値に置き換え
    る

    • 依存コンポーネントをスタブに差し替えることで、特定の要件を満た
    したテストを実現することができる


    View Slide

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

    View Slide

  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)
    }
    }

    View Slide

  50. モック
    • 事前に間接出力の期待値を設定し、テスト実行時に実際の出力と
    期待値を検証する

    • ex) gomock

    • https://github.com/golang/mock


    View Slide

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

    View Slide

  52. スパイ
    • テスト対象の間接出力を記録し、後からテストコードでその出力を
    検証できるようにする

    • スパイではテストコード側で間接出力の検証を行うが、モックはモッ
    クオブジェクト自身で期待値との検証を行う点が異なる


    View Slide

  53. スパイ実装例: httptest.ResponseRecorder
    func main() {
    handler := func(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "Hello World!")
    }
    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


    View Slide

  54. フェイク
    • 実際のコンポーネントと同等か、それに極めて近い挙動を持つ

    • go-cloudのin-memoryモードなど

    • https://github.com/google/go-cloud 

    • https://gocloud.dev/howto/blob/#local-ctor

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


    View Slide

  55. ダミー
    • テストに影響を与えない代替オブジェクト

    • テストには関係ないが、テスト対象の生成時やメソッドのパラメータ
    としてオブジェクトが必要なときに使用する

    • 例: nullオブジェクト


    View Slide

  56. Codelab
    • Chapter2で用意したそれぞれのBusiness Logicのlayerにtestを追
    加しよう

    • repositoryをtest doubleにする

    • https://dena.github.io/codelabs/testable-architecture-with-go-pa
    rt3/#1


    View Slide

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

    • 実際にはTable Drive TestingやTest Helperの切り出しなどを行っ
    て、Maintainabilityの高いTest Codeへ修正していく作業がある

    • Test CodeのMaintainabilityも気にするようにしよう


    View Slide

  58. 実際の現場では
    • 依存が複雑になるので、test doubleは自動生成しちゃうことが多い
    です

    • DBアクセスのモジュールがGlobalなscopeで宣言されていて、test
    時に差し替え不可能みたいな実装になっていることもまちまち...

    • 逆に簡単なやつは特にlayerなどを分けないで、httptestを使って
    API Testだけで済ませることもあります

    • フェイクが提供されていることは稀。信頼されるフェイクがあったら
    使ったほうが良い。


    View Slide

  59. 本日のまとめ
    • Testabilityとは

    • リファクタリングについて

    • Testabilityを担保したアーキテクチャについて

    • Test Doubleについて


    View Slide

  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)) . 


    View Slide

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


    View Slide