Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

ハンズオンのゴール • このハンズオンではTestabilityの低いGoのAPI Serverから、 Testabilityの高い設計へのリファクタリングを行います
 • その過程でTestabilityの高い設計や、Test double等のテクニックな どを習得することができます
 • 講義だけではなく、*Codelabを用いることにより、実際にコードを書 きながら体験することができます。
 *Codelab https://codelabs.developers.google.com/

Slide 3

Slide 3 text

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


Slide 4

Slide 4 text

注意事項 • ハンズオンに参加するには以下の環境が構築されたPCが必要で す
 • Go1.13 / Docker1.19 / git / make
 • 休憩は演習中に各自自由にとってください


Slide 5

Slide 5 text

講師紹介 • 氏名:伊藤 瑛(@akito0107)
 • 所属:DeNA SWETグループ
 
 • 業務内容
 • Go言語プロダクトの開発効率化と品質向上
 • ゲーム基盤の運用開発
 • etc...


Slide 6

Slide 6 text

講師紹介 • 氏名:金子淳貴(@theoden9014)
 • 所属:DeNA SWETグループ
 
 • 業務内容:
 • Go言語プロダクトの開発効率化と品質向上
 • ゲーム開発プロセス改善
 • etc...


Slide 7

Slide 7 text

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


Slide 8

Slide 8 text

Chapter 1. Testabilityとは

Slide 9

Slide 9 text

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


Slide 10

Slide 10 text

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


Slide 11

Slide 11 text

Testabilityとは • 「テスト容易性とは可視性と操作性である」
 「ソフトウェアの動作を監視したり,状態を操作したりする機能を組 み込めば,よりテストが容易になる。」
 セム ケイナー,ジャームズ バック,ブレット ペティコード. ソフトウェアテスト293の鉄則 (Japanese Edition) より
 
 => システムの「状態」を操作・シュミレートできるかがTestabilityの1 つの要素
 
 
 *ISO/IEC 25010などの他の定義もあります また、「試験性」と訳されることもあります。

Slide 12

Slide 12 text

API Serverで考えてみる • API Server本体はStateless(=状態を持たない)な実装にすることが Best Practice
 
 • API Serverにおける状態は
 DBや外部Serviceに保存・依存する


Slide 13

Slide 13 text

例) • ユーザを登録するAPI
 • email addressの重複は許さない仕様
 
 • Testの際に再現すべき状態
 • email addressが重複するような状態を作り出してtestする必要 がある
 • Test時にこの状態をどれだけ簡単に安定して
 作り出せるか


Slide 14

Slide 14 text

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


Slide 15

Slide 15 text

ハンズオンのゴール (1) • 一番最初のコード
 • すべての機能が1つの関数に
 • Testを書くときはすべての機能を結合してTestを書かなければい けない
 • Testに必要な状態をDBに
 作り出す必要がある


Slide 16

Slide 16 text

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


Slide 17

Slide 17 text

リファクタリングについて • 「ソフトウェアの外部の振る舞いを保ったままで、内部の構造を改 善していく作業」
 Martin Fawler. 新装版 リファクタリング 既存のコードを安全に改善する より
 • APIの外側からの振る舞いを担保する仕組みが必要
 (Testが必須)
 • 今回は簡単なAPI Testを実装し、振る舞いの担保を行ってみます
 • API Testの実装の際に、Data(=状態)をどう扱うかを観察してみ てください。


Slide 18

Slide 18 text

Codelab • このCodelabでは、サンプルアプリケーションのダウンロード・ビルド を行います。
 
 • アプリケーションの仕様把握と、次のChapterで行うリファクタリング のために、簡単なE2Eテストを実装してみましょう。
 
 • https://dena.github.io/codelabs/testable-architecture-with-go-pa rt1/#0


Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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


Slide 21

Slide 21 text

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


Slide 22

Slide 22 text

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


Slide 23

Slide 23 text

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


Slide 24

Slide 24 text

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


Slide 25

Slide 25 text

Web APIのアーキテクチャ • 3層アーキテクチャ
 • (Rails的) MVC
 • DDD
 
 ⬅ 今日はこれを紹介


Slide 26

Slide 26 text

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


Slide 27

Slide 27 text

3層アーキテクチャ実装の2つのポイント • (1) ひとつ下の層にのみ依存する
 • 例えば、presentationはBusiness Logicにのみ
 依存する。
 presentationが直接Data Access
 を呼び出してはいけない
 • (2) それぞれの層は具象的な実装ではなく、
 抽象に依存する
 • Goの場合はinterfaceを使う


Slide 28

Slide 28 text

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


Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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


Slide 33

Slide 33 text

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


Slide 34

Slide 34 text

プレゼンテーション層(コントローラー) • クライアントからの呼び出しを吸収し、適切なビジネスロジックを呼 び出し、クライアントへ適切なデータ形式で応答する層
 • APIクライアントとのやり取りを実装し隠蔽する
 • ex) Httpの場合、request / responseの加工など
 • 通常interfaceは用意しない
 (依存相手がいない)


Slide 35

Slide 35 text

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


Slide 36

Slide 36 text

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


Slide 37

Slide 37 text

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


Slide 38

Slide 38 text

Chapter 3. Test Double

Slide 39

Slide 39 text

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


Slide 40

Slide 40 text

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


Slide 41

Slide 41 text

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


Slide 42

Slide 42 text

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


Slide 43

Slide 43 text

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


Slide 44

Slide 44 text

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


Slide 45

Slide 45 text

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


Slide 46

Slide 46 text

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


Slide 47

Slide 47 text

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


Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

スタブ実装例: 現在時刻の差し替え 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) } }

Slide 50

Slide 50 text

モック • 事前に間接出力の期待値を設定し、テスト実行時に実際の出力と 期待値を検証する
 • ex) gomock
 • https://github.com/golang/mock


Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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


Slide 53

Slide 53 text

スパイ実装例: 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
 


Slide 54

Slide 54 text

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


Slide 55

Slide 55 text

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


Slide 56

Slide 56 text

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


Slide 57

Slide 57 text

(参考) maintainableなtest codeへ • 今回Codelabで実装しcodeは重複が多く、保守性(=maintainability) が高いとは言い切れない
 • 実際にはTable Drive TestingやTest Helperの切り出しなどを行っ て、Maintainabilityの高いTest Codeへ修正していく作業がある
 • Test CodeのMaintainabilityも気にするようにしよう


Slide 58

Slide 58 text

実際の現場では • 依存が複雑になるので、test doubleは自動生成しちゃうことが多い です
 • DBアクセスのモジュールがGlobalなscopeで宣言されていて、test 時に差し替え不可能みたいな実装になっていることもまちまち...
 • 逆に簡単なやつは特にlayerなどを分けないで、httptestを使って API Testだけで済ませることもあります
 • フェイクが提供されていることは稀。信頼されるフェイクがあったら 使ったほうが良い。


Slide 59

Slide 59 text

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


Slide 60

Slide 60 text

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


Slide 61

Slide 61 text

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