はじめに
このエントリは2016/5/7に開催したNiigata.NET 2.0で行ったセッションを再構成したものです。
ライセンス
この 作品 は クリエイティブ・コモンズ 表示 – 継承 4.0 国際 ライセンスの下に提供されています。
Agenda
テスト駆動開発(TDD)とは
テスト駆動開発とは、Kent Beck(ケント・ベック)が提唱した開発手法です。彼の著書であり、「原典」とも呼ばれる「テスト駆動開発入門」(http://www.amazon.co.jp/dp/4894717115)(残念ながら絶版。再販望む!)にはこのように書いてあります。
「動作するきれいなコード」、ロン・ジェフリーズのこの簡潔な言葉は、TDD(テスト駆動開発)の目標である。動作するきれいなコードは、あらゆる理由で価値がある。
それでは、どのように「動作するきれいなコード」を目指していくのでしょう?
TDDの進み方
まず、「動作するきれいなコード」にたどり着くには2つの道があります。一つはきれいなコードを書きながら動作するように直していくもの。もう一つは動作するコードを書きながらきれいに直していくものです。
(図は日本におけるTDDの第一人者である「和田 卓人(@t_wada)」さんのスライド「TDDのこころ」より拝借しています。)
TDDはこのうち後者の「着実に一歩ずつ進む道」をとります。つまりまず動くようにしてからきれいにしていきます。なぜこの道かというと、何はともあれ「動作」していなければ、製品としての価値はないということが一つです。
他に、この「動かない」→「動く」の線を超えるときに多くの「気づき」が得られるということもあります。フィードバックは早ければ早いほど修正が容易ですので、まずは動くように書き、フィードバックを得て素早く修正していくのがTDDということになります。
TDDのサイクル
TDDはこの着実な道を次のようなサイクルで進んでいきます。
- 次の目標を考える
- その目標を示すテストを書く(テストファースト)
- そのテストを実行して失敗させる(Red)
- 目的のコードを書く
- 2で書いたテストを成功させる(Green)
- テストが通るままでリファクタリングを行う(Refactoring)
- 1~6を繰り返す
そしてこの手順を小さく回すことで、素早いフィードバックを得ることができます。
これをTDDの界隈では「黄金の回転」と呼んでいます。これはくるくると回りながらどんどんと高みを目指していくイメージから、荒木飛呂彦先生の「スティール・ボール・ラン」に出てくる「黄金の回転」が連想されるため、このように呼ぶようになったそうです。
(スティールボールラン、荒木飛呂彦著より)
動作するきれいなコード
では、「動作するきれいなコード」とは何でしょう?それは原典の該当箇所の原文を見ればわかります。
「動作するきれいなコード」は「Clean code that works」であり「Beautiful」ではないのです。つまりは整頓されたコードがきれいなコードであるということになります。
とはいえ、もう少し具体的な指針が欲しいところです。そこで、「きれい」なコードとは何かを知ることのできる書籍をいくつか紹介します。
まずは「リーダブルコード」です。サンプルコードはC#など.NET言語ではありませんが、「リーダブル≒読みやすい」コードを書くためのテクニックに「名前」を付けて完結にまとめているため、最初に読む1冊としては最適です。
次に「リファクタリング」です。コードの挙動を変えずに構成を直す「リファクタリング」のテクニックについて網羅的にまとまっています。「きれいなコード」にどのように直せばよいのかを学ぶのによいでしょう。なお、この本もTDD入門と同じく一度絶版になりましたが、オーム社により再販されています。
これまでの2冊は実際のコードに近い話でしたが、「きれいな」コードは「きれいな」設計から生まれます。そのため、オブジェクト指向プログラミング言語を使ったアプリケーションの「設計」に関する原則を学ぶことも重要です。それを学ぶための書籍を2つ紹介します。
まずは「C#実践開発手法」です。これはその名の通り、C#を使っていわゆる「SOLID原則」と呼ばれるような設計原則を、実際にどのように使えばよいのか実例とともに紹介しています。こういった分野の書籍には「アジャイルソフトウェア開発の奥義」がありましたが、コードがJava(もしくはC++、Smalltalk)だったので、C#でコードが読めるというだけでも本書はありがたいです。もちろん、内容もより新しいものになっています。
そして、オブジェクト指向プログラミング言語の設計原則について学ぶなら「Head First デザインパターン」がおすすめです。本書のタイトルは「デザインパターン」となっていますが、実際はSOLID原則やハリウッド原則といった基本原則を学ぶのに最適な本だと私は思っています。サンプルコードはJavaですが、C#を知っていればそれほど苦も無く翻訳して読めるでしょう。
また、きれいなコードは既存のライブラリ等との統一感も重要です。特に.NET Frameworkは標準ライブラリが巨大なので、勝手なルールでライブラリを作ってしまうと、利用者が混乱してしまいます。
そこで、標準ライブラリの設計ガイドラインを知るために、「.NETのクラスライブラリ設計」も強くお勧めします。この本は単にライブラリ設計のガイドラインが書いてあるだけでなく、過去の失敗についての.NETの中の人の議論が掲載されていることもおすすめポイントの一つです。
TDDの目的
さて、こういった書籍などを参考にしつつも「きれいなコード」を目指すことがTDDの目標であることはすでに述べたとおりです。では、目標のさらに先にある「TDDの目的」はなんでしょうか。それは、一言でいうと「健康」です。
TDDはテストを書きますが、それは大きな目的を実現するための手段でしかありません。TDDで目標とするのは「動作するきれいなコード」は、すなわち「変化に対応」しやすいコードであるともいえます。
変化に対応しやすいコードは、言い換えれば不安が少ないコードだともいえます。ユーザーの要望をかなえるためには、コードを変化させなければいけませんが、「動作するきれいなコード」ならば、安心して変更することができます。
そして、この「不安が少ない」状態を一言で表せば、それが「健康」であるということです。
そして、「健康」なコードは不安が少ないため、開発者自身の「健康」にもつながります。一人一人の開発者が健康なら、チーム全体も「健康」であり、ひいては開発、運用している製品、サービスとその開発者、ユーザーなど関係者全員の健康にもつながります。
この大きな目的のために、まずは目標として「動作するきれいなコード」を維持するよう、TDDを行っていきます。
.NETにおけるTDD
TDDがどういうものかわかりましたが、.NET開発においてTDDをどのように進めていけばよいのでしょうか?次の4つの項目に焦点を当て、順に説明していきます。
- テスティングフレームワークおよびサポートツール
- ソリューション構成
- テストコード
- テスト実行
テスティングフレームワーク
まずは、.NETで使える主なテスティングフレームワークをいくつか紹介します。
まず最初は「MSTest」です。MSTestはVisual Studioに標準搭載されたテスティングフレームワークで、現在はExpress Editionでも使用できるため、お手軽さはNo.1です。
次は「NUnit」です。JUnitをはじめとした、いわゆるxUnit系列の正統派オープンソーステスティングフレームワークで、MSTestがVSの下位エディションに搭載されるまでは、デファクトスタンダードでした。
最後は「xUnit.net」です。上記2つより新しく、テストコードの書き方等を一から見直したテスティングフレームワークで、MSのASP.NETチームなどでは、最近のオープンソースプロダクトのテストに使用されています。
ではどのテスティングワークを選べばよいのでしょうか?個人的にはMSTestかNUnitをお勧めします。
MSTestは何といってもVSを入れれば入っているというお手軽さが一番で、必要最小限の機能はあるので、入門には最適だといえるでしょう。また、NUnitは歴史があることもあり、Web上の日本語情報も多く、MSTestよりは高機能(パラメタライズド・テストなど)です。
サポートツール
次にサポートツールについて見ていきましょう。まずは「テストアダプター」です。
MSTestは標準でVS上からテストを実行することができますが、その他のテスティングフレームワークは、専用のテストランナーを使う必要がありました。それを改善し、VSからテストを実行できるようにするのが、「テストアダプター」です。
テストアダプターを導入すれば、NUnitやxUnit.netのテストコードも、VS上から実行して結果を確認できるようになります。
また、テストコードの書き味を改善するため、「Chaining Assertion」というライブラリもお勧めです。
Chaining Assertionはテストコードの書き方を、A.Is(B)の形式で書けるようにします。MSTestの他、NUnitやxUnit.netにも対応しています。後で実例を出すときに、Chaining Assertionを使ったコード例を使って説明します。
ソリューション構成
次にTDDを行うソリューションの構成を見ていきましょう。.NETでTDDする場合、テスト対象プロジェクトとテストプロジェクトの複数プロジェクト構成にするのが一般的です。
まずテスト対象プロジェクトは*.exe形式の実行ファイルや*.dllのクラスライブラリどちらでも大丈夫です。ただ、クラスライブラリにする方が一般的ではあります。
そして、テストプロジェクトが別になるため、テスト対象の型は原則publicとします。internalな型についても、テスト対象プロジェクトにアセンブリ属性InternalsVisibleToを使い、テストプロジェクトに対してのみ公開することもできます。
次にテストプロジェクトですが、MSTestを使う場合は「単体テストプロジェクト」を選択します。他のOSSテスティングフレームワークを使う場合は、クラスライブラリプロジェクトを作成した後、使用するテスティングフレームワークへの参照を追加します。
テストコード
次にテストコードの構成を見てみましょう。今回はMSTest+前述のChaining Assertionを使ったテストコードについて説明します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
namespace Test.MSTest | |
{ | |
[TestClass] | |
public class UnitTest1 //テストクラス | |
{ | |
[TestInitialize] | |
public void SetUp() { /*テスト初期処理*/ } | |
[TestCleanup] | |
public void TearDown() { /*テスト後処理*/ } | |
[TestMethod] | |
public void TestSomething() | |
{ | |
//テスト主処理 | |
“実際の値”.Is(“期待する値"); | |
} | |
} | |
} |
まず、MSTestのテストクラスであることを示すため、TestClass属性をクラスに付けます。テストクラスはpublic出なければならないことに注意してください。
次に、実際のテストを行うメソッドにはTestMethod属性を付けます。テストメソッドもpublicとし、戻り値はvoid、引数はなしとします。テストメソッドは必要な数だけ複数個作ってかまいません。テストメソッド内では、実際の値と期待値が等しいことを、Isメソッドを使い検証します。
この他、それぞれのテストメソッドが実行される前に毎回実行したい処理、例えばテスト対象オブジェクトの初期化などは、TestInitialize属性のついた、いわゆるSetUpメソッドに書きます。同様に、テストメソッド実行後に毎回実行したい後処理は、TestCleanup属性のついた、いわゆるTearDownメソッドに書きます。
テスト実行
こうして作成したテストコードは、Visual Studio上から実行すると、「テストエクスプローラー」にテストメソッドごとに成功、失敗、そして失敗ならその失敗した箇所の情報が表示されます。
このようにして、VS上でテストを行っていきます。
TDDの実例
それでは、実際にTDDを行っている様子の実例を見せましょう。今回はFizzBuzzを題材として使用します。作成したコードは、GitHubにアップしているので、併せて参照してみてください。
設計
まず最初に行うのは「設計」です。作成するプログラムの仕様を元に、ざっと設計して必要と思われるタスクをToDoリストとして作成します。最初にこの工程を行うことで、事前の目標付けとともに、仕様であいまいな部分やどのようにテストを行うのかといった不安要素をなるべくなくします。
最初のテスト作成
ToDoリストを作成したら、その中から一つ選び最初の「失敗する」テストを作成します。今回は「正の整数の場合、その数値を文字列で返す」を選び、このことをテストするために「引数が1の場合、”1″を返す」ことを確認するテストコードを書きます。
この時、自分が「最初のユーザー」となり、使いやすいAPI設計を考えることが重要です。それには、型名やメソッド名、引数の名前や型、戻り値の型など、様々な観点があります。
今回はまずテスト対象プロジェクトFizzBuzzとテストプロジェクトFizzBuzz.Testを作成し、FizzBuzz.TestプロジェクトにFizzBuzzTest.csを追加してFizzBuzzTestクラスを作成しました。なお、テスティングフレームワークにはMSTestを使い、Chaining AssertionをNuGet経由でインストールしています。
PM> Install-Package ChainingAssertion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
namespace FizzBuzz.Test | |
{ | |
[TestClass] | |
public class FizzBuzzTest | |
{ | |
[TestMethod] | |
public void 引数が1の場合文字列で1を返す() | |
{ | |
// arrange | |
var fizzBuzzer = new FizzBuzzer(); | |
// act | |
string result = fizzBuzzer.Say(number: 1); | |
// assert | |
result.Is("1"); | |
} | |
} | |
} |
(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/af2891d63f95b2ea60c88e643b335d0ca14159bd/FizzBuzz.Test/FizzBuzzTest.cs)
.NET開発で最初のテストコードを書く時のポイントは以下の通りです。
- 戻り値の型を明示する
- 引数の型を明示する
- 名前付き引数で引数名を明示する
こうしておくことで、次のフェーズを行う際、VSの機能を使ってコードを自動生成できます。
またこの他、テストコードは適宜日本語で書くことで、その意図がわかりやすくなることもあります。特にC#はメソッド名に使える文字が限られているため、日本語の表現力に頼るのは理にかなっています。
最初のテスト失敗
テストコードを作成したら、テストを実行して失敗することを確認します。ここではテストの役割のうち「正しく失敗する」ことを確認することが重要です。
なお、「最初の失敗するテスト」の失敗にはコンパイルエラーも含めてよいです。この段階ではFizzBuzzerクラスは無いのでコンパイルエラーになるはずです。
仮実装(Fake It)
テストが正常に失敗することを確認したら、いよいよテスト対象コードの実装に入ります。まず最初に行うのは、「仮実装(Fake It)」です。仮実装とは「テストが成功する最速の実装」のことで、リテラル定数を使ってテストが成功するように書いてしまいます。最初に作成したテストコードであれば、戻り値として”1″を返せば、テストが成功するはずです。
一見意味がないようにも見えますが、この手順には失敗しようのない状態で「テストが正しく成功する」ことで、テストの妥当性を担保するという目的があります。もし仮実装でもテストが失敗するようなら、その後本当の実装にしてもテストが成功するわけはないのですから。
VSで仮実装を行う際、まずはテストコードからテスト対象クラスを生成します。コンパイルエラーとなっているFizzBuzzerクラスの箇所でCtrl+.キーを押し、「新しい型の生成…」を選択します。
そして、表示された「型の生成」ダイアログにて、「種類」にclass、「場所」にFizzBuzzプロジェクト、そして「ファイル名」に”FizzBuzzer.cs”を指定して「OK」ボタンをクリックします。
すると指定した通り、FizzBuzzerクラスがFizzBuzzプロジェクトのFizzBuzzer.csファイルに作成されます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace FizzBuzz | |
{ | |
public class FizzBuzzer | |
{ | |
public FizzBuzzer() | |
{ | |
} | |
} | |
} |
次に、FizzBuzzTest.csに戻り、コンパイルエラーとなっているSayメソッドの生成を行います。Ctrl+.を押し、「メソッド ‘FizzBuzzer.Say’ を生成します」を選択します。
すると、未実装のメソッドSayが生成されます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace FizzBuzz | |
{ | |
public class FizzBuzzer | |
{ | |
public FizzBuzzer() | |
{ | |
} | |
public string Say(int number) | |
{ | |
throw new NotImplementedException(); | |
} | |
} | |
} |
テストクラスで引数名、型、戻り値の型を明示していたことで、それに合わせたシグネチャでメソッドが生成されます。
最後に、Sayメソッドが1を返すように仮実装すれば終了です。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace FizzBuzz | |
{ | |
public class FizzBuzzer | |
{ | |
public FizzBuzzer() | |
{ | |
} | |
public string Say(int number) | |
{ | |
return "1"; | |
} | |
} | |
} |
この時、FizzBuzzTest.csファイルのSayメソッド呼び出しか所でAlt+F12を押すことで、「定義をここに表示」すると便利です。
最初のテストの成功
テストを実行し、成功することを確認します。Ctrl+R, Aキーを押すことで、すべてのテストを実行できます。
無事テストが成功しました。
リファクタリング
テストが成功したので、今度はリファクタリングです。テストが成功する状態を保ったまま、コードを整理します。言い換えれば、挙動を変えずに構造を改善するということです。
なお、テストコードを整えるのもリファクタリングの一つです。ここでは、テストコード内で明示した型や引数名を取り除いてみましょう。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
namespace FizzBuzz.Test | |
{ | |
[TestClass] | |
public class FizzBuzzTest | |
{ | |
[TestMethod] | |
public void 引数が1の場合文字列で1を返す() | |
{ | |
var fizzBuzzer = new FizzBuzzer(); | |
fizzBuzzer.Say(1).Is("1"); | |
} | |
} | |
} |
(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/42483eb7eaa217d80440a026572ffd211bb3ddab/FizzBuzz.Test/FizzBuzzTest.cs)
コードを変更したら、テストを実行して成功することを確認しておきましょう。
次の失敗するテストを追加する
先に進むため、「小さな一歩」となるような新たなテストを追加します。この時、先ほど仮実装したメソッドにて、同じ仕様を満たすもう一つのテストコードを追加することで、実装1点に対してテスト2点の「三角」で進めることを「三角測量」と呼びます。今回の例でいえば、「正の整数の場合、その整数を文字列にして返す」という仕様に対して、最初に作った1を”1″にして返すテストに加え、2を”2″にして返すかどうかを確認するテストを追加することで、三角測量を行います。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
namespace FizzBuzz.Test | |
{ | |
[TestClass] | |
public class FizzBuzzTest | |
{ | |
[TestMethod] | |
public void 引数が1の場合文字列で1を返す() | |
{ | |
var fizzBuzzer = new FizzBuzzer(); | |
fizzBuzzer.Say(1).Is("1"); | |
} | |
[TestMethod] | |
public void 引数が2の場合文字列で2を返す() | |
{ | |
var fizzBuzzer = new FizzBuzzer(); | |
fizzBuzzer.Say(2).Is("2"); | |
} | |
} | |
} |
(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/f21f34237754464babe6e6746a7f52df94295f4b/FizzBuzz.Test/FizzBuzzTest.cs)
すべてのテストを実行
テストを追加したらすべてのテストを再実行し、追加したテストだけが失敗することを確認します。もしこの時複数のテストが失敗するようであれば、それは良くない設計であることを示す臭いです。「良くない」とは仕様でなく内部実装に対してテストしている、責務分割が不十分で一つの変更があちこちに影響するような作りになっている可能性が高いのです。
無事2つ目のテストだけが失敗しました。
テストが成功するまで修正
あとは、失敗した2つ目のテストについて、テストが成功するまで実装を行います。このとき、仮実装で使用した定数、リテラルを、引数や変数を使うように直します。サンプルでは、Sayメソッドの引数numberを文字列にして返すようにします。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace FizzBuzz | |
{ | |
public class FizzBuzzer | |
{ | |
public FizzBuzzer() | |
{ | |
} | |
public string Say(int number) | |
{ | |
return $"{number}"; | |
} | |
} | |
} |
(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/f21f34237754464babe6e6746a7f52df94295f4b/FizzBuzz/FizzBuzzer.cs)
修正後にテストを実行し、2つのテスト両方が成功することを確認します。
再びリファクタリング
2つのテストで共通で使っているfizzBuzz変数をフィールドにしてSetUpメソッドで初期化したり、整数が整数の文字列を返すテストは一つあれば十分なので、一方を消したりします。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
namespace FizzBuzz.Test | |
{ | |
[TestClass] | |
public class FizzBuzzTest | |
{ | |
private FizzBuzzer fizzBuzzer; | |
[TestInitialize] | |
public void SetUp() | |
{ | |
fizzBuzzer = new FizzBuzzer(); | |
} | |
[TestMethod] | |
public void 引数が正の数の場合文字列でその数を返す() | |
{ | |
fizzBuzzer.Say(1).Is("1"); | |
} | |
} | |
} |
(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/f3ffd123918d51a20a2e6cb8b4892097a9adccbe/FizzBuzz.Test/FizzBuzzTest.cs)
テストを実行し、成功することを確認します。
ToDoリストを更新する
ここまでで事前に作成したToDoリストの最初の仕様の実装が終わったので、完了したタスクを消してしまいます。
次のテストを追加する
今度は別の仕様について実装していこうと思うので、次のテストを作成し、追加します。3の倍数のテストを追加してみます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
namespace FizzBuzz.Test | |
{ | |
[TestClass] | |
public class FizzBuzzTest | |
{ | |
private FizzBuzzer fizzBuzzer; | |
[TestInitialize] | |
public void SetUp() | |
{ | |
fizzBuzzer = new FizzBuzzer(); | |
} | |
[TestMethod] | |
public void 引数が正の数の場合文字列でその数を返す() | |
{ | |
fizzBuzzer.Say(1).Is("1"); | |
} | |
[TestMethod] | |
public void 引数が3の倍数の場合Fizzを返す() | |
{ | |
fizzBuzzer.Say(3).Is("Fizz"); | |
} | |
} | |
} |
(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/f4b7c5a74d3ccada1787a583b96ab66d613705e4/FizzBuzz.Test/FizzBuzzTest.cs)
明白な実装
実装が自明である場合、仮実装をスキップしてしまっても構いません。これを「明白な実装」と呼び、イメージとしては開発スピードアップのため、ギアをあげるイメージです。今回は引数を3で割った余が0の時に”Fizz”を返すよう実装します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace FizzBuzz | |
{ | |
public class FizzBuzzer | |
{ | |
public FizzBuzzer() | |
{ | |
} | |
public string Say(int number) | |
{ | |
if (number % 3 == 0) | |
{ | |
return "Fizz"; | |
} | |
return $"{number}"; | |
} | |
} | |
} |
(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/da6c8c0288634b67789fcfa17965bf4ff761910c/FizzBuzz/FizzBuzzer.cs)
以上のような手順を繰り返して、ToDoリストの項目を全部消したら、実装が完了したということになります。
TDDの指針
最後に、TDDを行う上でのいくつかの指針を紹介します。
いつTDDを行うか
まず、「いつ」TDDを行えばよいのかというと、それは「不安」を感じたとき、ということになります。例えば、ライブラリの使い方に自信がなかったり、第三者の実装が信用できない時など、開発に関連する不安があれば、それをテストで保護してあげるのです。
ただし、今作成しているのが「使い捨て」のコードであれば、TDDを行わない方が良いこともあります。テストコードを作成しても、そのテストが数回しか実行されないようであれば、テストはただのコストでしかありません。
その他、GUIのコードについてはTDDを行いません。GUIの自動化テストを作成するには非常に手間がかかり高コストな作業です。しかし、GUIは一番ユーザーに近いところであることから、変更が頻繁に起こります。そうすると、せっかく作ったテストもすぐに変更しないといけなくなってしまいます。
また、トライ&エラーであれこれ試すような時もTDDは向いていません。ただし、あれこれ試した結果、確定した実装に対してテストを書いても問題ありません。「テストファースト」にはこだわらなくても大丈夫です。
どんなテストが良いテスト?
一般的に「良い」とされるテストの特徴に「F.I.R.S.T」と呼ばれるものがあります。
- Fast(高速)
テストの実行がすぐ終わること - Independent(独立している)
他のテストへの影響がなく、任意のテストメソッド単体でも実行可能であること - Repeatable(繰り返し実行可能)
何度実行しても同じ結果となること - Self-Validationg(手動での検証がない)
テストコードのみでテストが完結していること。 - Timely(テストファースト)
最初にテストを書いていること
これら5つの特徴には優先度があり、特に重要なのはIとR、続いてF、S、Tです。少なくともIとRだけは考慮したテストを書きましょう。
どのようにテストを行うか
ToDoリストを元にどのようなテストを行えばよいのかについては、各種のテスト技法を参考としましょう。基本的なテスト技法として、同値クラス、境界値分析くらいは考慮したテストを書きましょう。
既存コードがある場合
「テストがないコードはレガシーコードだ!」のキャッチコピーでおなじみの「レガシーコード改善ガイド」を参考に、小さく初めて少しずつ範囲を広げていきましょう。
最後に
最後に大事なことですが、TDDはあくまで「センス」ではなく「スキル」です。訓練次第で誰でも習得画家のなものなのです。たまに「テストが書けるようになったらTDDをやってみよう」みたいなことを聞くことがありますが、「できるようになったら始める」というのは永遠に始めることはできません。まずは始めてみて、うまくいかなければ直せばよいのですから。
また、本来はチームを巻き込んだ形TDDができるのが理想ですが、まずはあなた一人でも始めることができます。やってみて、良いものであれば他の人に勧めて、徐々にTDD人口を増やしていきましょう。
そして、TDDによって安心を得て、「健康になりましょう」
参考資料