テスト駆動開発ってなに

記事の概要

こんにちは、ネットオン開発SecのNです。

今年はテスト駆動開発20周年らしいです。
ということで、テスト駆動開発の話をします。

この記事はこういう方にオススメ

テスト駆動開発に興味あるけどよく知らないという方。

テスト駆動開発ってなに

とりあえず定義を wikipedia から引用します。

「テスト駆動開発 (てすとくどうかいはつ、英: test-driven development; TDD) とは、プログラム開発手法の一種で、プログラムに必要な各機能について、最初にテストを書き(これをテストファーストと言う)、そのテストが動作する必要最低限な実装をとりあえず行なった後、コードを洗練させる、という短い工程を繰り返すスタイルである。」

テスト駆動開発 / Wikipedia

具体的な手法としてもっとも広く知られている手順は、TODOリストを書いてタスク洗い出してから、レッド、グリーン、リファクタのサイクルを回すというものになります。

  1. TODOリストを書く
    課題を箇条書きにして、やるべきことをざっと整理する。
  2. レッド
    TODOリストから1つピックアップして、テストを書き、実行し、そのテストが失敗することを確認する。
  3. グリーン
    失敗しているテストを成功させるための実装を行う。(汚い実装でもよい)
  4. リファクタ
    全てのテストが成功している状態を維持しつつ、実装コードとテストコードを綺麗にしていく。

まあ要はこれです。

50分でわかるテスト駆動開発 / TDD Live in 50 minutes より引用。

テストファーストとは?

TDDで重要なアイデアとして、テストファーストがあります。これは、テスト対象のコードが書かれる前に、そのコードを対象とした自動テストを書くということを意味します。
テストファーストには、以下のようなメリットがあるといわれています。

  • 必ずテスト可能なコードになる
  • 設計改善効果が高い
  • インタフェースと実装を分けて考えることができる
  • 利用者の視点に立った設計になりやすい

注意すべき点として、不必要なものまで詳細に設計しすぎてしまうリスクがある(スコープクリープ)があります。

なお、テストを対象のコードに先立って書くというアイデア自体は、TDDが初出というわけではなく、パンチカードの時代からあるらしいです。

1959 through 1963 : programmers for the Mercury Space Program use a form of TDD while programming punched cards

Ten Years Of Test Driven Development
http://wiki.c2.com/?TenYearsOfTestDrivenDevelopment

一口にTDDと言っても、いろいろあります

提唱されてから20年もたっているので、いろいろな流派が出来ています。

インサイドアウトTDDとアウトサイドインTDDというものがあります。

インサイドアウトTDDとは

関数やコンポーネントといった、小さな単位でTDDを行い、その積み重ねでシステム全体を構築しているスタイルのことです。
デトロイトスタイル、古典派とも呼ばれています。
mockを多用する必要がない分、手間が省ける事が利点であるといえます。
一方で、テストが実装の詳細と結びつきやすい事や、システム全体の整合性を見通しにくくなることなどが欠点として挙げられます。

アウトサイドインTDDとは


ロンドンスタイルまたはモック派とも呼ばれるアウトサイドインTDDは、上記とは逆に、最初にアーキテクチャの全体設計を行い、コンポーネントの振る舞いを明確化してから、実装に移る手法です。
mockを未実装コンポーネントの代用として、システム全体を仮想的に動かせる状態にしたうえで、TDDの手法でmockオブジェクトを本物のオブジェクトに置き換えていきます。
システム全体のふるまいを記述したうえで個々の実装に移るという流れを取ることで、最終的に全体の整合性が取れたシステムを開発することを目指します。

皆さんいつもそんなにちゃんとテスト書いてらっしゃるんですか?

テストを(先に)書かないことはあるのか

Kent Beck 氏いわく、書かないこともあるらしいです。

Kent Beck氏、ごく短期のプロジェクトではテストを省略することを提案
テストを書くか否かという質問は、要するに、そのテストが単位時間当たりにより多くの有効な実験をするのに役立つかどうかでした。もし役に立つのであれば、私はテストを書きます。そうでなければ、不要だと判断します。

https://www.infoq.com/jp/news/2009/06/test-or-not/

t-wada さんもテストを(先に)書かないことがあるらしい。

「テストを書くタイミングが直前か直後かはあまり問題ではない。」
「大事なのは、テストを書くことによって、設計にフィードバックが働くかどうか。」
「テストの期待値が作りにくいものに対して、実装直後にテストを書くことはときどきある。」
「大事なのはテストを書くことではなく輪っか(フィードバックサイクル)を高速でまわすこと」
「フィードバックサイクルを高速で回す方法がほかにあるならば(たとえばREPLとか)、テストを書く必要はない」
「その上でフィードバックサイクルを通して学んだことを再現可能にすること。再現可能にすることは自動テストがめちゃくちゃ強い。」

texta.fm – Sideshow 9. Master of Writing Test Code

フィードバックサイクルの早さと、再現性がポイントのようですね。
未知のものを既知にすることと、一度既知になったものを、既知のままにし続けることが重要で、そのための手段として自動テストがある。

しかし、テストを書いたほうがいいかどうかを判断するための基準はあるのでしょうか?

テストピラミッド

Test Automation / Wikipedia より引用

Mike Cohn が2004年に書籍『Succeeding with Agile』提唱したモデルです。
ピラミッドの上から、

  1. ST:システムテスト
  2. IT:インテグレーションテスト
  3. UT:ユニットテスト

の順で並んでおり、それぞれ各レイヤーで実行するテスト量とテストにかかる工数の目安になるとされています。
マーティン・ファウラーは自身のブログで、このモデルは現代的な観点からはシンプルすぎるものの、しかし依然として有益であると書いています。

