Slide 1

Slide 1 text

13年続くレガシーサービスを安全に リリースし続けるためのテスト戦略 RAKUS Meetup Osaka #8 株式会社ラクス 吉元和仁

Slide 2

Slide 2 text

自己紹介 • 所属・氏名 • 株式会社ラクス 第四開発部 • 吉元 和仁(よしもとかずひと) • 仕事 • 2018年10月より自社サービス「配配メール」の開発担当

Slide 3

Slide 3 text

配配メール について • BtoB向けメールマーケティング・サービス • 中小企業のお客様の多種多様な業務に合わせて4つのプラン、 8つのオプション機能を提供

Slide 4

Slide 4 text

2019年10月に新プラン 「配配メールBridge」をリリース

Slide 5

Slide 5 text

話すこと • 13年続く自動テストの無いウェブサービスで、レガシーと向 き合い、安全にリリースし続けるためのテスト戦略とテスト ツールの活用事例を紹介。

Slide 6

Slide 6 text

レガシーサービスの定義

Slide 7

Slide 7 text

レガシーサービスの定義 • テストがないコードはレガシーコード だ! (書籍「レガシーコード 改善ガイド」より)

Slide 8

Slide 8 text

レガシーサービスの定義 • テストされていない/テストできないコード • 柔軟性のないコード • 技術的負債を抱え込んでいるコード (書籍「レガシーソフトウェア改善ガイド」より)

Slide 9

Slide 9 text

レガシーシステムの改善方針 • リファクタリング • リアーキテクティング • ビッグ・リライト (書籍「レガシーソフトウェア改善ガイド」より)

Slide 10

Slide 10 text

我々のレガシーサービス

Slide 11

Slide 11 text

我々のレガシーサービス • 2007年5月にサービス提供を開始 • 長年の機能追加により、ユーザへの価値提供を増大 • 一方で、コードの複雑性も増大

Slide 12

Slide 12 text

