TDD/BDDにおける「振る舞い」の意味するところとは何なのか:いまさら聞けないTDD/BDD超入門(3)(3/3 ページ)
BDD初心者が持ちがちな3大疑問点を提示して、さまざまな角度からそれを明らかにしつつ、振る舞いを表現する2つのテクニックを紹介する。
【疑問3】「振る舞いをテストしている」とはどういうことか
TDD/BDDにおいて「振る舞いを記述している」とは、どういったことでしょうか。振る舞いの定義、表現力という切り口で説明し、実際に幾つかのテクニックを紹介します。
振る舞いの定義から見る「振る舞いの記述」
前述の「『振る舞い』とは何か」の節の立場では、振る舞いの記述は「対象における何らかのイベントにひも付く外部から見える変化の記述」といえますし、より詳細にいえば「オブジェクトの組み合わせの記述」ともいえるでしょう。
「振る舞いを記述している」とは、(例えば)「オブジェクトの内部については関知せず、オブジェクト間を記述する」ことになります。また『GOOS』では、オブジェクト間で発生するコミュニケーションパターンのことを「ドメインモデル」と呼んでいます。
「オブジェクト間を記述する」ことが、各メソッドに対するテストコードである場合は、「振る舞いを記述している」とはあまりいえません。それは先の「設計が悪い場合/説明的な記述になっていない場合に起こる3つの問題」で挙げた3つの問題のうち、どれかを再現させてしまうでしょう。
つまり、Given/When/Thenを使ったとしても、「振る舞いに対するテスト」ではなく「メソッドに対するテスト」になってしまうことがあります。Given/When/Thenは「単なるテンプレート」といえば、それまでなのです。
例えば、「オブジェクト指向における振る舞いを記述する」ことは、「テスト対象のメソッドの組み合わせ方のテストコード」という意味が強いです。つまり、振る舞いのテストは「対象オブジェクトにおけるメソッドの組み合わせによって外部から見える変化に対するテスト」といえます。
これはある種のプログラミング言語の問題ですが、インターフェースを定義することはできても各APIの関連性は定義できません。SMTPなどのプロトコルでは、「この順序で呼んでください」と定義されていますが、それはプログラミング言語のインターフェース定義ではできません。実際は内部実装によって、そのプロトコルが定義されています。
全てのメソッド間が「関係がある」と主張しているわけでもなく、「あるインターフェースにおけるメソッド間の関係定義をできるだけ要らないようにする」方針もあります。後者については「振る舞いを1メソッドで定義できている」ので、それをさらに組み合わせることは、「少し大きな粒度の振る舞いをテストしている」といえるでしょう。
少し大きな粒度の振る舞いをテストすることが必要かどうか、は状況によります。「テスト対象の責務」と「想定している会話方法の確認」が、テスト対象を知るためには大切であり、「振る舞いをテストすることで、それを達成できるだろう」という考え方です。
つまり、必要なテストかどうかは、「少し大きな粒度の振る舞いをテストしておく方が、テストコードの保守への影響を考慮しても、『テスト対象の責務』がより明確になり、全体としての保守性が向上する」と判断できる状況かどうかによるでしょう。
表現力のあるテスト
より説明的に振る舞いを実例として記述することで「今、何が求められているのか」の大きな指針になります。いい換えると、「なぜ、この対象が存在するのか」に対する回答を記述しているテストコードとなります。そして誰がそれを要求しているかは、その対象のユーザー(ステークホルダー)によって変化することは前回の記事で触れました。
「対象の振る舞いを記述する」ことについてソフトウェアから離れて例を出します。例えば、あなたが友人や親や配偶者や子供が普段どのようであるかを説明するときに何と答えるでしょうか? 「歩くことができる」「手を握り返せる」「信号の区別が付く」「笑う」という個々の説明よりは、「晴れている日に一緒に歩いていると、赤信号で止まったときに笑いながら手を強く握り返してくる」の方が、より「どんな人であるか」を表現しています。後者が説明的で実例としての振る舞いを表現しているのに対して、前者はどのような人であるかは想像が難しいのです。
このように、「どんな対象であるか」「対象が何をするのか」についてより言及できているものが「表現力がある」といえます。
TDD/BDDにおいてテストコードとして振る舞いを記述することは、バグを見つけるよりもはるかにテスト対象を明確に定義する方法としての側面が大きいのです。
振る舞いを記述しているテストコード
振る舞いの定義、表現力という切り口から見て、どのようなコードがよろしくなく、どのようなコードがよろしいといえるか、の違いが雰囲気で分かる程度にコードで比較します。
public void is_walk_with_boyFriend(){ def boyFriend = new BoyFriend() def mary = new Mary() mary.walk(with:boyFriend) assert mary is WALKING } public void is_walk_with_boyFriend(){ def boyFriend = new BoyFriend() def mary = new Mary() mary.stop() assert mary is STOPPING }
public void is_fun_when_walking_with_boyFriend(){ def boyFriend = new BoyFriend() def mary = new Mary() mary.walk(with:boyFriend, weather:SUNNY) .stop(because:RED_SIGNAL) assert mary.face is SMILE assert mary.squeezeHand is boyFriend.hand }
上記の例は非常に短く簡素なものなので、「必要十分なテストをどう書くか?」というよりは「振る舞いを書いているかどうか」を理解しやすくするためだけのコードであることに注意してください。
振る舞いを表現する2つのテクニック
最後に、振る舞いを表現しやすくする具体的で簡素なテクニックを2つほど紹介します(参考『BDD in Action』)。
【1】クラス名はコンテキストを表現する
テストコードのクラス名に「{テスト対象クラス}Test」や「{テスト対象クラス}Spec」が使われることがあります。ここから分かるのは「テスト対象クラスが何であるか?」でしかありません。この「{テスト対象クラス}Test」は「○○機能テスト」と書いているに等しいといえます。そして、これは「テストコード内に現れるSUT(System Under Test)オブジェクトと重複している」ともいえます。
class TDDStepTest { TDDStep sut }
TDD/BDDとしてテストコードを書き始めるとき、つまりテストクラスを作るときは、「まだ機能は存在しない」ので「○○機能テスト」というのは作れないはずなのです。頭の中で「○○機能を作る予定だから、○○機能テストとしよう」としているわけで、それはあまり「BDDらしい」とはいい難いのです。
TDDはテストコードという手段を用いて、分析、設計を行うことが主目的です。そこから派生するテストはあったとしても、最初の一歩つまり、特定テストクラスを作り始めるときは、要求や振る舞いを書くことが重要です。その場面で「○○機能テスト」と書いていては、○○機能を実装することにおいて、あまりにも設計理由や設計意図が不明瞭です。
「{テスト対象クラス}Test」よりも良い表現をする方針としては、例えば「コンテキスト」を表現することです。1つのテクニックは「テストクラス名をWhenで始める」ことで、これから実装しようとしていることがより明確になります。もちろん、Whenから始めなくてもコンテキストを表現することはできます。
class WhenCyclingTDDSteps { TDDStep sut }
TDDStepTestよりはWhenCyclingTDDStepsの方が「これから何を実装しようとしているか」が明確になっています。
【2】テストメソッド名は振る舞いを説明的に表現する
テストコードのテストメソッド名に「test{テスト対象メソッド}」が使われることがありますが、ここから分かるのは「テスト対象メソッドが何であるか?」でしかありません。
@Test public void testAddStory(){ // 処理 } @Test public void testSortStories(){ // 処理 }
ここから分かるのは「AddStoryメソッドをテストしている」「SortStoriesメソッドをテストしている」ということでしかありません。TDD/BDDにおいて「『(何かは分からないけれど)テストをした』という宣言は重要ではない」のです。このようなテストメソッド名では、一つ一つのテストメソッドを細かく見ていく必要があります。
プロダクトコードと同じように、テストメソッドも名前が重要です。これは「テスト対象が何であるか?」を宣言していますが、それはプロダクトコードにあるべきです。またテストコードには、「テスト対象が何をするのか?」が書かれているべきです。
また、このテストメソッドからは「オブジェクトのプロトコルについて、あまり説明がない」という見方をすることもできます。
「test{テスト対象メソッド}」よりも良い表現をする方針としては、例えば振る舞いを表現することです。1つのテクニックは「テストメソッド名を『Should』で始める」ことや「テストメソッド名を『Test That』で始める」ことで、これから実装しようとしていることがより明確になります。もちろん、「Should」から始めなくても振る舞いを表現することはできます。
ある種(たいていは、多く)のテスティングフレームワークではテストメソッドから別のテストメソッドを呼ぶことはほとんどないわけですから、非常に長いメソッドがあることによる悪影響はプロダクトコードに比べて少ないです。
@Test public void shoudAddStoryByProductOwner(){ // 処理 } @Test public void test_that_developer_team_sort_stories_when_sprint_planning(){ // 処理 }
BDDにおける振る舞い、そして振る舞いの「レイヤー」へ
TDD/BDDを思想とツールを整理し、振る舞いという言葉の使われ方について幾つか引用しました。それらを「振る舞いをテストしていないという状態」に結び付けました。冒頭で述べた3つ疑問については、まとめると次のようにいえます。
- 「振る舞い」とは何か
一般的な意味に近い「対象における何らかのイベントにひも付く外部から見える変化のこと」である。コードレベルでの振る舞いの意味は、どの立場で振る舞いを捉えているかをいくらか考える必要がある程度には、よいTDD/BDDには必要である。また、振る舞いの対比としては構造、内部構造、仕組みという表現が多い - 「振る舞いをテストしていない」とは何か
構造の説明、内部構造の説明、仕組みの説明といい換えることができ、テストコードによるソフトウェアの理解を促進できなかったり、プロダクトの変更による影響範囲が膨大になったりしてしまう - 「振る舞いをテストしている」とは何か
さまざまな表現が存在するが次のようにいえる- 「対象オブジェクトにおけるメソッドの組み合わせによって外部から見える変化を記述している」
- 「対象がどのように何をするかを記述している」
- 「コミュニケーションパターンを記述している」
いろいろな場所でいわれている「メソッドをテストしていて、振る舞いをテストしていない」とは、辞書で「単語の説明はしているけれど、熟語やよくある言い回しや間違った用法が書かれていない」といい換えられます。言い回しを追加したり変更するときに、単語の意味しか書かれていないと、整合性を保つのが大変です。
また、「ある人物について個々の動作を独立に説明しているけれど、その人物を特徴付ける一連の行動を説明していない」とも言い換えることができます。これを回避するのに、BDDや「Specification By Example」という考え方が役立ちます。
次回はBDDにおけるレイヤーの捉え方、テストレベルとの関係について触れる予定です。
著者プロフィール
ソフトウェアテストアーキテクト。ソフトウェアテストを専門にし、テスト戦略やテスト設計に関するモデリングの手法やアジャイルなソフトウェアテストに関する研究と実践を行っています。GroovyやF#などのプログラミング言語を好んでいて、断然IntelliJ IDEA派です。Nagoya.Testing、SCMBootCamp、基礎勉強会などの勉強会を主催しています。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- テスト自動化の歴史と今後、良い/悪い事例〜システムテスト自動化カンファレンス2013レポート
テスト自動化を開発の“武器”にするための3つのポイントや、“自動化”の良い事例、悪い事例など、テストの現場を「進化させる」知見が多数紹介されたカンファンレンスの模様をレポートする。 - PHP開発者のためのテストのすゝめ(1):ユニットテストはなぜ必要なの?
開発の全工程の中で、あまり人気がないのがテスト工程だ。ソフトウェアの品質を証明するためのテストは、なぜ低く見られてしまうのか - Railsで目指せ、情熱エンジニア(5):なぜリファクタリングは必要なのか?
今回からいよいよコードの話を始めます。もはやRubyの文化の一部だという主張もあるリファクタリング、テストについて、その意義や概要を紹介します - Railsで目指せ、情熱エンジニア(7):Railsの人気テストフレームワーク6選!
前回は具体的なWebアプリを例にして簡単なコードレビューをしました。今回からは、テストを使ったリファクタリングについて解説していきます - Railsで目指せ、情熱エンジニア(8):実例で学ぶRailsアプリのテスト方法
前回はRailsで使われるテストフレームワークをご紹介しました。今回は具体的なWebアプリを例に、簡単なテストを使ったリファクタリングについて解説します - 第1回Androidテスト祭りレポート:Android開発で泣かないための「テスト」の重要性
その自由度の高さや多様性ゆえに、さまざまな課題を抱える、Androidアプリ開発の“テスト”に焦点を当てたイベントの模様を紹介します - UX Clip(28):JavaScriptのテストを開発工数に入れてもらうには?
Webアプリの大規模化とフロントエンド領域の拡大により、JavaScriptのテストの必要性が高まっている。数ある高機能なテストフレームワークをどう使いこなせば、高速かつ高品質な開発が可能になるのだろうか。 - テスト駆動開発はプログラマのストレスを軽減するか?
テスト・ファーストとリファクタリングの技法を組み合わせてプログラム開発を行う「テスト駆動開発」。簡単な例題を試しながら、この新しい開発手法を評価、検証する - 特集:Visual Studio 2008単体テスト機能徹底活用(後編):VS 2008単体テスト機能でテスト駆動開発/NUnitからの移行
VS 2008 Pro版に搭載されている単体テスト機能を使ってテスト・ファーストを実践。NUnitからのテスト移行についても考察。 - IT用語解説系マンガ:食べ超(3):テスト駆動開発で、みずから築いた壁にドーン
テストを書け! 話はそれからだ!