THECOOのプロダクト組織シリーズ:社内勉強会レポート:「初級:テスト駆動開発の力: クオリティと速度を手に入れよう」
※この記事はTHECOOのプロダクト組織を知ってもらうためのシリーズ記事・第一弾です。
こんにちは、エンジニアの佐藤です。普段はエンタメ業界向けのプロダクト開発や、社内外への情報発信などを行っています。
さて、THECOOではプロダクトチーム(エンジニアだけでなく、ビジネスサイドなども含めたプロダクトに関わるメンバー全員を指します)の知識ベースの向上を目的に、定期的に勉強会を開催しています。
今回は弊社のプロダクト組織をご紹介する記事の第一弾として、その初回資料の一部を公開したいと思います。THECOOのプロダクト文化にご興味のある方だけでなく、本資料の内容にご興味のある方も、ぜひご笑覧いただければ幸いです。
本記事の対象読者
THECOOのプロダクト組織に興味がある人
テスト勉強会の中身に興味のある人
ソフトウェアのテストについて知りたい人(エンジニアなど)
DevOpsについて知りたい人(オペレーション部門の方など)
ソフトウェアにテストを導入することでビジネスを加速したい人(PdM、経営者など)
勉強会の目的
「勉強会」と一口に言っても、その目的に応じて、対象者も内容も異なりますよね。
今回の場合、勉強会の対象者はエンジニアだけでなく、ビジネスサイドのメンバーも対象としました。これは「直接的に開発には参加しないものの、業務にソフトウェアが大きく関わる」メンバーが多く、彼らの知識の底上げも大きな目的にしていたためです。
初回の勉強会ではソフトウェア開発の重要なプロセスである「テスト」に焦点を当て、参加者に以下のことを提供することを目的としました。
大きな目的
テストの重要性を理解してもらうこと
メンバーの知識・スキルを向上すること
最新のトレンドとテクノロジーを紹介すること
さて、ここからは実際の勉強会の内容を、記事形式でご紹介したいと思います。初歩的でエンジニアであればご存知の方も多い内容だと思いますので、その場合は気になる部分のみご覧いただければと思います。
テストの基本要素
ソフトウェアにおけるテストは大きく2種類あります。
既知のものを確かめるテスト(Checking)
未知のものを探すテスト(Testing)
これ以降、「テスト」という場合はCheckingのことを指します。
テストのパターン「3A」
テストは、主に3Aというパターンに則って実施します。
Arrange(準備)
Act(実行)
Assert(検証)
例を挙げます。
データを準備する(準備)
データを保存する(実行)
データを検索する(実行)
保存したデータと検索したデータが同じことを確かめる(検証)
ソフトウェアにおけるテストは、全てこの3Aで実施できます。
複雑さを増すソフトウェアにおいて、テストは必要不可欠
近年ソフトウェアは巨大で複雑になっていく一方ですが、一文字間違えると動かなくなるという脆さは変わっていません。
これを端的に表す有名な言葉として、「ソフトウェア危機(Software Crisis)」があります。
ソフトウェア危機(Software Crisis)とは、ハードウェアの性能が向上したものの、ソフトウェアの複雑さが増すことによる問題を懸念した意見のことです。
1960年代後半頃に登場した言葉です。
つまり、ソフトウェアの複雑性に起因する問題は、50年以上前から警鐘が鳴らされてきたわけです。
事実として、「ソフトウェア バグ」というワードを使ってサーチエンジンで検索してみると、バグに苦しむたくさんの実例が出てきます。
IPA(独立行政法人 情報処理推進機構)は次のようなデータを公開しています。
(少し古くて恐縮ですが、2019年を最後に、データの収集や公開は終了しているようです)
このデータが示す通り、システム障害は年々増加傾向にあります。
これは体感的にも正しいと言わざるを得ません。
毎月、どころか毎週、もしかしたら毎日この手のシステム障害のニュースを聞いているかもしれません。自分が認識できる範囲内でもこの頻度ですから、知らない範囲も含めたらとてつもない件数になることでしょう。
また、良きにせよ悪しきにせよ、ソフトウェアは年々、我々の生活に食い込んできています。
何かを調べる時
人と情報交換をする時
映画やドラマを鑑賞する時
音楽を聴く時
ゲームで遊ぶ時
本を読む時
勤務時間を打刻する時
給料を受け取る時
株式や為替の状況を見る時
ニュースを見る時
車を運転する時
道を調べる時
食べ物を注文する時
などなど…
これらのソフトウェアが「バグ」でおかしくなったら、人々の生活にとてつもないダメージを与えることになりますね。
ソフトウェアは最早、「あったら良いな」という存在ではなく、「なくてはならない」存在になっています。
そのため、リリース前にソフトウェアを可能な限り無害化し、安全に、安定して動くものを提供しなければなりません。これがテストの大きな重要性の一つであり、もはやテストはソフトウェアの一部であるとも言えます。
テストの自動化が非常に重要
上述の通り、ソフトウェアは複雑なため、テストをしようとすると膨大な数になります。
ソフトウェアを十分に無害化するためには、コードの変更がされる度にその膨大な数のテストを毎回実行しなければなりません。
コードは毎日変更されるものですから、その膨大な数のテストは毎日実行されなければなりません。つまり、安全なソフトウェアを作るためにはテストの自動化は欠かせないわけです。
従来の手法(ウォーターフォール)
まずはこの図を見てください。これは「ウォーターフォール」と呼ばれる開発手法の流れを、端的に表した図です。設計から実装、テストが、一つの矢印で繋がっているのがわかりますね。
数ヶ月の設計フェーズが終わったら実装フェーズに、数ヶ月の実装フェーズが終わったらテストフェーズに、数ヶ月のテストフェーズが終わったらようやく本番リリースする、というこのようなプロジェクトの進め方のことをウォーターフォールと呼びます。
ウォーターフォールでは、それぞれフェーズにミスが存在せず、完全な状態になってから次のフェーズに移ることが前提となっているため、逆流することはできません。
さて、この方法を使った場合、どれくらいソフトウェアを無害化できるでしょうか?
『最後の味付けとしての品質』アンチパターン
システム運用アンチパターン ―エンジニアがDevOpsで解決する組織・自動化・コミュニケーション
出版社 O’REILLY Japan 著者 Jeffery D. Smith 翻訳者 田中 裕一 を参考
残念ながら、このウォーターフォールでのテストのアプローチではソフトウェアの無害化はほとんどできません。
「テストフェーズになってから」はじめてテストをしようとすると、複数の問題が発生するからです。これは有名なアンチパターンとして、『最後の味付けとしての品質』と呼ばれています。
どのような問題が発生するのか、実際の会話形式で見てみましょう。
トタタ…
この例には、不都合な現実が隠れています。
このPMは「ウォーターフォールが逆流しないこと」を前提にしていますが、これはあり得ません。
何故なら、テスト工程でバグが見つかったら、コードを変更しなければいけないからです。
さらにコードが変更されたら、テストは最初から再度実行しなければなりません。
バグが発見されてコードが修正される度にテストフェーズは振り出しに戻ります(現実的ではありませんね)。
ソフトウェアは複雑で難解です。
とても人海戦術程度でかなう相手ではありません。
このような不都合な現実から目を背けていると、安全なソフトウェアは作れません。
それどころか、作ったシステムを起動することにすら苦戦するでしょう。
また、顧客へ提供する価値を高めるためには、常に変化を受け入れる、という考え方も重要になります。
クオリティと速度を両立したプロダクト進化のためには、「変更しやすいソフトウェア」は欠かせません。
このためには、テストがしやすい環境、つまり「コードに変更を加えたタイミングで自動的にテストが行われ、複雑なソフトウェアにも対応できる」環境を整えることが重要です。
テストコードを書くことの重要性をおわかりいただけたかと思います。
バグ発見は遅れれば遅れるほどコストがかかる
ウォーターフォールの別の問題として、バグを作ってから気づくまで時間がかかればかかるほど、修正に掛かるコストが跳ね上がる、という問題もあります。
以下の図では、要求仕様誤りを「1」とした場合の修正コストは、フェーズが後の段階に近づけば近づくほど、数百倍にまで跳ね上がるとされています。
つまり、早期にバグに気付き、可能な限り早く対応することが、開発コスト全体に大きく影響するのです。
ウォーターフォール型開発のテストには問題が多い
このように、最後にテストをまとめて行うのはうまくいかないことが、ご理解いただけたかと思います。
それでは、どうすればこの『最後の味付けとしての品質』アンチパターンから抜け出せるでしょうか。
現代的な手法
『最後の味付けとしての品質』にならないようにするには、もう少し早い段階からテストをしなければいけません。
そこで出てくるのが、モダンなソフトウェア開発で重要な手法、「TDD(テスト駆動開発)」です。
テスト駆動開発(TDD: Test Driven Development)
今までは実装した後にテストをしていました。
この場合、バグが見つかった時には実装から数週間、数ヶ月立っており、修正するためのコストが高くなる、という問題がありました。
これに対して、実装をする前にテストをするというのがTDDの発想です。
TDDはRed、Green、Refactorという、三つの工程で構成されています。
1.Red:最初は動作しないテストを1つ書く。
2.Green:Redで書いたテストを迅速に動作させる。
3.Refactor:テストのために発生した問題をすべて除去する。
このやり方を用いると、素早くフィードバックを得ることができるようになります。
このステップに従うと、もの凄い頻度でテストを実行することになります。具体的には30秒に1回程度、数ヶ月のプロジェクトの最後に一回テストをするウォーターフォールとは、全く異なる考え方なのがおわかりいただけるかと思います。
この工程を経ることで、おかしいコードを書いたら即時に大量のフィードバックを受け、数週間、数ヶ月後ではなく、30秒後に気づけるようになるわけです。
TDDを用いると、プロダクトコードと同時にテストコードを作れる上に、テストコードに工数をかけたにもかかわらず、プロダクトコードだけを作るよりも何故か早く作れます。
更に、ここで作ったテストコードは残るため「チリも積もれば山になる」の恩恵を受けることもできます。
TDDはいくつものプラクティスと深い関係がありますが、これを説明すると長くなるため、今回はキーワードを書くに止めようと思います。
リファクタリング
シンプルな設計
継続的インテグレーション(ここでテストの実行を行います)
受け入れテスト
TDDを実践している動画はこちらを参照してください。
実際にTDDをしているのを見ると、TDDについてより深く学べます。
TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング
ここまでの振り返り
さて、以上のことから、TDDが非常に重要であること、ウォーターフォールの工程に含まれていた「実装」と「テスト」は同時に行った方が良いということがわかりました。
では、残り一つの工程、「設計」についてはどうでしょうか。
ソフトウェアの「設計」とは
大前提として、ソフトウェア設計は2つの構成に分けて考えることができます。
振る舞い
構造(今回はあまり触れないよ)
Clean Architecture 達人に学ぶソフトウェアの構造と設計
著者 Robert C. Martin 翻訳者 角 征典、高木正弘 を参考
振る舞いとは
簡単に言えば、そのソフトウェアがどのように動作するか、ということです。
例を挙げます。
データを準備する
データを保存する
データを検索する
保存したデータと検索したデータが同じことを確かめる
この例、どこかで見たことがありませんか?
そう、これは冒頭でご紹介した3A(準備・実行・検証)です!
3Aと同じということは、つまりこれは「テストである」ということです。
整理しましょう。
ソフトウェア設計は、「振る舞い」と「構造」で構成されています。
このうち「振る舞い」は、3Aと同じ要素を持つことから、テストのことを指すと判断できます。
つまり、テスト自体が設計そのものなのです。
実は、先ほど紹介したTDDも「テスト手法」ではなく「設計手法」と呼ばれています。
さらに、「テスト駆動開発(TDD)」は小さくコードを書く手法でしたが、これを拡張した「振る舞い駆動開発(BDD)」という考え方もあります。
振る舞い駆動開発(BDD: Behavior Driven Development)
※受け入れテスト駆動開発(ATDD: Acceptance Test Driven Development)と呼ぶこともあります。
振る舞い駆動開発(BDD)は、どのような振る舞いをするのかを先に決めておき、その振る舞い(テスト)を実行し、成功するか、失敗するかで機能が完成したかどうかを確認する手法です。
基本的な考え方はTDDと同じですが、適応する範囲が大きくなっており、BDDの思想で書かれたコードは日本語として読めます。例えば
シナリオ: 会員登録ができること
前提: 有効なメールアドレスを持っている
もし: 会員を登録した
ならば: ログインできる
かつ: 会員の情報を取得できる
この振る舞いの記載を裏で動くコードと紐付けることにより、このシナリオがテストとして実行できるようになります。これがBDDです。
ウォーターフォールとテスト駆動開発の比較
さて、テストそのものが設計だということがわかると、テスト駆動開発にはウォーターフォールに含まれていた全てが包含されていることがわかると思います。今まで実装とテストだけをやっていたつもりでしたが、実は設計もしていました。
ウォーターフォールと見比べてみましょう。
かなり進化しましたね!
では、設計・実装・テストの前後の工程はどうなるでしょうか。
この構成における重要な要素として、DevOpsという概念を紹介したいと思います。
DevOps(全て混ぜる)
DevOpsとは開発(Development)と運用(Operations)を合わせる、という考え方のことです。その名の通り、開発と運用を常に継続して行い、それを繰り返すことでユーザーに価値を提供し続けるやり方です。
この工程の中で、一体テストはどこに入るのでしょうか?
正解は、「テストは常に行う」です。
常にテストをし、常にフィードバックを受け、常に設計し、常に改善します。
先ほどの図で考えると、このようになります。
ちなみに、DevOpsは近年、セキュリティ(Security)を合わせたDevSecOpsと呼ぶこともあります。ソフトウェアの複雑性が増すにつれ、こうした考え方も常に進化を続けています。
なぜバグが発生するのか
最後に、ソフトウェアに関わる人の全てが、一度は考えたことのある問いに立ち返りましょう。優れた組織は上述のようなモダンな手法を使い、常にテストをしていますが、それでもバグは発生します。
どうしてでしょうか。
バグフィルター
次の図を見てください。
これは、上からコードが降ってきた時にどれくらいバグを捕まえられるか、を表しています。
いくつかのテストを通してバグを捕まえていますが、いくつかの理由から完全にバグを捕まえることはできません。
バグフィルターの網目を潜り抜けることがある
テストが不十分
バグフィルターの外でバグが発生することがある
本番でしか発生しないバグ
未知の振る舞い
バグが存在しないことは証明できない
ソフトウェアは複雑すぎる
悪魔の証明
マーフィーの法則
起こる可能性のあることは、いつか実際に起きる
つまり、バグは必ず発生します。バグをゼロにしたいと思っても、絶対にゼロにすることができないなら、プロダクトは何を目指せばいいのでしょうか?
そこで重要な指標になる「MTTR(平均復旧時間)」を最後にご紹介したいと思います。
MTTR: Mean Time To Recovery(平均復旧時間)とは
バグが必ず発生するのであれば、バグを検知してから修復するまでの時間を短くしよう、という考え方です。
バグを検知できなければ修復できないため、ソフトウェアの監視(Monitoring)によるテストは非常に重要です。
関連する言葉に、「可観測性」というものがあります。
可観測性が高ければ問題に素早く気付くことができるようになります。
問題を発見するまでの時間を縮める、ということは、MTTRを短縮する上で非常に重要になります。
パフォーマンスの低い組織は復旧に1ヶ月から6ヶ月かかるのに対し、パフォーマンスの高い組織は1時間未満で復旧することができます。この復旧時間の短さこそ、プロダクト開発における非常に重要な指標になるわけです。
しかも、これらの数値は組織のパフォーマンスと強い相関関係があります。
ご覧の通り、MTTRを向上させるには組織のパフォーマンスを含めた、他のものも改善しなければいけません。
となると、MTTRを向上させるためには複数の手法を用いる必要があり、急に向上させることはできません。
このような言葉があります。
これは、すぐにはできないが今から取り組めばそう遠くない内に改善できる、ということです。
目標にするMTTRを定めるにあたり、SLO(Service Level Objective)を考えることも非常に重要ですが、これを記載すると長くなってしまうため、詳しくは下記の記事を参考ください。
まとめ
さて、歴史的なテストのやり方から、近年多くの開発組織で使われている手法(TDD / BDD /DevOps)、重要になる指標(MTTR)まで、駆け足でご説明してきました。
従来のウォーターフォール手法では、後半のフェーズになるにつれて修正コストが跳ね上ってしまうこと、テストを早期から全ての工程に取り入れることで解決できるということが、ご理解いただけたのであれば幸いです。
THECOOではプロダクトメンバーの知識や関心を高めるため、こういった勉強会を定期的に行っています。
ご興味のある方は、是非お気軽にメンバーにお声かけください!
ご応募もお待ちしております!
参考資料一覧
翻訳と辞書 ソフトウェアクライシス
IPA 情報システムの障害状況一覧
システム運用アンチパターン ―エンジニアがDevOpsで解決する組織・自動化・コミュニケーション
ソフトウェア品質保証の方法論、技法、その変遷
The Clean Code Blog
TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング
Clean Architecture 達人に学ぶソフトウェアの構造と設計
DevOpsエンジニア向けOCIガイド
A Practical Guide to Testing in DevOps Japanese Edition
A Practical Guide to Testing in DevOps
福田交易 平均修復時間
可観測性とは Red Hat
2022 年の State of DevOps Report
SLO、SLI、SLAとは何か?
LeanとDevOpsの科学[Accelerate] テクノロジーの戦略的活用が組織変革を加速する
Agile Testing Condensed
テスト駆動開発
Clean Agile 基本に立ち戻れ
Clean Code アジャイルソフトウェア達人の技
継続的デリバリーのソフトウェア工学 もっと早く、もっと良いソフトウェアを作るための秘訣
Clean Craftsmanship 規律、基準、倫理
The BDD Books - Discovery
The BDD Books – Formulation
現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法
リーン開発の現場 カンバンによる大規模プロジェクトの運営
SLO、SLI、SLAとは何か?
「史上最悪のソフトウェアバグ」ワースト10を紹介(上)
「史上最悪のソフトウェアバグ」ワースト10を紹介(下) | ■ 爆発的ATOWN主義 本店 ■