また、ファウラーは以下の2点を念頭におくべきであるとしています。

  1. Write tests with different granularity
  2. The more high-level you get the fewer tests you should have

多くの粒度の細かい単体テストを書き、可能な限り粒度の荒いE2Eテストは少なくすべきであるという意見のようですね。

ここで面白いのは、React や Angular、ember.js によって書かれる SPA では、UIテストはピラミッドの最上段におかれる必要はないとしている点です。
これらのフレームワークはUIテストをユニットテストとして書くことができるからです。

テスティングトロフィー

Kent C. Dodds が提唱しているモデル。
各種テストのコスト(実行速度、開発/保守工数)と、各テストの効果のトレードオフを示したもの。
E2Eテストや結合テストを充実させるほうがソフトウェアの品質を保証する効果が高いのだから、これらを軽視すべきではないという考え方。
しかしここでも、単体テストを書かなくていいと言っているわけではない。(要はバランスが大事、ということ)

テストを書くコストは?

テストを書くことで、実工数は15%から35%増加するといわれています(Nagappan. 2008)。
その代わりに、システムの欠陥密度を4割から9割低下させ、デバッグや手戻り工数の減少、トータルの工数を削減するなどの効果を得ることができます。

The increase in development time ranges from 15% to 35%.
All the teams demonstrated a significant drop in defect density: 40% for the IBM team;
60–90% for the Microsoft teams.

https://www.microsoft.com/en-us/research/wp-content/uploads/2009/10/Realizing-Quality-Improvement-Through-Test-Driven-Development-Results-and-Experiences-of-Four-Industrial-Teams-nagappan_tdd.pdf

欠陥密度(defect density)とは、発見された欠陥の数を、システムまたはコンポーネントの一つあたりの尺度で割った値の事です。
標準的な尺度には、コード行数、クラス数、またはファンクションポイント数などがあります。

おおむね、テストを書くことで若干の工数増はあるものの、開発が長期化すれば(おおむね2週間)メリットが上回ると言えると思います。

まとめ

以前『LeanとDevOpsの科学』を読んだ時に、デリバリのパフォーマンスが企業のパフォーマンスに強い影響を与えていることに衝撃を受けました。
そして、そのデリバリのパフォーマンスを支えるのがテスト容易性とデプロイ容易性であること、企業としての競争力に技術的な側面が直接インパクトを与えていることを数値で示されたことが驚きでした。
テストファーストによる設計改善効果を得ながら、テスト容易性の高いコードを書き、それを維持することでコードを常にデプロイできる状態に保つことがIT企業の継続的な成長のために、いかに重要か。

現状、弊社では組織的にTDDを実践しているわけではないのですが、私個人はときどき業務でもテストファーストで開発してみるときがあります。
いまのところ、そうしているのはテストが書きやすい処理だけなのですが、少ない経験でも、「実装が先、テストが後」のときと考え方が変わるな、と感じたことがあります。
しかし、実際のところTDDにどういう効果があり、どんな考え方で実践していけばいいのかいまいち理解していませんでした。

普段開発する中で、テスト面倒くさいなと思うこともあるのですが、そんな時は先達の教えを思い出し、TDDのこころを胸にテストを書いていこうと思います。

その他参考にした資料

質とスピード(2022春版、質疑応答用資料付き)
https://speakerdeck.com/twada/quality-and-speed-2022-spring-edition

組織にテストを書く文化を根付かせる戦略と戦術(2020秋版)
https://speakerdeck.com/twada/strategy-and-tactics-of-building-automated-testing-culture-into-organization-2020-autumn-edition

TDD のこころ @ OSH2014
https://www.slideshare.net/t_wada/osh2014-sprit-of-tdd

50分でわかるテスト駆動開発 / TDD Live in 50 minutes
https://speakerdeck.com/twada/tdd-live-in-50-minutes

TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング
https://www.youtube.com/watch?v=Q-FJ3XmFlT8

『LeanとDevOpsの科学[Accelerate] テクノロジーの戦略的活用が組織変革を加速する』
Nicole Forsgren Ph.D. (著), Jez Humble (著), Gene Kim (著), 武舎広幸 (翻訳), 武舎るみ (翻訳)

いまさら聞けないTDD/BDD超入門
https://atmarkit.itmedia.co.jp/ait/series/1431/

The Practical Test Pyramid
https://martinfowler.com/articles/practical-test-pyramid.html

Software Design 2022年3月号 「そろそろはじめるテスト駆動開発」和田卓人 / 櫛引実秀
WebDB Press vol.129「サバンナ便り 第1回 学習用テスト 学びを自動テストとして書く」和田卓人

ユニットテストの流派
https://zenn.dev/yum3/articles/i_unit_test_schools

いまさら聞けないTDD/BDD超入門 / ITmedia
https://atmarkit.itmedia.co.jp/ait/series/1431/

texta.fm
https://anchor.fm/textafm/

  • 5. Accelerate
  • 9. The 20th Anniversary of TDD
  • Sideshow 9. Master of Writing TestCode

技術評論社[動画で解説]和田卓人の“テスト駆動開発”講座
https://gihyo.jp/dev/serial/01/tdd

ZOZO・Quipper・SmartHRのフロントエンド開発効率化/MidasTechStudy#2
https://www.youtube.com/watch?v=Y0RYVSwr4as

チームで取り組みテスト改善のあゆみ
https://speakerdeck.com/negi_andleek/timudequ-rizu-mitesutogai-shan-falseayumi