【翻訳記事】TDD: 目的と実践

目次

はじめに

今回は著者本人の許可をもらった上で、TDD: 目的と実践(原題は「TDD: Purposes and Practices」)を翻訳したので紹介します。

www.industriallogic.com

この記事はIndustrial Logic社のTim Ottingerが書いた記事です。Tim OttingerはClean Codeの執筆にも関わっています。

また、Industrial Logic社はAgileなどに関するコーチングやトレーニングを行なっている会社です。そのため、記事の中ではIndustrial Logic社のe-learningのコースの多くがリンクされていますが、それらの宣伝を抜きにしても、非常に良い記事だと思います。

これ以降は元記事を翻訳したものになります。


TDD: 目的と実践

テスト駆動開発(TDD)は、無駄な争い、遅延、動揺を招く方法と誤解されることがよくあります。

誤解や不正確な説明は非常に苦痛であり、開発者はフラストレーションを溜めて、時には実践全体が有害で無意味であり、「死んだ」と宣言することもありました。

f:id:nihonbuson:20201109091043p:plain
悪いTDDは死んだ。

おそらく、人々がこの重要な実践をより健康的で生産的な形で理解してもらうことができるでしょう。

TDDとは何ですか?

機械的には、TDDは非常に単純です。

f:id:nihonbuson:20201109092012p:plain
下の文章で説明したTDDの4段階の図

  1. テストで期待される動作をコードで実装していないために失敗する(マイクロ)テストを記述します。
  2. テストが成功するコードをすぐに記述します。図で示した赤​​い矢印に注意してください。失敗したテストがある場合、たとえ多くの変更を元に戻すことを意味しても、プログラマーの唯一の仕事はすべてのテストに成功することです。
  3. コードがこれまでの意図した機能を満たしていることを証明することで大胆になります。それによって、新しい機能が明確、意図的、明白な方法でコードに適切になるように、コードとテストを修正します。
  4. コードがきめ細かく動作し、すべてのテストが成功したので、コードをソース管理システムにコミットし、他の開発者が行った変更を取得します。すべてのテストはまだ成功しています。

TDDは単純な4ステップのプロセスであるため、ほとんどの人は必要なスキルとテクニックを過小評価していました。

  • リファクタリングをサポートするテストを書くことは、いくつかのテクニックを含むスキルです。
  • テストが成功するために必要なコードのみを書くには、かなりの自制心が必要です。
  • リファクタリングでは、コードの構造を変えながら常にテストが成功し続けるために、多くの技術スキルとコード構造のある程度の認識が必要です。
  • 他の開発者と継続的に統一するには、一種の社会契約が必要です。私たちは皆、他の人のコードがうまく書かれていて、すべてのテストが成功していることを当てにしています。

TDDの意図

TDDはプログラミングを衛生的に保つ仕組みです。継続的インテグレーションによるインクリメンタルでイテレーティブな開発をサポートしながら、コードを整然と保ちます。

ここでの整然としたという言葉は、乱雑さ、混乱、ちらかっていることがないことを示唆しています。

整然としたコードは、コードの臭い(Code smells)がないと表現でき、完全に間違っているわけではないでしょう。

コードの臭いとその改善方法を学ぶことで、大きな進歩を遂げることができます。私たちのCode smells albumと無料のcode-smells-to-refactoringsのチートシートは、あなたを良い方向に導くのに役立ちます。

コードの臭いだけに焦点を当てても、プログラマーが整然としたコードを書くことを理解することはできません。他にも必要なスキルがあります。

整然としたコードは、理解と修正が容易なコード(柔軟であり、さらなる開発を妨げないコード)として説明することを好むかもしれません。

今後のブログ記事では、良いコード編成とソースコードの信号雑音比(signal-to-noise ratio、SN比)に関するアイデアの組み合わせについて議論します。ここでは他にも取り上げたいことがあります。

インクリメンタル開発とは、定義されたすべての機能を完成させ、1つのパスまたはセッションですべての制約を収めようとするのではなく、一度に少しずつコードに機能や動作を追加することです。

機能を追加するたびに、チームにインクリメンタル開発を依頼します。アジャイルチームでは、できるだけ頻繁に新たなものを出すように計画しています。

イテレーティブ開発とは、既存のコードを見直し、必要に応じて手直したり、手を加えたりして、全体の設計を改善することです。

これらの再検討は、やり直すといった無駄ではなく、むしろ衛生的な無駄の削減です。私たちは学んだことを取り入れて適用し、すべての機能を考慮してコードが「最初から何をしていたかを分かっているように見える (Ward Cunningham氏の言葉を引用)」ようにします。これは、最初のバージョンが書かれて以来、追加または複雑化したもの、ドメイン知識を得られたものです。

継続的インテグレーションに任せることで、ソフトウェアの変更は、できるだけ早く(可能であれば1日に何回も)他の開発者と共有されます。

