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
2k
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
440
リリース後21年目になる レガシーサービスにLaravelを導入した話 / PHPTechCafe-2022-01-26
kyoshimoto
0
430
「始めるのをやめて、終わらせることを始める」ことを始めた開発チームの話 / Rakus Meetup Osaka 2020-02-05
kyoshimoto
17
9.3k
Chatdealerの高速開発を支えるLaravel
kyoshimoto
0
2.2k
Other Decks in Technology
See All in Technology
JAWS DAYS 2025 アーキテクチャ道場 事前説明会 / JAWS DAYS 2025 briefing document
naospon
0
2.5k
Potential EM 制度を始めた理由、そして2年後にやめた理由 - EMConf JP 2025
hoyo
2
2.8k
技術スタックだけじゃない、業務ドメイン知識のオンボーディングも同じくらいの量が必要な話
niftycorp
PRO
0
110
Amazon Aurora のバージョンアップ手法について
smt7174
2
160
データエンジニアリング領域におけるDuckDBのユースケース
chanyou0311
9
2.3k
生成AI “再”入門 2025年春@WIRED TUESDAY EDITOR'S LOUNGE
kajikent
0
140
わたしがEMとして入社した「最初の100日」の過ごし方 / EMConfJp2025
daiksy
14
5.2k
株式会社Awarefy(アウェアファイ)会社説明資料 / Awarefy-Company-Deck
awarefy
3
11k
マーケットプレイス版Oracle WebCenter Content For OCI
oracle4engineer
PRO
3
530
ABWG2024採択者が語るエンジニアとしての自分自身の見つけ方〜発信して、つながって、世界を広げていく〜
maimyyym
1
190
大規模アジャイルフレームワークから学ぶエンジニアマネジメントの本質
staka121
PRO
3
1.3k
Cracking the Coding Interview 6th Edition
gdplabs
14
28k
Featured
See All Featured
Docker and Python
trallard
44
3.3k
The Power of CSS Pseudo Elements
geoffreycrofte
75
5.5k
Building Flexible Design Systems
yeseniaperezcruz
328
38k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
29
1k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
28
9.3k
The Invisible Side of Design
smashingmag
299
50k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.2k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Designing for humans not robots
tammielis
250
25k
Documentation Writing (for coders)
carmenintech
67
4.6k
BBQ
matthewcrist
87
9.5k
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テストで品質基 準を検査する。 • テスト駆動開発で品質を向上し、計画の不確実性を低減する。
まとめ • 重要機能テストとテスト駆動開発で、レガシーシステムと向 き合うことで、安全なリリースを実現できた。
ご静聴ありがとう ございました