メソッドが組み合わせ爆発 • プログラム修正がコアロジックに集中 • 増え過ぎた引数 • 引数の論理演算($midOrSid) • No グローバル変数, No life (https://youtu.be/Q4gTV4r0zRs)

Slide 13

Slide 13 text

BigQuery生成ロジック • 複数のメソッドが1つのSQLを生成するロジック • サービスの成長とともにSQLが巨大化 • 各メソッドは責務崩壊 • つじつま合わせのハッキングコード • 新人泣かせのBigQuery(SQL)生成ロジックへと成長

Slide 14

Slide 14 text

リファクタリングしたい

Slide 15

Slide 15 text

レガシーコードのジレンマ • リファクタリングを行うとき、最初にすることは常に同じで す。対象となるコードについてきちんとしたテスト群を作り 上げることです。テストは不可欠です。 (書籍「リファクタリング」より)

Slide 16

Slide 16 text

レガシーコードのジレンマ • 自動テストの仕組みも文化もないサービス • ドキュメントが無く、ソースコードが仕様 • テストを作るためには、まずはじめに複雑度の高いロジック を理解することから始めなければならない

Slide 17

Slide 17 text

開発チームの課題

Slide 18

Slide 18 text

開発チームの課題 • スター選手は新規サービス開発の最前線へ • 相次ぐコアメンバーの異動 • 開発チームの半数以上は若手エンジニア

Slide 19

Slide 19 text

開発チームの課題 • ソースコードが仕様 • 既存仕様の考慮漏れにより、開発終盤で設計が破綻 • 進捗90%の壁 • 納期のプレッシャーにより品質が低下 • 疲弊する開発チーム

Slide 20

Slide 20 text

計画の不確実性をなくしたい

Slide 21

Slide 21 text

計画の不確実性 • 楽観主義などの要因により、コストを低く見積もる傾向があ る。 書籍「人月の神話」より

Slide 22

Slide 22 text

計画の不確実性 • 見積りは、あくまで予測です。 • 予測を「ノルマ」にした途端、それを達成するための過負荷 な労働が生まれ、クオリティが下がってしまいます。 • 書籍「エンジニア組織論への招待」より

Slide 23

Slide 23 text

計画の不確実性 • エンジニアリングで重要なのは「どうしたら効率よく不確実 性を減らしていけるのか」という考え方なのです。 • 書籍「エンジニア組織論への招待」より

Slide 24

Slide 24 text

課題まとめ • 課題1 • 複雑度が高すぎるプロダクトコード • 課題2 • 計画の不確実性の問題

Slide 25

Slide 25 text

レガシーサービスを安全にリ リースし続けるための テスト戦略

Slide 26

Slide 26 text

課題まとめ(再掲) • 課題1 • 複雑度が高すぎるプロダクトコード • 課題2 • 計画の不確実性の問題

Slide 27

Slide 27 text

テストの7原則 1. テストは欠陥があることは示せるが、欠陥がないことは示せない 2. 全数テストは不可能 3. 早期テストで時間とコストを節約 4. 欠陥の偏在 5. 殺虫剤のパラドックスにご用心 6. テストは状況次第 7. 「バグゼロ」の落とし穴 (参考: http://jstqb.jp/dl/JSTQB-SyllabusFoundation_Version2018V31.J02.pdf)

Slide 28

Slide 28 text

テストの7原則 4. 欠陥の偏在 リリース前のテストで見つかる欠陥や運用時の故障の大部分は、 特定の少数モジュールに集中する。 テストの労力を集中させるために欠陥の偏在を予測し、テストや 運用での実際の観察結果に基づいてリスク分析を行う。

Slide 29

Slide 29 text

戦略① 安全にリリースし続ける ための重要機能テスト

Slide 30

Slide 30 text

重要機能テストとは • 我々の開発チームでは、代替手段がなく顧客影響が大きいバ グをクリティカルバグと呼ぶ。 • このバグを抽出するためのテストが重要機能テスト。 • 安全なリリースサイクルを回すためには、重要機能テストの 実施が不可欠。

Slide 31

Slide 31 text

テストの作成手順 1. サービスの有識者と協議の上で重要機能を抽出 2. 重要機能のテストケースを作成 3. テストを手動実行し、カバレッジ結果を取得 4. カバレッジ結果を参考に、テストケースを見直し

Slide 32

Slide 32 text

テストケースの作成手順 1. サービスの有識者と協議の上で重要機能を抽出 2. 重要機能のテストケースを作成 3. テストを手動実行し、カバレッジ結果を取得 4. カバレッジ結果を参考に、テストケースを見直し

Slide 33

Slide 33 text

手動テストのカバレッジって 取得できるの?

Slide 34

Slide 34 text

手動テストの カバレッジ取得について

Slide 35

Slide 35 text

利用するライブラリ • Xdebug (https://xdebug.org/ ) • PHP_CodeCoverage (Github: https://github.com/sebastianbergmann/php-code-coverage)

Slide 36

Slide 36 text

ライブラリのインストール • Xdebug • 公式ページ参照 • https://xdebug.org/docs/install • PHP_CodeCoverage composer require --dev phpunit/php-code-coverage

Slide 37

Slide 37 text

カバレッジログ 出力プログラムの準備

Slide 38

Slide 38 text

カバレッジログ出力プログラムを準備 •

Slide 39

Slide 39 text

カバレッジログ出力プログラムを準備 •

Slide 40

Slide 40 text

カバレッジログ出力プログラムを準備 •

Slide 41

Slide 41 text

カバレッジログ出力プログラムを準備 •

Slide 42

Slide 42 text

カバレッジ結果出力プログラムを準備 •

Slide 43

Slide 43 text

カバレッジレポート 出力プログラムを準備

Slide 44

Slide 44 text

カバレッジレポート出力プログラムを準備 • filter() • ->addDirectoryToWhitelist('/usr/local/myapp', ['.php', '.inc’]); • $reportFiles = glob(__DIR__ . '/reports/json/*.json’); ① レポート用モジュールを生成し、ログデータ読み込み準備

Slide 45

Slide 45 text

• $countFiles = count($reportFiles); • for ($i = 0; $i < $countFiles; $i++) { • printf("[%d/%d] append coverage data %s¥n", • ($i + 1), $countFiles, $reportFiles[$i] • ); • $tmpData = json_decode( • file_get_contents($reportFiles[$i]), JSON_OBJECT_AS_ARRAY); • $summaryCoverage->append($tmpData, 'myapp'); • } カバレッジレポート出力プログラムを準備 ② ログデータを読み込む

Slide 46

Slide 46 text

• echo "Output html report start..." . PHP_EOL; • $report = new SebastianBergmann¥CodeCoverage¥Report¥Html¥Facade(); • $report->process($summaryCoverage, __DIR__ . '/reports/html'); • echo "Output html report completed" . PHP_EOL; カバレッジレポート出力プログラムを準備 ③ カバレッジ結果をHTML出力

Slide 47

Slide 47 text

PHP設定ファイルの編集

Slide 48

Slide 48 text

PHP設定ファイルの編集 • 「auto_prepend_file」ディレクティブ設定 ; Automatically add files before PHP document. ; http://php.net/auto-prepend-file auto_prepend_file = {カバレッジログ出力プログラムのパス}

Slide 49

Slide 49 text

カバレッジレポート出力の流れ 1. カバレッジ用プログラムの配置 2. 「auto_prepend_file」設定し、サービス再起動 3. 重要機能テストを手動実行 4. 「auto_prepend_file」設定クリアし、サービス再起動 5. カバレッジ出力プログラムを実行

Slide 50

Slide 50 text

カバレッジ取得&HTML出力

Slide 51

Slide 51 text

カバレッジ取得&HTML出力

Slide 52

Slide 52 text

カバレッジ取得&HTML出力 赤色行が重要機能ロジックであれば テストケースを追加する

Slide 53

Slide 53 text

重要機能テストの自動化

Slide 54

Slide 54 text

利用するライブラリ • Puppeteer • E2Eテスト用ライブラリ • プログラミング言語: JavaScript (https://github.com/puppeteer/puppeteer) • Visual Studio Code (IDE環境) • テストプログラム作成のためのIDE環境として利用 (https://azure.microsoft.com/ja-jp/products/visual-studio-code/)

Slide 55

Slide 55 text

ライブラリのインストール • puppeteer • Visual Studio Code • 割愛 npm install puppeteer

Slide 56

Slide 56 text

PageObject パターンでユーザ操作をメソッド化 module.exports = class HMPage { //...中略... async input件名(aSubject) { await this.page.type('#subject', aSubject); } async click新規作成() { await this.page.waitForXPath('//a[contains(text(), "新規作成")]') const link = await this.page.$x('//a[contains(text(), "新規作成")]') await link[0].click() await this.navigationPromise; } async click次へ() { await this.page.waitForXPath('//a[contains(text(), "次へ")]') const link = await this.page.$x('//a[contains(text(), "次へ")]') await link[0].click() await this.navigationPromise; }

Slide 57

Slide 57 text

重要機能テストをテストコード化 • (async () => { • const page = new HMPage() //.. 中略... await page.inputログインID("テスト太郎") • await page.inputパスワード("password") • await page.clickBtnログイン() • await page.click新規作成() • await page.click次へ() • actualValue = await page.getTitle() • assert.equal(actualValue, 'メール作成画面', '「メール作成画面」画面が表示される'); • await page.input件名("テストメール件名") • await page.input本文("テストメール本文") • await page.click次へ() • actualValue = await page.getTitle() • assert.equal(actualValue, 'メール確認画面', '「メール確認画面」画面が表示される');

Slide 58

Slide 58 text

重要機能テストを自動化

Slide 59

Slide 59 text

戦略② 不確実性に立ち向かうための テスト駆動開発

Slide 60

Slide 60 text

課題まとめ(再掲) • 課題1 • 複雑度が高すぎるプロダクトコード • 課題2 • 計画の不確実性の問題

Slide 61

Slide 61 text

安全にリリースし続けるために • 開発終盤の不確実性を低減させる必要がある

Slide 62

Slide 62 text

テスト駆動開発

Slide 63

Slide 63 text

テスト駆動開発について • 基本となる開発サイクル • 失敗するテストを書く • できる限り早く、テストに通るような最小限のコードを書く • コードの重複を除去する(リファクタリング)

Slide 64

Slide 64 text

テスト駆動開発について • ソフトウェアテストの世界における本来のテストとは、認知 の外を探求する、いわば創造的破壊行為です。それにたいし てTDDのテストとは、いわばプログラミングや設計の補助線、 治具です。 (書籍「テスト駆動開発」より)

Slide 65

Slide 65 text

我々のテスト駆動開発

Slide 66

Slide 66 text

導入前 1. 既存ロジックの仕様を把握する 2. プログラム設計書を書く 3. 実装する 4. 修正ロジック、デグレードしそうなロジックを手動テスト する

Slide 67

Slide 67 text

導入後 1. 既存ロジックのユニットテスト作成 2. 既存ロジックをリファクタリング 3. 新規ロジックのユニットテスト 4. 実装して動くものを作る 5. リファクタリングする

Slide 68

Slide 68 text

テスト駆動開発への期待 • 納期のプレッシャーを低減 • 開発終盤でも安心して、修正・再設計できる。 • 既存バグや複雑な作業に翻弄されずに、進捗させやすくなる。 • 開発タスクを分業しやすくし、クリティカルパスを低減できる。 • スイッチングコストの低減 • 実装結果がすぐにフィードバックされるので、実装に専念できる。 • 調査・実装・デバッグ・既存バグによる作業中断によるスイッチングコ ストを低減できる。 • 開発チームの設計力底上げ • テスト容易性を考慮した設計が行えるようになり、開発チームの設計力 を底上げできる。

Slide 69

Slide 69 text

テスト駆動開発用ツール • PHPUnit • PHPで動くxUnit系フレームワーク (公式: https://phpunit.de/) • Guzzle • PHPで利用できるHTTPクライアント (http://docs.guzzlephp.org/en/stable/index.html)

Slide 70

Slide 70 text

ライブラリのインストール • PHPUnit • Guzzle • PHPで動くHTTPクライアント composer require phpunit/phpunit composer require guzzlehttp/guzzle:^7.0

Slide 71

Slide 71 text

実行環境

Slide 72

Slide 72 text

結果報告 (中間報告)

Slide 73

Slide 73 text

重要機能テストについて • 重要機能テストにより、発見された不具合は今のところ0件 • 重要機能が定義され、死守すべき品質基準が明文化された点は大き い • 自動化テストについては、一部オプション機能を除き自動化済み • 現在は、より堅牢なサービスを目指し、重要機能の範囲を広げて自 動テストを拡充中

Slide 74

Slide 74 text

テスト駆動開発 • プロジェクト終盤の不確実性が圧倒的に低減できている • ユニットテストがあることで、開発終盤でも安心して設計方針を見 直せる • 携わった開発メンバー全員が、この取り組みを評価しており、今後 積極的に採用していく意思をしめしている

Slide 75

Slide 75 text

リリース状況 • 2019年03月予定 v5.3リリース • 2019年04月予定 v5.3リリース • 2019年05月16日 v5.3リリース

Slide 76

Slide 76 text

リリース状況 • 2019年10月10日 v6.0リリース • 2020年05月23日 v6.1リリース • 2020年04月16日 v6.2リリース • 2020年06月03日 v6.3リリース • 2020年08月末 v6.4リリース

Slide 77

Slide 77 text

まとめ

Slide 78

Slide 78 text

まとめ • 重要機能テストで品質基準を明文化し、E2Eテストで品質基 準を検査する。 • テスト駆動開発で品質を向上し、計画の不確実性を低減する。

Slide 79

Slide 79 text

まとめ • 重要機能テストとテスト駆動開発で、レガシーシステムと向 き合うことで、安全なリリースを実現できた。

Slide 80

Slide 80 text

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