http://martinfowler.com/bliki/UnitTest.html

2014/5/5

ソフトウェア開発において、ユニットテスティングの話題になることが多い。私がプログラムを書きはじめて以来ずっと、ユニットテスティングという言葉はおなじみだった。 しかし、ソフトウェア開発用語の常として、ユニットテスティングという用語もきちんと定義できていない。 ユニットテスティングという用語の意味を実際よりも厳密にとらえてしまったせいで、混乱してしまっている人もよく見かける。

ユニットテスト

もちろんそれ以前からもユニットテスティングはやってきていたのだが、それを人前で公表したのは、Kent Beckと仕事をして Xunit系のツールを使い始めたころのことだった (この種のテストのことは、ユニットテスティングっていうより「xunitテスティング」って呼んだほうがいいと思うんだ)。 ユニットテスティングはその後、エクストリームプログラミング(XP)の看板アクティビティとなり、 やがてテスト駆動開発が誕生した。

XPが始まった頃から、XP界での「ユニットテスティング」の定義には懸念があった。 当時のusenetでの議論を思い出す。XPな人たちが、テスティングのエキスパートたちに「おまえたちは『ユニットテスト』という言葉の意味をはき違えている」とつるし上げられたんだ。 「じゃあ、本当の意味を教えてくださいな」というと、答えは「ワシの研修に参加しろ。午前中に、ユニットテストの24の定義を教えてやろう」みたいな感じだった。

定義は人それぞれだが、共通するところもいくつかある。 まず、ユニットテストは詳細レベルのテストで、ソフトウェアシステムの小さなパーツを対象とするものだ。 次に、最近のユニットテストは、通常はプログラマー自身が普段使っているツールを用いて書くものだ。なんらかのユニットテスティングフレームワークを使うことがあるかもしれない。[*1] そして、ユニットテストは、他のテストよりもすばやく実行できるものと見なされている。

こんなふうに共通する部分もあれば、それぞれ違っている部分もある。 違う部分のひとつとして挙げられるのが、何をもって’‘ユニット’‘とするのかという点だ。 オブジェクト指向設計では、ひとつのクラスを「ユニット」と扱うことが多い。 一方、手続き型や関数型のアプローチでは、ひとつの関数を「ユニット」と見なすかもしれない。 でも、実際のところ、これは状況によるものだ。何を「ユニット」と見なすのかは、チームがそのシステムやテストをどのように理解しているのかに応じて決めるものだ。 私は、とりあえずひとつのクラスを「ユニット」として始めたいと思っているが、関連するクラス群を一括で「ユニット」と扱うこともよくある。 逆に、ひとつのクラスの中の一部のメソッドだけを「ユニット」とすることは、めったにない。 これはあくまでも私の考えなので、他の人がどう思うかは特に気にしない。

分離

もっと重要な違いがある。テスト対象のユニットを、他のコンポーネントから分離すべきか否かというところだ。 orderクラスのpriceメソッドをテストしているとしよう。 priceメソッドの内部では、productクラスやcustomerクラスの機能を実行する必要がある。 他のコンポーネントから分離するという原則に従うなら、ここで実際のproductクラスやcustomerクラスを使うのは避けたい。 たとえばcustomerクラスの挙動に問題があったときに、orderクラスのテストも失敗してしまうからだ。 そんな時には、実際のコンポーネントの代わりにテストダブルを使う。

分離

でも、ユニットテストでわざわざそんなことはしない、という人もいる。 実際、90年代にxunitテスティングが始まったころは、よっぽどのことがない限りこんな細工はしなかった (「よっぽどのこと」とは、たとえば外部のクレジット審査システムを使っているなどの場合だ)。 たとえテスト対象以外のところが問題でテストが失敗していたとしても、その原因を探るのはそれほど苦にならないと考えられていた。 なので、実際のところ、わざわざ分離するほどの問題だとは思わなかったのだ。

分離を気にしなかったことも、当時「おまえらのユニットテスティングは間違っている」と批判された原因のひとつだろう。 でも私は、それもまた「ユニットテスティング」だと思っていた。だって、単体のユニットの振る舞いをテストしているという点では同じなのだから。 私たちは、テスト対象のユニット以外はすべて正常に動くものだという前提のもとで、テストを書いている。

2000年代に入ってxunitテスティングがはやりだしたころに、分離主義がふたたび登場してきた。 モックオブジェクトや、モッキングをサポートするフレームワークが現れたのだ。 xunitテスティングは二つの学派に分裂した。私は両者を古典主義者そしてモック主義者と呼んでいる。 古典主義のxunitテスターは分離など気にしないが、モック主義者は分離を気にする。 どちらの考えかたも理解できるし、どちらの考えかたも尊重している(ただ、個人的には、古典主義者の考えに近い)。

私みたいな古株のテスターだって、扱いづらい外部コンポーネントがあればテストダブルを使う。 リモートサービスとの通信時の非決定論を取り除けるという価値は計り知れない。 古典主義者の中にも、データベースやファイルシステムなどの外部リソースについてはテストダブルを使うべきだと主張する人がいる。 非決定論のリスクや、スピードのリスクを考慮した意見だ。 有用な指針だとは思うが、私は「外部リソースにはテストダブルを使う」と絶対的に決めてしまう気にはならない。 そのリソースが十分に安定していて、かつ高速にやりとりできるのなら、ユニットテストで直接それを使わない理由はないと思っている。

スピード

ユニットテストに共通する性質(スコープが小さい、プログラマー自身が実行する、高速に行う)はつまり、 プログラミングの際に頻繁にユニットテストを実行できるということを意味する。 実際これは、自己テストコードの鍵となる特性でもある。 プログラマーは、コードに変更を加えるたびにユニットテストを実行する。 私の場合は、コンパイルできるコードがあるときにはいつもテストを実行するので、実行頻度は1分間に数回のレベルになる。 もし間違ってどこかを壊してしまったときには、すぐに気づけるようにしたいからだ。 不具合の原因となった変更の直後にその不具合に気づければ、バグを見つけるのもたやすくなる。今いじったところだけを見ればいいわけだ。

このように高速にユニットテストを回すときには、必ずしも毎回すべてのテストを実行しなくてもかまわない。 通常は、今作業中のコード周りのテストだけを実行すればいいだろう。 テストの深さをある程度犠牲にして、テストスイートの実行時間を取ることになる。 私はこの手のテストスイートを’'’コンパイルスイート’'’と呼ぶ。「コンパイルしようかな」と思ったとき(Rubyみたいなインタプリタ型の言語でも同じだ)に常に実行するからだ。

継続的インテグレーションを使っているなら、テストスイートもその一環で実行すべきだ。 このときの使うテストスイート(私は’'’コミットスイート’'’と呼ぶ)には、すべてのユニットテストを含めるのが一般的だ。 それ以外に、いくつかのBroadStackTestsを含めることもある。 プログラマーとして、このコミットスイートは1日に何度か実行すべきだ。バージョン管理システムへのコミットの前に行うのはもちろん、 それ以外の場合でも、たとえば休憩前や打ち合わせに出る前などに行うといい。 コミットスイートの実行速度が上がれば上がるほど、より頻繁に実行できるようになる。[*2]

ユニットテストやテストスイートの実行速度については、人によっていろんな意見がある。 David Heinemeier Hansson は、コンパイルスイートが数秒で実行できて、コミットスイートが数分で終わるようなら問題ないと考えている。 Gary Bernhardt は、それでは我慢できないようだ。コンパイルスイートは300ms程度で実行できるべきだと言う。 また、Dan Bodart は、コミットスイートに10秒以上かけたくないとしている。

絶対的な答えがあるとは思っていない。 コンパイルスイートが1秒以内で終わろうが数秒かかろうが、個人的にはあまり差がないと思っている。 私はKent Beckの経験則(コミットスイートは10分以内で実行できるべき)を好む。 が、その本質は、頻繁に実行するのが苦にならない程度に、テストを高速に実行できるべきだということだ。 そして、「頻繁に」というのは、最小限の手間で手早くバグを見つけられる程度にということだ。

備考

[*1] わざわざ「最近の」としたのは、XP前とXP後で何かが確実に変わったからだ。前世紀から続く議論の中で、XPな人たちはこの点を強く批判されてきた。「自分が書いたコードを自分でテストするなんて、とんでもない!」というのが、当時の常識だった。別の開発者が書いたコードに対するユニットテストを書くだけのお仕事をする、専任のユニットテスターを雇っている企業もあった。「人は、自分の書いたコードには盲目になってしまう」「優れたプログラマーが優れたテスターだとは限らない」というのがその理由だ。その結果、開発者とテスターとの間に敵対関係ができてしまった。XPの考えはそうではない。プログラマーは(少なくともユニットテストレベルでは)すぐれたテスターになれるし、開発者とテスターを分けてしまうと、テストから得られるフィードバックを取り込むのが決定的に遅くなってしまう。ここで重要な役割を果たすのがXunitだ。Xunitは、プログラマーがテストを書く際の抵抗を減らすことを目指して作られた。

[*2] 有用なテストがあるのだが、それを実行するとコミットスイートに時間がかかりすぎるという場合は、デプロイメントパイプラインを作って、遅いテストはパイプラインの後半のステージに配置すべきだ。