他の開発者とコードを共有するには、1日に何度も「安全」かつ「完成」(「完成」の値が小さい場合)である必要があります。そのためには、よりインクリメンタルなアプローチと、高速なテストによって提供される安全性が必要です。

自信を持って頻繁に見直し、修正、機能の追加をして、コードを共有することを安全に行えるような実践(作業衛生)がない限り、これらのふるまいはすべて危険になるでしょう。

これがTDDを行う理由です。TDDはこれらの開発者の行動に安全性をもたらします。

TDDはTestingですか?

一部の人々は、TDDはテスターが行うTesting手法だと思っている人もいます。

TDDは、ソフトウェア開発者がリファクタリング継続的インテグレーションを成功させるために使用する、ソフトウェア開発作業を衛生的に保つ仕組みです。

例に基づいたテスト分野がいくつかありますが、これらはすべて、意図したとおりに使われている良いものです。ATDDとBDDは、テスターを要件と設計の実践にシフトレフトして実行するのが最適かもしれません。リファクタリングをサポートする例に基づいた実践は、マイクロテストユニットテスト、場合によってはストーリーテストなど、最も詳細になる傾向があります。

純化しすぎではありますが、Testingは、製品の使用に対する適合性を評価または確認しようとする活動であると考えてください。それがTestingの場合、TDDはTestingではありません。

TDDは、システムが動いていること、または機能が明確に満たされていることを証明しません。TDDは、主にリファクタリングの条件を作るために存在します。そのためにテスト(マイクロテスト)を使っているからといって、それがTestingの実践になるわけではありません。

TDDの目標は、迅速なリファクタリングの環境を作り出すことであり、より高レベルのテストのほとんどは、実行が遅すぎてこの目的には役立ちません。

TDDサイクルは高速です。1時間に少なくとも12回サイクルを完了できなければ、TDDサイクルを使用する余裕がなくなるまで、作業の速度が低下することになります。私たちはそれを素早く行うか、(最終的には)まったくやらないかのどちらかになります。

UIレベルのテストは良いのですが、UIレベルのテストを実行するためには、アプリケーションのインスタンスとそのすべてのサポートサービスを立ち上げる必要があり、これらのテストは画面またはページの更新を待たなければなりません。これには長い時間がかかり、開発者が生産性を維持しながら1時間に6〜12回実行することはできません。

ストーリーテストやシステムテストはあまりにも多くのセットアップやサポートが必要になるため、2分またはコードの2,3行に1回実行できないという考えがあります。

つまり、TDDはプログラムが書かれた後にテスターができるものではなく、テスターの目的には適していないということです。しかし、テスターが発見する欠陥の数を減らすことができ、テスターがより大きな問題に集中することができます。

TDDはユニットテストを書くことですか?

TDDの目的はユニットテストを作ることであり、もちろん、ユニットテストの密度はコードカバレッジのパーセンテージで測定できると考える人もいます。これは誤りです。

ユニットテストを作成し、ソースコードのテストカバレッジを増やす方法はたくさんあり、そのうちの1つがTDDというだけです。

確かに、少しのコードを書いて、そのコードを通るすべてのパスをカバーするユニットテストを書くことによって、非常に良いテストカバレッジを得ることができ、多くの素晴らしいユニットテストを生成することができます。これらのテストは、コードが作成された後に書くことができます。なぜ違うのでしょうか?

実際、コードの完成後にテストを作成する(コードの検証に必要な一連のテストを正確に作成する)方が効率的かもしれません。TDD経由でコードを完成させ、TDDでは考慮しなかった状況をカバーするテストを追加していることが分かります。

ただし、TDDの目的は、ユニットテストを作成してテストカバレッジを増やすことではありません。これは起こりますが、それは付随的な作用でもあります。

ユニットテストを行うことは良いことであり(さらに高レベルのテストは素晴らしいことです)、チームから回避された欠陥を減らすことができますが、ここでの本当の目標は、コードをリファクタリングするための環境をすばやく作成することです。

TDDだけで良いのでしょうか?

システムが動くことを証明するには不十分であるため、TDDは役に立たないと主張する人もいます。TDDは小さな部品が確実に動くようにするには役立つようですが、組み立てられたシステムが正しく動作することを証明するには、ほとんど(またはまったく)役に立ちません。

彼らの言うことは正しいです。それがTDDの目的ではありません。

しかし、この論理を使えば、抗生物質線維筋痛症を治さないため、役に立たないと言うこともできます。同様に、ツーリング用の自動車も、道路があるところにしか乗れないので、無価値になります。

TDDがソフトウェア品質に対して完全に十分ではないと主張することは、ポイントを外しています。重要なのは、TDDは(うまくやれば)目的には容易に十分になりますが、その目的はシステムの十分性や正確性を証明することではないということです。

