Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
13年続くレガシーサービスを安全にリリースし続けるためのテスト戦略 / rakus-meetu...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
kyoshimoto
August 05, 2020
Technology
2.4k
4
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
13年続くレガシーサービスを安全にリリースし続けるためのテスト戦略 / rakus-meetup-osaka-vol8-2020-08-05
Rakus Meetup Osaka #8 の発表資料です。
https://rakus.connpass.com/event/161744/
kyoshimoto
August 05, 2020
More Decks by kyoshimoto
See All by kyoshimoto
視座とアジャイル / shiza_and_agile
kyoshimoto
0
550
リリース後21年目になる レガシーサービスにLaravelを導入した話 / PHPTechCafe-2022-01-26
kyoshimoto
0
550
「始めるのをやめて、終わらせることを始める」ことを始めた開発チームの話 / Rakus Meetup Osaka 2020-02-05
kyoshimoto
17
10k
Chatdealerの高速開発を支えるLaravel
kyoshimoto
0
2.6k
Other Decks in Technology
See All in Technology
OR学会2026年秋期大会_機械学習と数理最適化の融合による、配合レシピ最適化システム開発事例
brainpadpr
0
130
LLM機能を自作して分かるSnowflake Cortex AIの強み
nayuts
0
350
生成AIで高い生産性を求められて、 悩み、乗り越えた話
mot_techtalk
2
160
Deep Data Security 機能解説
oracle4engineer
PRO
2
770
UniFi(ユニファイ)AIカメラの現場活用
ludicmind
1
410
OSC2026on_the-world-is-waiting-for-your-voice.pdf
naruoga
0
370
DevelopersIO 2026 知識の地図を描く ─ オントロジーとは何か?
ishikawa_satoru
1
1.4k
今こそ知りたいAmplifyGen2
mkdev10
2
160
12 個 Tool と、 4,096 Token
hlum
0
130
継続的なAIコスト最適化
bgpat
1
280
契約書画像からの情報抽出に特化した視覚接地モデルを育てる話
sansantech
PRO
1
160
1万名の社員が使う認証基盤で どう信頼性を担保するか?
kairim0
0
230
Featured
See All Featured
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
450
Context Engineering - Making Every Token Count
addyosmani
9
1.2k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.6k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
430
Navigating Weather and Climate Data
rabernat
0
550
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
400
Fireside Chat
paigeccino
43
4.1k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.4k
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
580
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.6k
We Have a Design System, Now What?
morganepeng
55
8.4k
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テストで品質基 準を検査する。 • テスト駆動開発で品質を向上し、計画の不確実性を低減する。
まとめ • 重要機能テストとテスト駆動開発で、レガシーシステムと向 き合うことで、安全なリリースを実現できた。
ご静聴ありがとう ございました