ユニットテストを書かない方法

TEFでもTDDや単体テストの話題で持ちきりですが、

極力ユニットテストを書かずに品質を確保する方法 by ひがやすを blog

やり方を簡単に紹介すると、最初は、Programming First Developmentで、機能を実装して、ユーザに動かしてもらうってことをユーザの要件が固まるまで繰り返します。このときは、基本的にユニットテストは書きません。動かすことに集中します。

ユーザの要件が固まった(実装がほとんど終わった)ら、保守のためのドキュメントの一つとして、テストシナリオ(ユースケーステスト)を作って、テストを行います。そのテスト中に、バグが発見されたらその周辺のユニットテストを書いていきます。

後述にもありますが、「ユニットテストは工数がかかる」という意見に対する対案のようなもので、せっかく書いたテストコードをより効果的・効率的にするための工夫ですね。

事実、ここ数年、「ユニットテストは工数がかかる」と思っている人と「ユニットテストを書いたほうが最終的にはコストが下がる」と思っている人の会話は平行線のままです。

このような状況を改善するために考えたのが、「Programming First Development」のなかで、バグったところ(とその周辺)のみユニットテストを書くという方法です。このやり方なら、ユニットテストを書く工数は、かなり減ります。そして、偏在しているバグを効率よくつぶすことができるのです。

著者の「ユニットテストの工数を少なくしつつ、品質を保つ」という試み・着眼点はすごく興味惹かれますね。ユーザにまずは動かしてもらうという方法以外にも、考えたらいろいろ思いつきそう。難易度・重要度マトリックスを作って、ユニットテストの強弱をつけるとか。ただ、個人的な意見ですが、「ユーザにまずは動かしてもら」ったとしても、開発者側が思ってるほど協力的ではない場合もありそう。その場合は、バグったところの他のバグは見つけられるかもしれないが、そもそもユーザがあまり操作してくれなかった箇所・機能なんかは少し不安が残るかもしれません。


| | コメント (3) | トラックバック (0)

C言語の単体テスト、いろいろ

ある意味、餃子ブームだ。

==

メールサーバってCだから、C向けのテスト手法はいろいろと調べたりすることが多いし、自分でツールを作ったりします。せっかく調べたので、C言語をターゲットとした単体テスト/ユニットテストツールを枚挙してみる。

  • CUnit
  • http://sourceforge.net/projects/cunit/によるユニットテストフレームワーク。出力に「__FILE__」「__LINE__」を記載して、分析しやすく結果を表現する。XML出力モード(Automatedモード)で実行し、DTDとXSLスタイルシートによって、統計情報が見やすくなる。


  • CUnit for Mr.Ando
  • 安藤利和さんによる「言語技術者のC言語技術者によるC言語技術者のための C言語テスティングフレームワーク」。ソースはhttp://sourceforge.jp/projects/cunitforando/から入手。関数のスタブ生成でテストを実行していく。ドキュメントには使い方のほかに、単体テストの原理などの記事も。


  • CCUnit
  • Cのユニットテストツールはネーミングが特徴的なものが多い。CCUnitは「繰り返し可能なテストを書くため」のフレームワーク。CCUnit クックブックが、適用への近道か。ソースは、こちらから入手。


  • CuTest
  • ネーミングは、「C Unit Testing Framework」。このキュートなテストプログラムは、make-tests.shによって生成される。ソースはこちら


  • Cutter
  • マニュアルが充実している。テスト実行の進捗状況を「.」「F」を並べて見やすくしている。その他、テスト結果詳細として、テストケース数、テストパス数、テスト失敗数、テスト異常終了数、テスト保留数などがわかる。ソースはこちらから。


  • MinUnit
  • 3行だけのユニットテスト。「a minimal unit testing framework for C」は、個人的にはライトで小気味よく、重宝してます。


    --

    リファレンスやチュートリアルが充実しているのですが、より実践的な例があるといいなーなんて思ってたりして。こういったツールの活用と効果的なテスト設計によって、結合テスト・システムテスト、あるいは静的テストの負荷軽減を図っていこう。

    | | コメント (4) | トラックバック (0)

    どんな単体テストをしますか? - for文のホワイトボックステスト その2

    ずいぶん、亀自己レス。

    ==

    for文を含むプログラムでは具体的にどうやってホワイトボックス的アプローチをするのか。ループする回数を同値分解するのはどうだろうか。0回、1回、無限回(実際に無限回はありえないので一般的な大数か)とか。んで、今回のソースはmonthという入力条件があるので、

    For1


  • month=0のとき(for文を経由しない、monthパラメータの無効クラス)

  • month=1のとき(for文を1度経由する、monthパラメータの有効クラス)

  • month=13のとき(for文を複数回経由するが、monthパラメータの無効クラス)
  • の3条件は少なくともチェックしたい。まあ、こうもいかないループもありそうだけど。。。

    ==
    2007/06/17追記。
    よく考えたら、month=12のがいいな。そうすればループ内のswitch文でcase 1~case 12までを経由できるし。

    | | コメント (8) | トラックバック (0)

    どんな単体テストをしますか? - for文のホワイトボックステスト

    今日は少し早めに帰宅した。

    ==

    年月日を指定して、元旦までの日数を計算する関数の単体テスト設計です。

    が、ホワイトボックステストが随分長引いている。だって。for文ってどうやって設計するんだろう。意外にわからない。わからないけど、for文のカウンタを固定してひとまず原因結果グラフを作ってみることにした。

    Daycountceg3

    作ったところで、頭が回らなくなってきた。36番、いらないなあ。10番→40番、12番→39番も、そのままだ。教派も寝る。また明日考えようっと。


    | | コメント (0) | トラックバック (0)

    どんな単体テストをしますか?その5

    家から確認作業、夜は長いぞ。

    ==

    どんな単体テストをしますか?その4の原因結果グラフを修正。

    Daycountceg2

    この分岐はいたってシンプルなので、こう眺めてみると原因結果グラフを明記しなくても、デシジョンテーブルに落とし込むことはできそだな。少し余裕が出たときにサンプルの関数全体をホワイトボックステストで設計するのと、グレーボックステストで設計するのをやるかな。グレーボックステストは今までのまとめっぽいけど。

    ==

    例のMC/DCについて。まだ読み中。

    Photo
    引用:NASA/TM-2001210-876, A Practical Tutorial on Modified Condition/Decision Coverage

    MC/DCというカバレッジ基準は図にあるように比較的高レベルなカバレッジ基準といえる。他の資料には、高信頼性を求めるシステム、リアルタイム性が求められる組込み系ソフトウェアなどで使われるらしく、この引用元も米国の航空・宇宙関連システムにおける資料のようだ。ただし、「ここの条件が独立して結果に影響を及ぼす」ことを示すためのカバレッジという点では、原因結果グラフでの網羅を意識した設計が、とても似ていると感じた。(n個のAND条件では、n+1回の条件、など)

    もう少し読み込んでみようっと。

    | | コメント (0) | トラックバック (0)

    どんな単体テストをしますか?その4

    どんな単体テストをしますか?その3」の続き。

    関連記事はこちら。コメント欄にも目を通すと理解が進むかも。

  • どんな単体テストをしますか?

  • どんな単体テストをしますか?その2

  • どんな単体テストをしますか?その3
  • ホワイトボックステストの視点で、daycount関数(年月日を指定して、元旦からの日数をカウントする関数)を単体テスト設計してみるのだが、テスト設計は「なるべく少ないコストで」という意識ですすめるほうがいい。ということで、今回は原因結果グラフから導いたデシジョンテーブルで、テストケースを洗い出してみる。全部やるのは後にして、まずは最初の、

    Daycount7

    この部分。この分岐に関して、原因結果グラフを作成すると、こんな感じ。
    Daycountceg1

    これをデシジョンテーブルに落とし込むのだが、まずは3つの中間ノードについてデシジョンテーブルを作成する。3つともEXC制約を考慮して、こんな感じ。
    Daycountdt1

    そして、全体のデシジョンテーブルはこんな感じで。
    Daycountdt2

    この3つのデシジョンテーブルを結合すると、テストケース数は7つ。
    Daycountdt3

    作ってみて、ブラックボックステストの視点でテストケースを洗い出したときも原因結果グラフを使っているので、似ている。違う点は、コーディングの構成を知っているので、「0」や「-1」などの特異値については考慮されない点だ。

    さて、この調子で関数全体のテストケースを作る。が、それはまた今度。MC/DCについても今度調べて記事にするか。


    | | コメント (6) | トラックバック (0)

    どんな単体テストをしますか?その3

    アバウトミーをはじめました。

    ==

    さて、単体テストをブラックボックステスト、ホワイトボックステスト、などの視点で考えてみようとしているわけですが、前回の「ホワイトボックステストの視点」では「ステートメントカバレッジ」を基準にしたため、テストパターンが限られてしまった。

    では、今回は「マルチコンディションカバレッジ」を基準にしてみようかな。

    Daycount7

    うーむ。。。いきなり6つも条件が。めんどくさい。2の6乗=64通りってことになるけど。一番最初のブラックボックステストの観点でこの部分は10通りだったしな。これは、怠けてもいいのだろうか。

    | | コメント (3) | トラックバック (0)

    どんな単体テストをしますか?番外

    番外。

    どんな単体テストをしますか?にて、ひと月のパターンとして「1日」「15日」「晦日(28日、29日、30日、あるいは31日)」の3パターンを考慮したが、一般的には「15日」は不要なのだろうか?必要なのだろうか?もしかしたら、最頻出値をパターンとして選ぶのが妥当?

    イマイチ基本的な技法を理解していないかも。。。

    | | コメント (2) | トラックバック (0)

    どんな単体テストをしますか?その2

    新年度に入ってから、電車事故が多い気がする。

    ==

    どんな単体テストをしますか?では、ブラックボックステストの観点でテストケース・テスト条件を洗い出してみた。

    今度はホワイトボックステストの観点で考えてみよう。

    元の記事はこちら(自動で単体テスト、ステートメントカバレッジ、そしてテストケースレビュー)なので、ご参考までに。

    • ホワイトボックステストの観点
    • まずはコード。西暦、月、日を指定することで元旦からの日数を計算する関数。


      30:int
      31:daycount(
      32:    int year,
      33:    int month,
      34:    int day )
      35:{
      36:    int     i = 0;
      37:    int     count = 0;
      38:    int     mday = 0;
      39:
      40:    if( year < 1900 || year > 2100
      41:    || month <= 0 || month >= 13
      42:    || day <= 0 || day >= 32 ){
      43:        return(-1);
      44:    }
      45:
      46:    count = 0;
      47:    for(i = 1; i < month + 1; i++){
      48:        switch(i){
      49:        case 4: case 6: case 9: case 11:
      50:            mday = 30; break;
      51:        case 1: case 3: case 5: case 7: case 8: case 10: case 12:
      52:            mday = 31; break;
      53:        case 2:
      54:            if( (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0) ){
      55:                mday = 29;
      56:            }
      57:            else{
      58:                mday = 28;
      59:            }
      60:            break;
      61:        default:
      62:            return(-1);
      63:        }
      64:
      65:        if( i == month ){
      66:            if( day > mday ){
      67:                return(-1);
      68:            }
      69:            else{
      70:                count += day;
      71:            }
      72:        }
      73:        else{
      74:            count += mday;
      75:        }
      76:    }
      77:
      78:    return(count);
      79: }


      ホワイトボックステストではカバレッジを基準にすることが多い。ステートメントカバレッジ、ブランチカバレッジ、コンディションカバレッジなどなど。組織やプロジェクトチーム内でどのカバレッジレベルを採用するかが決まっている場合はそれに従うのもいい。でも、最低限ステートメントカバレッジは抑えたい。テスト(実施)カバレッジについてはここを参照。(俗称として、「C0」「C1」「C2」などと呼ばれることがある。)とりあえず、ステートメントカバレッジを基準にしてみるか。

      まずは、最初の「引数チェック」の箇所。これはif文を分岐として2種類が考えられる。
      Daycount4

      次に月に関するfor文内の処理。ここで少し複雑だが、switch文やif文で複数のブランチが考えられるが、テスト設計者の主観によっては、全ての月の条件(1月~12月)と、うるう年条件(平年、うるう年、100年後との平年、400年後とのうるう年)も網羅すべきだと考える場合もある。
      Daycount5

      これらを組み合わせて、全ての命令文・手続きを実行するようなテストケース・テスト条件表を作成すると、こんな感じになるだろうか。
      Daycount6

      結構、テストケースが少ない。これだけじゃテストしたという自信はないな。ホワイトボックステストってもしかしたら、コード構成によっては威力を発揮できないこともあるのかしら?


    | | コメント (4) | トラックバック (0)

    どんな単体テストをしますか?

    自動で単体テスト、ステートメントカバレッジ、そしてテストケースレビューで、適当な関数を作って、適当な単体テストの条件を作ってみた。この中で「指定日が元旦から何日目かを返答する関数(daycount)」についてもう少しきちんとテストしてみようかと。

    ==

    • ブラックボックステストの観点
    • ブラックボックステストなので、基本的には「指定日が元旦からの日数」が正しく計算できるか、という仕様からテストケース・テスト条件を洗い出す。でもまずは関数インターフェースを眺めてみる。

      int daycount( int year, int month, int day );

      引数は、
      1. year(西暦)
      2. month(月)
      3. day(日)
      の3つ。西暦はここでは「1900年~2100年」という範囲に限定する、という(かくれ)仕様。は「1月~12月」、は「1日~31日」である。これは自明な仕様とでもいうのだろうか。ただし、不明確な仕様として「その月に存在しない日を指定した場合の動作」を確認しなくてはならない。ここでの仕様は「エラー」である。(9月の場合、31日を指定するとエラーとなる、など)

      最近の僕の場合、ここで大分類として


      1. 引数エラーケース

      2. 内部エラーケース

      3. 正常ケース


      というように考えている。

      引数エラーケースは、3引数の引数を原因結果グラフ・デシジョンテーブルで考えることが多い。
      Daycount1
      西暦のエラー値は、「-1」「0」「1899」「2101」あたりが妥当かな。int型の最小値、最大値は、別にいらないだろう。月のエラー値は、「-1」「0」「13」あたりかな。日のエラー値は「-1」「0」「32」としましょうか。

      そして、3つともエラー値ではない場合は、さらに「内部エラーケース」「正常ケース」で考えてみる。

      内部エラーケースは、月と日の不整合だろう。大の月(1月、3月、5月、7月、8月、10月、12月)は31日まであるので、引数エラーケース以外での不整合はなさそう。小の月(4月、6月、9月、11月)は30日なので、日が31日の場合に不整合が発生する。2月はややこしいが、西暦がうるう年ではない場合は、29日、30日、31日が不整合となるパターン。うるう年の場合は、30日、31日が不整合となるパターン。

      正常ケースは、どうする?(1月、6月、12月)と(1日、15日、晦日)くらいでいいのかな。

      まとめてみると、、、

      Daycount2
      Daycount3

      あってるかなあ。

    ホワイトボックステストの観点、グレーボックステストの観点は、また今度書きたい。忘れなかったら。

    | | コメント (4) | トラックバック (0)