システムが正常に動くことを確認したい場合は、Testingが必要です。目的や原動力が異なるため、TDDとは異なります。

上記のTDDはTestingではないという議論を参照してください。

TDDは良い設計を強要しません

はい。TDDでアサーションが進むことについては納得しますが、優れた設計を強要することはTDDの目的ではありません。

TDDでうまく動けば、迅速なフィードバックで設計を修正できます。イテレーティブな作業およびインクリメンタルな作業は簡単に可能であり、粗悪な設計も短期間ではるかに優れた設計にリファクタリングできます。

TDDでは設計を行いませんが、設計を改善するための多くの機会を提供します。

実際、TDDサイクルのすべてのループには、リファクタリングのための時間が明示的に提供されています。その時間を利用しないと、リファクタリングが必要なのにリファクタリングされていないコードになってしまう可能性が高いのです。

TDDループを使い始めたからといって自動的にリファクタリングの使い手になるわけではありませんが、TDDプロセスは、これらのスキルを学ぶ機会を提供します(Refactoring albumでも説明しています)。

悪いテストはリファクタリングを妨げます

繰り返しますが、ここは完全に合意します。

悪いテストがあることは、テストがないことよりも悪い場合があります。

TDDサイクルを無意識にループすることで、すべてのテストとすべてのコードが完璧になるとしたら、それは素晴らしいです。私は初心者の前にTDDの図を投げて、立ち去ることができたら嬉しいでしょう。

しかし、TDDは一連のスキルと態度です。これらのスキルと態度は、実践と情報を通じて開発されなければなりません。

TDDを行う私たちのほとんどは、これらのスキルとテクニックをしばらくの間、勉強しなければなりませんでした。これらは明白で簡単なものではありません。TDDをどのように行うべきかについては、さまざまな陣営に分かれています。

我々はTDDにおける、いわゆる「アンチパターン」と失敗の方法を文書化しました。

ただし、TDDサイクルの中にはリファクタリングフェーズが含まれています。リファクタリングフェーズでは、テストと設計をリファクタリングできるため、長いセットアップ手順と脆弱なタイミングでの酷いテストに耐える必要がなくなります。

また、TDDは「テストを持つ」ことではなく、コードの変更や追加を迅速かつ簡単にするために必要なことを行うことを目的としています。

テストによって気分を害したら、切り捨てましょう。テストは、小さく、安価で、高速で、簡単に破棄できる必要があります。マイクロテストの書き方を学ぶことは、学ぶ必要のあるテクニックの1つです。これは、Microtesting albumで教えているテクニックです。

Twitter上のある批評家は、TDDはモックとフェイクに依存しているため、テストを数十回(場合によっては数百回)書き直さないとコードを安全にリファクタリングできないと指摘しています。もしそうであれば、TDDが必要とする方法でリファクタリングをサポートするために、フェイクやモックを使う手法を学ぶことをお勧めします。この手法は、さまざまなソースから学ぶことができます。もちろん、Faking and Mocking albumで教えています。

なぜ悪評が多いのですか?

悪評は主に正当に苦労した人々からのものです。彼らはブログやソーシャルメディアを利用して、どのように苦労したか、そしてなぜ彼らが気になる困難なことをやめたのかを説明します。彼らは他の人に警告することで、イライラする無駄な時間から救います。

これは私が同意する行動です。私は、酷いTDDをしたり、意図しない理由でTDDをするよりも、TDDをしない方がいいと思います。ひどいTDDをして、それが提供しない利益を期待しますか?酷そうですね。

TDDを上手に行うことを学ぶための素晴らしいリソースはたくさんあります。多くのブログ記事があります。Kent Beck, Jeff Langr, James Grenningなどの専門家によって書かれた、TDDを教える素晴らしい本がいくつかあります。また、youtubeやオンライントレーニング会社のビデオもたくさんあります。

さらに、Industrial Logicでは、レガシーコードを扱うためのヘルプを含む「The Testing And Refactoring Box Set」と呼ばれるボックスセットに、(上記のアルバムを含む)最高の学習の多くを収集しました。また、これらのリソースは、ライブのTesting and Refactoring Workshopsでも活用しています。

TDDがうまくいけば、リファクタリングをサポートし、継続的インテグレーションによるインクリメンタルかつイテレーティブな開発を可能にします。あなたがしていることがこれらの働き方を可能にできず、サポートしない場合は、自身やあなたのバージョンのプロセスを非難するのではなく、それを動かすテクニックを勉強することを検討してください。

TDDは作業衛生です。それは学ばなければならない多くのテクニックを含む包括的な練習です。それをうまくやることに投資すれば、配当を受けることができます。

初期のレビュアーであるJesusVega、Josh Kerievsky、Mike Rieser、Jeff Langr、John Borys、JennyTarwaterに特に感謝します。