Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
13年続くレガシーサービスを安全にリリースし続けるためのテスト戦略 / rakus-meetu...
Search
kyoshimoto
August 05, 2020
Technology
4
1.9k
13年続くレガシーサービスを安全にリリースし続けるためのテスト戦略 / rakus-meetup-osaka-vol8-2020-08-05
Rakus Meetup Osaka #8 の発表資料です。
https://rakus.connpass.com/event/161744/
kyoshimoto
August 05, 2020
Tweet
Share
More Decks by kyoshimoto
See All by kyoshimoto
視座とアジャイル / shiza_and_agile
kyoshimoto
0
410
リリース後21年目になる レガシーサービスにLaravelを導入した話 / PHPTechCafe-2022-01-26
kyoshimoto
0
410
「始めるのをやめて、終わらせることを始める」ことを始めた開発チームの話 / Rakus Meetup Osaka 2020-02-05
kyoshimoto
17
9k
Chatdealerの高速開発を支えるLaravel
kyoshimoto
0
2.1k
Other Decks in Technology
See All in Technology
Platform Engineering for Software Developers and Architects
syntasso
1
520
マルチプロダクトな開発組織で 「開発生産性」に向き合うために試みたこと / Improving Multi-Product Dev Productivity
sugamasao
1
310
VideoMamba: State Space Model for Efficient Video Understanding
chou500
0
190
Why App Signing Matters for Your Android Apps - Android Bangkok Conference 2024
akexorcist
0
130
DynamoDB でスロットリングが発生したとき_大盛りver/when_throttling_occurs_in_dynamodb_long
emiki
1
430
初心者向けAWS Securityの勉強会mini Security-JAWSを9ヶ月ぐらい実施してきての近況
cmusudakeisuke
0
130
AI前提のサービス運用ってなんだろう?
ryuichi1208
8
1.4k
ドメインの本質を掴む / Get the essence of the domain
sinsoku
2
160
SRE×AIOpsを始めよう!GuardDutyによるお手軽脅威検出
amixedcolor
0
180
ExaDB-D dbaascli で出来ること
oracle4engineer
PRO
0
3.9k
アジャイルチームがらしさを発揮するための目標づくり / Making the goal and enabling the team
kakehashi
3
140
OCI Network Firewall 概要
oracle4engineer
PRO
0
4.2k
Featured
See All Featured
A Philosophy of Restraint
colly
203
16k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
246
1.3M
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
26
2.1k
Why You Should Never Use an ORM
jnunemaker
PRO
54
9.1k
The Straight Up "How To Draw Better" Workshop
denniskardys
232
140k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
8
900
Fontdeck: Realign not Redesign
paulrobertlloyd
82
5.2k
For a Future-Friendly Web
brad_frost
175
9.4k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
229
52k
Practical Orchestrator
shlominoach
186
10k
Side Projects
sachag
452
42k
Mobile First: as difficult as doing things right
swwweet
222
8.9k
Transcript
13年続くレガシーサービスを安全に リリースし続けるためのテスト戦略 RAKUS Meetup Osaka #8 株式会社ラクス 吉元和仁
自己紹介 • 所属・氏名 • 株式会社ラクス 第四開発部 • 吉元 和仁(よしもとかずひと) •
仕事 • 2018年10月より自社サービス「配配メール」の開発担当
配配メール について • BtoB向けメールマーケティング・サービス • 中小企業のお客様の多種多様な業務に合わせて4つのプラン、 8つのオプション機能を提供
2019年10月に新プラン 「配配メールBridge」をリリース
話すこと • 13年続く自動テストの無いウェブサービスで、レガシーと向 き合い、安全にリリースし続けるためのテスト戦略とテスト ツールの活用事例を紹介。
レガシーサービスの定義
レガシーサービスの定義 • テストがないコードはレガシーコード だ! (書籍「レガシーコード 改善ガイド」より)
レガシーサービスの定義 • テストされていない/テストできないコード • 柔軟性のないコード • 技術的負債を抱え込んでいるコード (書籍「レガシーソフトウェア改善ガイド」より)
レガシーシステムの改善方針 • リファクタリング • リアーキテクティング • ビッグ・リライト (書籍「レガシーソフトウェア改善ガイド」より)
我々のレガシーサービス
我々のレガシーサービス • 2007年5月にサービス提供を開始 • 長年の機能追加により、ユーザへの価値提供を増大 • 一方で、コードの複雑性も増大
メソッドが組み合わせ爆発 • プログラム修正がコアロジックに集中 • 増え過ぎた引数 • 引数の論理演算($midOrSid) • No グローバル変数,
No life (https://youtu.be/Q4gTV4r0zRs)
BigQuery生成ロジック • 複数のメソッドが1つのSQLを生成するロジック • サービスの成長とともにSQLが巨大化 • 各メソッドは責務崩壊 • つじつま合わせのハッキングコード •
新人泣かせのBigQuery(SQL)生成ロジックへと成長
リファクタリングしたい
レガシーコードのジレンマ • リファクタリングを行うとき、最初にすることは常に同じで す。対象となるコードについてきちんとしたテスト群を作り 上げることです。テストは不可欠です。 (書籍「リファクタリング」より)
レガシーコードのジレンマ • 自動テストの仕組みも文化もないサービス • ドキュメントが無く、ソースコードが仕様 • テストを作るためには、まずはじめに複雑度の高いロジック を理解することから始めなければならない
開発チームの課題
開発チームの課題 • スター選手は新規サービス開発の最前線へ • 相次ぐコアメンバーの異動 • 開発チームの半数以上は若手エンジニア
開発チームの課題 • ソースコードが仕様 • 既存仕様の考慮漏れにより、開発終盤で設計が破綻 • 進捗90%の壁 • 納期のプレッシャーにより品質が低下 •
疲弊する開発チーム
計画の不確実性をなくしたい
計画の不確実性 • 楽観主義などの要因により、コストを低く見積もる傾向があ る。 書籍「人月の神話」より
計画の不確実性 • 見積りは、あくまで予測です。 • 予測を「ノルマ」にした途端、それを達成するための過負荷 な労働が生まれ、クオリティが下がってしまいます。 • 書籍「エンジニア組織論への招待」より
計画の不確実性 • エンジニアリングで重要なのは「どうしたら効率よく不確実 性を減らしていけるのか」という考え方なのです。 • 書籍「エンジニア組織論への招待」より
課題まとめ • 課題1 • 複雑度が高すぎるプロダクトコード • 課題2 • 計画の不確実性の問題
レガシーサービスを安全にリ リースし続けるための テスト戦略
課題まとめ(再掲) • 課題1 • 複雑度が高すぎるプロダクトコード • 課題2 • 計画の不確実性の問題
テストの7原則 1. テストは欠陥があることは示せるが、欠陥がないことは示せない 2. 全数テストは不可能 3. 早期テストで時間とコストを節約 4. 欠陥の偏在 5.
殺虫剤のパラドックスにご用心 6. テストは状況次第 7. 「バグゼロ」の落とし穴 (参考: http://jstqb.jp/dl/JSTQB-SyllabusFoundation_Version2018V31.J02.pdf)
テストの7原則 4. 欠陥の偏在 リリース前のテストで見つかる欠陥や運用時の故障の大部分は、 特定の少数モジュールに集中する。 テストの労力を集中させるために欠陥の偏在を予測し、テストや 運用での実際の観察結果に基づいてリスク分析を行う。
戦略① 安全にリリースし続ける ための重要機能テスト
重要機能テストとは • 我々の開発チームでは、代替手段がなく顧客影響が大きいバ グをクリティカルバグと呼ぶ。 • このバグを抽出するためのテストが重要機能テスト。 • 安全なリリースサイクルを回すためには、重要機能テストの 実施が不可欠。
テストの作成手順 1. サービスの有識者と協議の上で重要機能を抽出 2. 重要機能のテストケースを作成 3. テストを手動実行し、カバレッジ結果を取得 4. カバレッジ結果を参考に、テストケースを見直し
テストケースの作成手順 1. サービスの有識者と協議の上で重要機能を抽出 2. 重要機能のテストケースを作成 3. テストを手動実行し、カバレッジ結果を取得 4. カバレッジ結果を参考に、テストケースを見直し
手動テストのカバレッジって 取得できるの?
手動テストの カバレッジ取得について
利用するライブラリ • Xdebug (https://xdebug.org/ ) • PHP_CodeCoverage (Github: https://github.com/sebastianbergmann/php-code-coverage)
ライブラリのインストール • Xdebug • 公式ページ参照 • https://xdebug.org/docs/install • PHP_CodeCoverage composer
require --dev phpunit/php-code-coverage
カバレッジログ 出力プログラムの準備
カバレッジログ出力プログラムを準備 • <?php • xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); • function output_coverage_report_file()
{ • if (!xdebug_code_coverage_started()) { • return; • } • xdebug_stop_code_coverage(false); • $jsonData = json_encode(xdebug_get_code_coverage()); • // コードカバレッジ結果をJSONファイルに出力する • $reportFile = __DIR__ . '/reports/json/coverage-' . microtime(true) . '.json'; • file_put_contents($reportFile, $jsonData); • } register_shutdown_function('output_coverage_report_file'); •
カバレッジログ出力プログラムを準備 • <?php • xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); • • •
• • • • • • • ① カバレッジ取得開始
カバレッジログ出力プログラムを準備 • <?php • xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); • function output_coverage_report_file()
{ • if (!xdebug_code_coverage_started()) { • return; • } • xdebug_stop_code_coverage(false); • $jsonData = json_encode(xdebug_get_code_coverage()); • // コードカバレッジ結果をJSONファイルに出力する • $reportFile = __DIR__ . '/reports/json/coverage-' . microtime(true) . '.json'; • file_put_contents($reportFile, $jsonData); • } register_shutdown_function('output_coverage_report_file'); • ② プログラム終了時に実行する コールバック関数を定義
カバレッジログ出力プログラムを準備 • <?php • xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); • function output_coverage_report_file()
{ • if (!xdebug_code_coverage_started()) { • return; • } • xdebug_stop_code_coverage(false); • $jsonData = json_encode(xdebug_get_code_coverage()); • // コードカバレッジ結果をJSONファイルに出力する • $reportFile = __DIR__ . '/reports/json/coverage-' . microtime(true) . '.json'; • file_put_contents($reportFile, $jsonData); • } register_shutdown_function('output_coverage_report_file'); • ③ カバレッジ取得停止
カバレッジ結果出力プログラムを準備 • <?php • xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); • function output_coverage_report_file()
{ • if (!xdebug_code_coverage_started()) { • return; • } • xdebug_stop_code_coverage(false); • $jsonData = json_encode(xdebug_get_code_coverage()); • // コードカバレッジ結果をJSONファイルに出力する • $reportFile = __DIR__ . '/reports/json/coverage-' . microtime(true) . '.json'; • file_put_contents($reportFile, $jsonData); • } register_shutdown_function('output_coverage_report_file'); • ④ カバレッジデータを ファイル出力
カバレッジレポート 出力プログラムを準備
カバレッジレポート出力プログラムを準備 • <?php • ini_set('max_execution_time', 0); • ini_set('memory_limit', -1); require_once
__DIR__ . "/vendor/autoload.php"; • $summaryCoverage = new SebastianBergmann¥CodeCoverage¥CodeCoverage(); • // ... 中略 ... • $summaryCoverage->filter() • ->addDirectoryToWhitelist('/usr/local/myapp', ['.php', '.inc’]); • $reportFiles = glob(__DIR__ . '/reports/json/*.json’); ① レポート用モジュールを生成し、ログデータ読み込み準備
• $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'); • } カバレッジレポート出力プログラムを準備 ② ログデータを読み込む
• 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出力
PHP設定ファイルの編集
PHP設定ファイルの編集 • 「auto_prepend_file」ディレクティブ設定 ; Automatically add files before PHP document.
; http://php.net/auto-prepend-file auto_prepend_file = {カバレッジログ出力プログラムのパス}
カバレッジレポート出力の流れ 1. カバレッジ用プログラムの配置 2. 「auto_prepend_file」設定し、サービス再起動 3. 重要機能テストを手動実行 4. 「auto_prepend_file」設定クリアし、サービス再起動 5.
カバレッジ出力プログラムを実行
カバレッジ取得&HTML出力
カバレッジ取得&HTML出力
カバレッジ取得&HTML出力 赤色行が重要機能ロジックであれば テストケースを追加する
重要機能テストの自動化
利用するライブラリ • Puppeteer • E2Eテスト用ライブラリ • プログラミング言語: JavaScript (https://github.com/puppeteer/puppeteer) •
Visual Studio Code (IDE環境) • テストプログラム作成のためのIDE環境として利用 (https://azure.microsoft.com/ja-jp/products/visual-studio-code/)
ライブラリのインストール • puppeteer • Visual Studio Code • 割愛 npm
install puppeteer
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; }
重要機能テストをテストコード化 • (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, 'メール確認画面', '「メール確認画面」画面が表示される');
重要機能テストを自動化
戦略② 不確実性に立ち向かうための テスト駆動開発
課題まとめ(再掲) • 課題1 • 複雑度が高すぎるプロダクトコード • 課題2 • 計画の不確実性の問題
安全にリリースし続けるために • 開発終盤の不確実性を低減させる必要がある
テスト駆動開発
テスト駆動開発について • 基本となる開発サイクル • 失敗するテストを書く • できる限り早く、テストに通るような最小限のコードを書く • コードの重複を除去する(リファクタリング)
テスト駆動開発について • ソフトウェアテストの世界における本来のテストとは、認知 の外を探求する、いわば創造的破壊行為です。それにたいし てTDDのテストとは、いわばプログラミングや設計の補助線、 治具です。 (書籍「テスト駆動開発」より)
我々のテスト駆動開発
導入前 1. 既存ロジックの仕様を把握する 2. プログラム設計書を書く 3. 実装する 4. 修正ロジック、デグレードしそうなロジックを手動テスト する
導入後 1. 既存ロジックのユニットテスト作成 2. 既存ロジックをリファクタリング 3. 新規ロジックのユニットテスト 4. 実装して動くものを作る 5.
リファクタリングする
テスト駆動開発への期待 • 納期のプレッシャーを低減 • 開発終盤でも安心して、修正・再設計できる。 • 既存バグや複雑な作業に翻弄されずに、進捗させやすくなる。 • 開発タスクを分業しやすくし、クリティカルパスを低減できる。 •
スイッチングコストの低減 • 実装結果がすぐにフィードバックされるので、実装に専念できる。 • 調査・実装・デバッグ・既存バグによる作業中断によるスイッチングコ ストを低減できる。 • 開発チームの設計力底上げ • テスト容易性を考慮した設計が行えるようになり、開発チームの設計力 を底上げできる。
テスト駆動開発用ツール • PHPUnit • PHPで動くxUnit系フレームワーク (公式: https://phpunit.de/) • Guzzle •
PHPで利用できるHTTPクライアント (http://docs.guzzlephp.org/en/stable/index.html)
ライブラリのインストール • PHPUnit • Guzzle • PHPで動くHTTPクライアント composer require phpunit/phpunit
composer require guzzlehttp/guzzle:^7.0
実行環境
結果報告 (中間報告)
重要機能テストについて • 重要機能テストにより、発見された不具合は今のところ0件 • 重要機能が定義され、死守すべき品質基準が明文化された点は大き い • 自動化テストについては、一部オプション機能を除き自動化済み • 現在は、より堅牢なサービスを目指し、重要機能の範囲を広げて自
動テストを拡充中
テスト駆動開発 • プロジェクト終盤の不確実性が圧倒的に低減できている • ユニットテストがあることで、開発終盤でも安心して設計方針を見 直せる • 携わった開発メンバー全員が、この取り組みを評価しており、今後 積極的に採用していく意思をしめしている
リリース状況 • 2019年03月予定 v5.3リリース • 2019年04月予定 v5.3リリース • 2019年05月16日 v5.3リリース
リリース状況 • 2019年10月10日 v6.0リリース • 2020年05月23日 v6.1リリース • 2020年04月16日 v6.2リリース
• 2020年06月03日 v6.3リリース • 2020年08月末 v6.4リリース
まとめ
まとめ • 重要機能テストで品質基準を明文化し、E2Eテストで品質基 準を検査する。 • テスト駆動開発で品質を向上し、計画の不確実性を低減する。
まとめ • 重要機能テストとテスト駆動開発で、レガシーシステムと向 き合うことで、安全なリリースを実現できた。
ご静聴ありがとう ございました