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

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

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Akito0107 Akito0107
March 06, 2021

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

Avatar for Akito0107

Akito0107

March 06, 2021
Tweet

More Decks by Akito0107

Other Decks in Programming

Transcript

  1. 目次 • Chapter 1: 
 • Testabilityについて
 • アプリケーションをを動かしてみよう (Codelab)


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

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


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

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

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

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

    (Japanese Edition) より
 
 => システムの「状態」を操作・シュミレートできるかがTestabilityの1 つの要素
 
 
 *ISO/IEC 25010などの他の定義もあります また、「試験性」と訳されることもあります。
  5. 例) • ユーザを登録するAPI
 • email addressの重複は許さない仕様
 
 • Testの際に再現すべき状態
 •

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

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


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

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


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

  8. 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をどうテスト するか?
  9. 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)に依存する
  10. 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に対応できるというメリットがある)
  11. (参考) 間接入力・間接出力 • 間接入力
 • 依存コンポーネントからテスト対象への入力
 • 例: RepositoryからDBのデータ読み出し
 •

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

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

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

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

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

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