SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

C#で始めるテスト駆動開発入門

TDDで仕様変更とデバッグをする方法

C#で始めるテスト駆動開発入門(4)

  • X ポスト
  • このエントリーをはてなブックマークに追加

仕様変更によって修正が必要になるテストケースの数

 ここで1つ考察しておきましょう。仕様変更のとき、思いがけない数のテストコードを修正するはめになって、泣きそうになることがあります。仕様変更のときに修正するテストコードが膨大にならないようにするには、どうしたらよいのでしょう。

テストコードもリファクタリングを欠かさない

 テストを実行するための準備や後始末に複雑な手順が必要な場合、その部分、例えばオブジェクトの生成手順に仕様変更が入ると、テストコードにも面倒な修正をしなければなりません。テストメソッドをコピー&ペーストで作っていると、その面倒な修正をテストメソッドの数だけ行うはめになります。

 準備や後始末の手順が複雑な場合(後で修正することになったら面倒だなと思えるとき)は、テストコードをリファクタリングしてその手順をできるだけ一か所にまとめておきましょう。NUnitでは、今回のサンプルコードのようにTestCase属性を使って書くと楽です。あるいは、準備するコードを別のメソッドに切り出したり、テストクラス全体で同じ準備が必要ならSetupにまとめるなど、工夫してみてください。

テストケースは最小限にする

 テストファーストに必要なテストケースよりも、たくさんのテストケースを作っていませんか? 製品コードとは無関係に作る品質保証のための単体テストと、テストファーストは違います。

 テストファーストで作るテストケースの数は、すべてを明白な実装で行った場合、対象のメソッドのサイクロマチック数と同じになります。仮実装 ⇒ 三角測量を行うと、そのぶんだけ+1されます。さらに、安心を得るために、GREENとなるはずのテストを追加することもあります。それでも品質保証のための単体テストよりは、テストケース数は少ないはずです。

サイクロマチック数とテストケース数

 サイクロマチック数(循環的複雑度、Cyclomatic Complexity)とは、線形的に独立した経路の数です。例えば次の図のメソッドX()には、処理B1を通る経路と処理B2を通る経路があるので、サイクロマチック数は2です。

サイクロマチック数が2のメソッド
サイクロマチック数が2のメソッド

 このメソッドX()をテストファーストで作っていくことを考えてみます。まず、処理A ⇒ 処理B1を通る経路を実装するために、明白な実装でやるなら1つのテストケース、慎重に仮実装と三角測量をやるなら2つのテストケースを書くことになります。つぎに、処理B2を通る経路の実装のために、明白な実装でテストケースを1つ追加します。これでもうREDになるテストケースは書けなくなったはずですから、テストファーストは終了です。書いたテストケースは2個または3個(最初に三角測量を行った場合)になります。一方、品質保証のための単体テストでは、同値クラス2つと、その境界値前後の2つ、合わせて最低でも4個のテストケースが必要になります。

 特に不安があるわけでもないのに、品質保証のための単体テストと同様にテストケースを書いてしまってはいませんか? 必要のないテストケースを増やしてしまうと、仕様変更が入った時に修正なければならないテストケースも増えてしまいます。

TDD三原則

 不必要なテストケースを書くことなく、しかもC0カバレッジは100%を達成するためのルールがあります。Robert C Martin氏が提唱した「TDD三原則」(原題は”The Three Laws of TDD”)です。

  1. 失敗するユニットテストを成功させるためにしか、プロダクトコードを書いてはならない。
  2. 失敗させるためにしか、ユニットテストを書いてはならない。コンパイルエラーは失敗に数える。
  3. ユニットテストを1つだけ成功させる以上に、プロダクトコードを書いてはならない。
(翻訳:安井力氏)

 「TDD三原則」といながら、内容はテストファーストに関することだけなので注意してください。また、この原則から外れて、不安を解消するためにGREENになるはずのテストを書いてもよいのです。このルールに従ってテストファーストを進めると、最小限のテストケース数で済むということです。

シンプルなメソッドにする

 さきほどのメソッドX()のテストケース数が最小の2個だったとします。処理B1の部分に仕様変更が入った場合、修正すべきテストケースは、1つのはずですね。では、処理Aに仕様変更が入ったらどうなるでしょう。メソッドX()のテストケースすべて(ここでは2個)を修正する必要があります。サイクロマチック数が大きいメソッド(すなわちテストケースが多い)で、共通に実行される部分に仕様変更があると、たくさんのテストケースを修正しなければならなくなります。

 テストファーストから見たシンプルなメソッドとは、

  • メソッドごとのサイクロマチック数を小さくする
  • 共通に実行される部分を通るテストケースを少なくする

 1つ目は、メソッドを分割することで個々のメソッドをシンプルにするということです。そうすることで、個々のメソッドは外部設計が考えやすく(=テストが書きやすく)、しかも仕様変更時の修正箇所は少なくなります(なお、publicにしたくないメソッドは、InternalsVisibleTo属性を活用してinternalにします)。

 また、ロジックを書き直す(今回のサンプルコードでは、switch文をやめてif文で書き直す)ことで、サイクロマチック数を小さくできることもあります(この場合、不要になったテストケースは削除する)。

 2つ目は、共通に実行される部分に仕様変更があると大変なので、そこを通るテストケースは減らすようにしましょうということです。例えば、メソッドX()から、次の図のようにメソッドY()を分離してみましょう。

メソッドX()から処理Bを分離
メソッドX()から処理Bを分離

 トータルのサイクロマチック数は変わっていませんし、トータルのテストケース数は増えています。しかし、処理Aを通るテストケースは1つ(三角測量するなら2つ)になります。処理Aに仕様変更が入った時に修正しなければならないテストケースを、減らすことができました。

 ただし、新しいメソッドX()のテストケースは、処理B1か処理B2のどちらかに依存しています。例えば処理B1に依存していて、そこに仕様変更があると、こんどは処理B1とメソッドX()の両方のテストケースを修正しなければならなくなります。この2つ目の指針は、サイクロマチック数が大きいときに有効なのです。

 例えばメソッドY()のサイクロマチック数が10だったとすると、その経路の共通部分に仕様変更があるたびに10個(あるいはそれ以上)のテストケースを修正するか、経路のどこに仕様変更があろうと修正は1個または2個で済むようにするかという選択になりますから、その場合は分離しておくのが正解でしょう。

 別解として、新しいメソッドX()のテストケースで使う期待値を、メソッドY()の戻り値を使ってテストケース内で算出するという方法もあります。この方法には賛否あると思うので、ここでは詳しく述べません。

次のページ
デバッグ(その1)

修正履歴

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
C#で始めるテスト駆動開発入門連載記事一覧

もっと読む

この記事の著者

biac(ばいあっく)

HONDA R&Dで自動車の設計をやっていた機械屋さんが、技術の進化スピードに魅かれてプログラマーに。以来30年ほど、より良いコードをどうやったら作れるか、模索の人生。わんくま同盟の勉強会(名古屋)で、よく喋ってたりする。2014/10~2019/6 Microsoft MVP (Windows Devel...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/6590 2012/05/29 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング