A NEW LOOK AT TEST-DRIVEN DEVELOPMENT 私家翻訳版

A NEW LOOK AT TEST-DRIVEN DEVELOPMENT

Original
http://daveastels.com/files/sdbp2005/BDD%20Intro.pdf
Translator
Takeshi Kakeda <t.kakeda_at_gmail.com>
Reviewer
Shintaro Kakutani (http://kakutani.com/) , nishikawa (http://d.hatena.ne.jp/nskj77/)
Updated
2005/12/20

はじめに(日本語版のみ)

Behaviour Driven Developmentについての初の記事ですが、訳語について悩んでいます。現在は以下のようにしてあります。

原文 訳語
behaviour ビヘイビア(振舞)
specification 仕様
specify スペック化する
BDD ビヘイビア駆動開発

問題

テスト駆動開発(TDD)は今や広く受け入れられている。大企業は自社のプログラマに、多くのコストを掛けてTDDを教育している。カンファレンスでTDDは人気のトピックだ。アジャイル系はもちろん、それ以外でも。

TDDについての書籍も何冊か出版されている。筆者(Dave Astels)自身の著作はJolt awardを受賞した。では、何もかもがうまくいってると言えるのだろうか? TDDを実践している人々全てが、TDDについて深く理解し、TDDの恩恵を最大限に受けていると言えるのだろうか?

んなこたぁない!

TDDの本当の姿を正確に理解している人を私はごくわずかしか知らない。要するにTDD実践者の多くは、TDDの恩恵を最大限には受けていないのだ。何がまずいのだろうか?

テスティングが焦点

さて…なによりもまず、みんなTDDはテスティングだと考えている。TDDはそれだけじゃない。確かに類似点はいくつもある。最終的にはよくできた低レベルのリグレッションテストスィートを手にすることにはなる。しかし、それらはたまたまか、運良く手にしたオマケだ。なぜ事態はかくも不幸な状況になっているのだろう? どうして多くの人びとはTDDの恩恵に浴することができないのだろう?

ちょっとした歴史の授業から始めたい。数年前、筆者は「Coad Letter」1 にTDDの記事を書いた。その中でTDDの背景(Ron Jeffriesから聞いた)についてこう記した。

かつては、XP実践者達も、テストを書くことについて語っていた。それはおそらく彼らの語彙がテスト中心(testing-centric)だったからで、それがためにTDDをテスティングだと考えていたからだ! テストケーステストスィートテスト 、”test”で始めなければならないメソッド名…これらを語るときにテストのこと以外に何を考えるだろうか? もちろん、すべてのxUnitフレームワークがこうした要件を備えていたわけではないが。

新しいJUnit(Version4)では、命名規約やTestCaseのサブクラス化から解放される。しかし他の用語やアノテーションでは依然としてテスト中心の語彙のままだ。TDDへの進化が起きた時、我々が手に入れた考え方は、これまでとは全く異なるものだった。それは、ちょっとした改良なんてものじゃなかった。

オリジナルのXP実践者達は、可能なもの全てにテストを書いていた。最初はテストファーストからはじまった。新しい機能の小さな一部分を表す小さなテストを書いて、それを実装し、次にまた小さなテストを書いて…という繰り返しの結果、やっとTDDへと辿りついた。

しかし、TDDは多くの人が考えているような最終地点ではない…ただの通過点だ。「ユニット」もそうだ。「ユニット」という考え方自身が大きな問題だ。まず第一に「ユニット」は不明確な用語である。次に、「ユニット」という言葉がコードの構造的分割を暗に示している(例えば、メソッドやクラスをテストしなければならないと考える)。ユニットをこのように捉えるべきではない。ユニットはビヘイビアの様々な側面として捉えるべきだ。

ユニットテストだとして考えてしまうと、テストをコードの構造単位で分割してしまう。例えば、テストクラス 2 と製品クラスが1:1のになるのを望んでいるわけではない。我々はビヘイビア単位で分割したいのであり、典型的なユニットテストによる分割単位よりも細かく分割したいのだ。

私はTDDに触れる以前に次のように主張していた。つまり、非常に小さい単位、ビヘイビアの断片に、..つまり1つのメソッドのちょっとした1つの側面に注目して作業するべきだと。例えば「リスト要素が空の時にadd()メソッドを実行した後は、リストの中に1つの要素が存在するべきである」というようなことである。メソッドは特定のコンテキストで呼ばれ、特定の値を引数に取り、特定の結果を返す。私は自分のBlogに「テスト毎に1アサーション」 3 と書いてしまうほどになったこともあった。あなたは手にしているんだ。テストという視点から考えるようにさせる、このきれいに包装されたすばらしいアイディアを。

結果

なぜこれが問題なのか? これから1分間、開発者が普段テストについてどう考えているか思い浮かべてみよう。プログラマはこう大抵はこう考える。「全部にテストなんて書けないよ」「これは本当に単純なコードだからテストなんて要らないな」「テストなんて時間の無駄だ」「こんなテスト(ループ/データ取得/機能、など)は何百万回もやったよ」

プロジェクトマネージャは、大抵こう考える。「テストはコーディングが終わってからやっている」「そのためにテスターを雇っているんじゃないか」「今そんな時間は無い」。みんなテストについて考えると、いくらでも否定的な反応やテストをしない理由が出てくる。特に納期までの時間が少なく、プレッシャーが掛っている時などはその典型だ。

テストじゃないなら何なのか?

それは中途半端な状態で走り出そうとする前に、やろうとする事を理解するということだ。つまり、簡潔で、明瞭で、実行可能な形の、小さなビヘイビアの側面を決定する仕様を書くということだ。

とても単純なことだ。これはテストを書けと言っているのだろうか? 違う。仕様を書くということは、コードがどのようにあるべきかを書くということだ。それはコードを書く前にビヘイビアを明示することも意味している。コードを書く前といっても、早すぎてはいけない。実際には、コードを書く直前がベストだ。なぜならその時こそが、ゴールに到達するのに十分な情報を手にしている時だからだ。

ちゃんとしたTDDのように、作業は少しずつインクリメンタルに進んでいく… その時のビヘイビアの1つの小さな側面を明示して、それから実装する。ビヘイビアをスペック化しているのであって、テストを書いているのではないと気づいたとき、世界の見方が変わる。突如として、テストクラスをプロダクトクラス毎につくらないといけないなんてバカバカしいと思うようになる。メソッド毎にテストメソッドを(1:1の関係で)書くなんて笑っちゃうような話だと思うようになる。

サピア-ウォーフの仮説

最初に背景みたいなものとして、Wikipedia 4 の定義を記しておく。:

言語が思考に影響を与えるという原則は、Wilhelm von Humboldtのエッセイ
『Uber das vergleichende Sparachstudium』に遡ることができる。
その概念は西洋の考えに広く取り込まれている。

私の意見の元となっているその仮説は、後に言語学者及び文化人類学者である、Edward Sapirと、彼の同僚であり生徒でもあるBenjamin Whorfによって名付けられた。それは次のような主張だ。

人が話す言語の文法カテゴリと、人がどのように世界を理解し行動を起すかとの間には体系だった関係がある

つまり私が言いたいことは、使用する言語が考え方を決める… ならば、考え方を変えたいのであれば、まずは使用する言語を変えたほうがよい、ということだ。

ではどうしろと?

まずテストの用語で考えるのをやめよう。Bob Martinはテストについて数年前にこう語った。「仕様である。検証ではない」。彼が言わんとしていることは、次のような内容である。検証(テスティング)とはコードが正しく機能しているかを確かめる(検証する)ことである。一方、仕様とはコードがどのように動くのが正しいのかを定義する(スペック化する)ことである。 5

xUnitのようなテスティングフレームワークを使うと、テストの用語から離れるのは難しい。テスト中心の言語で構成されているのだから当然だ。ビヘイビアを仕様化するための新しいフレームワークで始める必要がある。ThoughtWorks社のDan Northは、このためにjBehaveプロジェクトを開始した。 6 筆者と数名はRuby上のビヘイビア仕様フレームワークであるrSpec 7 を立ち上げた。rSpecについては、以降で詳細に見ていくことにする。

ビヘイビア仕様フレームワーク

では、ビヘイビアの仕様とはどのようなものだろう? パッと見はこれまでのxUnitと変わらないようにみえるだろうし、実際のところ、語彙の違いを除けば今までと同じように使える。TestCaseをサブクラス化する代わりに、Contextをサブクラス化する。メソッドの先頭を「test」で始める代わりに「should」とする。命名規約に煩わされすに適切な名前を選ぶことができる、こちらのほうが好ましいだろう。

アサーション(例えばassertEquals(期待値, 実績値))による検証を行う代わりに、shouldBeEqual(実績値, 期待値)のような事後条件をスペック化する。SmalltalkやRubyでは、クラスライブラリの中に組み込んでしまえば 8 、もっと自然に書けるだろう(Smalltalkでは、ごく普通のやり方だ)。

次の表の例のように記述できる。

JUNIT SBSPEC RSPEC
assertEquals(expected,actual) actual shouldEqual: expected actual.should_equal expected
assertNull(result) result shouldBeNil result.should_be_nil
JUNIT SBSPEC RSPEC
try { [2 / 0] shouldThrow: {2 / 0}.should_throw  
2 / 0; DivideByZero DivideByZero  
fail();    
} catch (DivideByZ    
ero ex) {    
}    

今は何があるの?

既に述べたように、Dan Northが始めたjBehaveプロジェクトで作っているjUnitの代替となるビヘイビア仕様のフレームワークがある。今すぐにダウンロードして試してみることができる。もしRubyを使っているならば、rSpecフレームワークが使える。rSpecの記述では、次のようなメソッド群がどのオブジェクトからも呼ぶことができる…ここがポイントなのだ… どのオブジェクトからも 、だ :

  • should_equal(expected)

  • should_not_equal(expected)

  • should_be_same_as(expected)

  • should_not_be_same_as(expected)

  • should_match(expected)

  • should_not_match(expected)

  • should_be_nil()

  • should_not_be_nil()

  • should_be_empty()

  • should_not_be_empty()

  • should_include(sub)

  • should_not_include(sub)

  • should_be_true()

  • should_be_false()

全てのエクスペクテーションメソッドはオプションでメッセージをパラメータとして受けつける。setup及びteadownメソッドはxUnitと同じく上書き可能で、全く同じ挙動をする。 では、ビヘイビア仕様はどんな風になるだろうか? ここに筆者のTDD本に含まれている例題をrSpec向けに書き直してみよう。

require 'spec'
require 'movie'
require 'movie_list'
class EmptyMovieList < Spec::Context
  def setup
    @list = MovieList.new
  end
  def should_have_size_of_0
    @list.size.should_equal 0
  end
  def should_not_include_star_wars
    @list.should_not_include "Star Wars"
  end 
end

class OneMovieList < Spec::Context
  def setup
    @list = MovieList.new
    star_wars = Movie.new "Star Wars"
    @list.add star_wars
  end 
  def should_have_size_of_1
    @list.size.should_equal 1
  end
  def should_include_star_wars
    @list.should_include "Star Wars"
  end
end

ガイドライン

コンテキストクラスは何に着目しているかに応じて命名する。例えば、EmptyMovieListやOneMoviewListのようになる。コンテキストには直接関連するスペックメソッドだけを含める。スペックメソッドは仕様のどこに焦点を当てているかを表わすように命名する。例えば、should_have_size_of_0 のようになる。コンテキストクラスとエクスペクテーションメソッドは読みやすくすべきであり、何をやろうとしているのかが正確にわかるように命名すべきだ。例えば「EmptyMovieList.should_have_size_of_0」といったように。

ちょっと考えてみれば、ビヘイビアから仕様書を作成するのがどんなに簡単かがわかると思う。スペックメソッドは単純で、短かく、どうなれば良いかが明確になっているべきだ。あるエクスペクテーションは、他に言いようもないほどに、実装すべきものを明確に指し示しているべきだ。シンプルなのは良いことだ。大きく肥大化したクラスと、長く複雑なメソッドが少しあるよりも、小さく単純で、焦点の絞られた理解しやすいクラスとメソッドが数多くあるほうが遥かに素晴しい。

まとめ

私がTDDに対して抱いている問題とは、TDDのマインドセットが我々を本来とは異なる方向性、つまり間違った方向性へと導いてしまうということだ。我々はビヘイビアを仕様化する用語を使って考え始める必要がある。テストによる検証の用語ではなく。ビヘイビア仕様の用語で考えることで、それぞれのビヘイビアについてより明確に考えるようになる。クラス毎やメソッド毎のテストという考え方に頼らなくなり、より優れた実行可能なドキュメントを手にすることができるようになる。

TDDはTDDであり、誰もその名前の意味を変えようとはしない(それを期待すべきではないし、そうしてもらいたいわけでもない)。新しいやり方には新しい名前が必要だ。これにはDan Northが考えてくれた名前がある……それがBDD、ビヘイビア駆動開発だ。

謝辞

Steven Baker、Gabriel Bauman、そしてAslak Hellesoyには本稿の初稿を取り上げてくれ、私の考えをrubyのフレームワークによって具現化してくれたことに感謝する。


  1. : http://bdn.borland.com/article/0,1410,29690,00.html “What is Test-Driven Development?”

  2. : [訳注] 原文はtext classesとなっている。多分 text ではなく test classes

  3. : http://blog.daveastels.com/?p=3

  4. : http://en.wikipedia.org/wiki/Sapir-Whorf_hypothesis

  5. : [訳注] 検証は仕様通りかを確認するが、仕様そのものではないということである。

  6. : http://jbehave.codehaus.org/

  7. : http://rspec.rubyforge.org/

  8. : [訳注] Smalltalkはクラスライブラリを拡張する形で開発をすすめる。Rubyの場合はObjectクラスに振舞いの定義を追加することができる。

[関連する記事]