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

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」は、個人的にはライトで小気味よく、重宝してます。


    --

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

    | | コメント (2) | トラックバック (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)

    自動で単体テスト、ステートメントカバレッジ、そしてテストケースレビュー

    C言語の単体テストの自動化はしてみたものの、テストケースレビューがしづらい。でも、テストプログラムにテストケース(テスト条件)を載せておけば、何かテストレビュー資料になる気がした。

    文字化けはめんどくさいからそのまま。

    一瞬テストケースレビュー資料になりうると思ったけど、まだまだ人間の読む資料じゃないかも。。。


  • サンプルソース(文字列の小文字化と、元旦からの日数カウント)
  • Statement_coverage


  • サンプルソースのテストプログラム
  • Unit_test_case


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

    [セミナー]第4回 「テスト環境」を見直す! 開発生産性・品質向上実践セミナー

    単体テストに焦点を当てたセミナー。また目黒雅叙園だわ。

    ==

    第4回 「テスト環境」を見直す! 開発生産性・品質向上実践セミナー

     ソフトウェア開発技術の目覚しい進歩により、構文エラーは事実上現存しなくなり、典型的なメモリーリークも減少、デバッガのおかげで隠れた問題も追跡が容易になりました。しかしながら、ソフトウェアが複雑さを増すにつれ、より多くのテストが必要になり、テスト開発にかかる時間と必要とされるリソースが増え、さらにテストを手作業で記述するプロセスには多くのリスクを伴うので作業効率が下がる恐れもあります。
      第4回の本セミナーは、単体テストの「必要性そしてビジネスにおけるインパクト」のご説明、単体テストを効率化するためのソリューションやサービスをご紹介致します。

    開催日 2007年3月15日(木)
    時間 13:30~  (受付開始13:00~ )
    会場 目黒雅叙園  オリオン
    定員 100名
    参加費 無料
    主催 サイオステクノロジー株式会社(旧 株式会社テンアートニ)
    協賛 アジターソフトウェアジャパン株式会社
    サン・マイクロシステムズ株式会社
    企画・運営 アイティメディア株式会社
    @IT 情報マネジメント編集部

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

    単体テスト技法を図解してみる

    インフルエンザが流行っています。

    ==

    単体テストを効果的にする手法をグラフィックで表現してみる。間違った解釈も含んでいそうだが。。。

    1. テスト空間U関数func1を写像としてイメージした図。
    2. 関数func1の結果は結果空間Vの部分集合、といったイメージ。


    3. 関数の引数チェックを行い、テスト空間Uを小さくする手法をイメージした図。

    4. 引数チェックをすることで、テストケースを少なく効率的なテストを行う、という感じ。点線に関する実質的な検証をすることがなくなる。JaSST'07で松尾谷さんが使った造語を使えば、「無則」と「有則」の識別、になると思ってます。
      2007/02/17修正。無則の識別とか関係なさそう。


    5. ちょいと苦しいが、デシジョンテーブルのつもり。

    6. func1の逆関数をとり、そのカーネル原像を利用してテストケースを設計する図。


    7. 同値分割の図。


    8. テスト空間をある規則で分割し、その代表元についてだけ検証する。関数func1が連続性を持ち、代表元の検証結果がテスト空間全体での検証と同程度の結果を得る、という意味。


    9. これも苦しいが、境界値分析のつもりの図。


    10. 境界(boundary)あるいはその近傍の元について検証し、効率よく欠陥を見つける手法。


    ==

    グラフィックにしてみて、ふと思ったのだが、同値分割って概念と、単体を小さくしてテストすることって、似てるんじゃないかって思った。関数をどんどん小さくして、テストしやすくしていく手法があると思うが、これの究極形が代表元だけ検証して、それに対する単体テストの検証結果と同一視するってことなんじゃないかと。

    なーんて、午前0時を回ってからぼんやり考えてみました。

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

    C言語における単体テスト テスタビリティを重視した関数設計

    今日は社外WGに参加。春の嵐だ!

    ==

    僕はC言語ばっかの開発をやってるので、ここでもC言語をベースにしていろんなことを書きます。あしからず。

    リリース後の欠陥発覚は手戻り工数が最もかかるものだが、その多くは単体テストで発見可能なものが多い。某静的解析ツールのベンダー資料によるとその割合は40%近くになるらしい。定量的な資料は持っていないが、僕も単体テストの質がシステム全体やプロジェクト全体の質を大きく左右すると考えている。

    そこでだ。僕の考える、上質な単体テスト for C言語はこんな感じ。

  • テストしやすいという、テスタビリティ
  • (例1)
    int
    func1(
        int a1,
        char* a2,
        int a2size )

    (例2)
    foor_obj_t*
    func2(
        var_obj_t* b1 )

    まず、基本的な関数の型はint型がいろいろ都合がいい気がする。エラー番号を復帰したり、errnoを復帰したり、直感的なものがよいと思う。例1のように、文字列格納先の先頭ポインタを引数渡しする場合は、呼び元で確保したサイズも合わせて引数渡しすること。(ちなみによくあるライブラリによっては文字列の先頭ポインタとサイズを構造体にしているものも多い。)サイズを引数指定することで、データコピー時のバッファチェックがしやすくなる。

    例2のような引数や復帰値に構造体を使った関数は、関数内で動的確保を行い、呼び元の責任で開放する。構造体の初期化がしやすくなる(と、僕は思ってる)。

  • テストケースを作成しやすいという、テスタビリティ
  • 例3
    int
    func3(
        int c1,
        const char* c2,
        char* c3,
        int c3size )
    {
    /* (A)引数チェック */
        if( c1 <= 0 || c2 == NULL || *c2 == 0x00 || c3 == NULL || c3size <= 0 ){
            return(ERR_ARGV);
        }

    /* (B)本体処理 */

    /* (C)エラー処理 */
    retired:
        /* 事後処理・初期化・開放処理 */
        func_free();
        return(ERR_SYSTEM);
    }

    実装は(A)引数チェック(B)本体処理(C)エラー処理の3部で構成すると、テストケースが作成しやすいのではないかと思っている。(A)引数チェックは必須。ここでのチェックがこの先のテスト空間を限定してくれるから。あるいは、ここに欠陥を紛れ込ませて、テストのテスト(テストが無作為で紛れ込ませた欠陥を検出できるかの検証)ができるかも?引数によっては関数の旅をするものもいるだろうが、
    僕の場合はすべての関数に対してここは必ずテストケースとして設定する。

    (B)実装部分については、割愛。

    (C)C言語で「goto文は使わない」というしきたりがあるかもしれないが、エラー処理については使ったほうがフローが簡潔になるし、開放漏れ・クローズ漏れを起こしにくいんじゃないかと思います。開放漏れ・クローズ漏れはなかなか単体テストでは発見しずらいか。

    てな感じで僕は設計してます。

    ==

    さあ、実装とテストを駆動させるのだが、ご紹介するのは、僕が単体テストで使う3つのツール

    • minunit(自作改良版) - 単体テスト用テストコマンド自動生成
    • minunitというたった3行のユニットテストツール。これをベースにテストコマンド用ソースを自動生成するツールを自作して利用。

    • gcov - プログラムコードカバレッジ測定ツール
    • GCCのプロファイリング機能で「-fprofile-arcs -ftest-coverage」を指定してコンパイルする。詳しくはここで。分岐網羅の測定も可能か。

    • lcov - テストカバレッジ情報グラフィック化
    • gcovで測定した結果をHTMLと画像に変換するツール。詳しくはここ。Linux用だが、Solarisにも移植はできるよ。

    これらのツールについては、もう少し勉強・改良してみたいと思っています。

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

    C言語における単体テスト、あるいはTDDについての考察

    今日はあったかかった。というか昼間は暑かった。

    ==

    単体テストといえばjavaのためのJUnitが有名だが、C言語でもCUnitCUnit for Mr.AndoEmbeddedUnitCCUnitなどいくつか世の中に出回っている。

    C言語におけるTDDの問題点と解決方法

    (1) スタブと実体の競合
    他の関数をテストするために作成したスタブと、関数の実体が競合を起こします。

    (中略)

    (2)スタブ同士の競合
    テスト対象の関数に応じたスタブを作成した場合にスタブ同士が競合を起こします。

    ここではスタブの競合という問題を、関数の別名宣言、関数ポインタの設定などの手順を使って回避しています。あるいはコンパイル環境を分ける、という解決法も示されています。

    これって、ボトムアップの単体・結合テストを繰り返すことでも回避できるんじゃないかと思ってるんだけど、どうなんだろう。最下部のコンポーネント(C言語であれば、システムコール標準関数にあたるか)については単体テスト実施済みというスタンスをとれば、ボトムアップの結合テストを繰り返していけば、すべてのコンポーネント(C言語であれば、ユーザ関数)の単体テストとみなせるのでは?という考え方。

    実際に僕がやっている単体テストってこのやり方なのだ。正確には単体テストの進捗具合によって単体(コンポーネント)の単位が大きくなっていくので、見方によっては結合テストともとれるか。

    詳細についてはいずれまた。今日はこれくらいで。


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

    単体テストの効用

    年末年始の休み中に読み返そうと思って、ボーリス バイザーの本体系的ソフトウェアテスト入門を家に持って帰ったが、読まずじまい。。。

    ==

    「テスト環境」を見直し、開発生産性・品質向上を実現!【第1回】 by TechTargetジャパン

    単体テストには大きい利点があるにも関わらず、開発チームが首尾よく単体テストを実行していくためには多くの努力と挑戦が必要となる。最も大きい挑戦の一つは、実質的にすべての開発グループに存在するスケジュールに対するプレッシャーである。開発者が一つのプログラムを書いた後で、開発者には選択肢がある。それは、テストコードを書くべきか。それとも、次の機能のプログラムを作成すべきか。

    ほとんどの開発者が、スケジュールに対するプレッシャーを感じ、テストコードを書かないことを選択し、それを正当化するのは簡単である。しかも、単体テストに最適なプログラムコードを書くことは単体テストよりも多くの時間が必要となり、プレッシャーのかかった開発者にとっては「テストのため」のプログラミング手法へと変更することは間違っていると感じてしまうのは最も現実的な話なのである。

    単体テストがプログラムを開発することと異なる作業と感じている限り、この抵抗感は存在してしまう。それではどうすればよいのだろうか。

    スケジュールに対するプレッシャーも、単体テスト実施の抵抗のひとつだが、もっと大きい壁が存在する場合があると思う。単純にいえば「単体テストの軽視」。もちろん、ある程度の教育を受けたエンジニアであれば、「工程が進むにつれてバグ修正コストが大きくなる」ということは知っている。でも知っているだけで、それを回避するところまでたどり着いていないエンジニアやチームも多いんじゃないかと思う。

    テスト駆動開発を含めて、単体テストに注目した開発をすることで、品質の高い設計・品質の高いモジュール・品質の高い開発手法を手に入れられる。チームメンバーに単体テストのエクササイズをさせて、単体テストの効果を実感させ、単体テスト重視の第一歩を踏み出させるのもテストマネージャーの仕事かな、と思います。

    あとは、各テスト工程で重複したテストをすることによるムダを嫌う、というのもあるか。

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

    バグの巣を探る - 「AgitarOne」for Java自動テスト生成

    今日は社外の勉強会にはいけませんでした。残念!

    ==

    自動テスト生成によって、既存コードの質を上げる--アジター社CEO語る by ITPro

    これまでも、開発者はユニット単位で動作を検証するのが普通だった。しかしそれは、コードが「何をすべきか」ということについての検証しかしないことが多い。つまり、想定外のコードや例外となるケースのテストを記述するのは難しい。

    (中略)

    その通りだ。旧版に搭載していた「アジテーション」という、おかしな振る舞いをするケースを見つける機能を応用して、いろいろな入力データに対する出力結果のフローを見て、どのように振る舞うかを解析する。その結果からテスト・ケースとすべき状態を抜き出して、テスト・コードを自動生成する。

    自動的にバグのありそうな条件を見つけ出すって、どうやってるんだろう?truss/strace/ltraceをして出現したシステムコールについて自動分析して怪しい条件を作成してるのかな?



    --ほかのプログラミング言語へ対応することは考えているのか。

    いや。全く考えていない。CやC++、Visual Basicはミッション・クリティカルな言語ではない。C#は可能性があるが、製品で対応しなければならないほどの市場ではない。

    C言語も作ってください~~メールサーバとかけっこうミッション・クリティカルなものもありますよん。

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

    単体テスト~結合テスト

    単体テストをするとき、「スタブ」と「ドライバ」が必要になってくる。

    Photo

    スタブとは、テストのために用意されるテスト対象が呼び出す予定のメソッドや関数、サブルーチンのこと。ドライバは、テストのために用意されるテスト対象を読み出すメソッドや関数、サブルーチン。ひとつのテスト対象にはひとつずつスタブとドライバが必要になってくる。ただし、一般的には単体テストをトップダウンあるいはボトムアップで行うことが多いため、スタブとドライバの片方はすでにテスト実施済みのメソッドや関数、サブルーチンがあてられる。

    そこでだ。世の中で結合テストといわれているのはどこを指すのか。

    Photo_1

    あくまで僕の認識だが、上図でいうと呼び出しa、呼び出しb、呼び出しcを「単体テストの通し」と考えている。この最上位の単体テストにたどり着くとそれはほとんどモジュールAの機能テスト=結合テストの一部とみなせるんじゃないか。

    なんでこんなことを書いているかというと、結合テストって単体テストやシステムテスト、非機能テストに比べて定義があいまいな気がしたので。誰かこれっていう定義を知ってたら教えてください。

    ちなみに、Softeware Testing のコラムでは、

    結合テストは、モジュール間のインターフェースや組み合わせを対象としたテストです。C言語などではモジュールの呼び出しや戻り、Javaなどではメッセージのやり取りになります。モジュール間のインターフェースに食い違いがあるバグや、(単一のモジュールでバグが見つからないのに)複数のモジュールを組み合わせた時にだけ見つかるバグを検出します。

    となってます。

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

    ソフトウェアテストPRESS vol.1 - テスト駆動開発と単体テストのカンケイ

    本棚にある雑誌を読み直してて見つけた。

    テスト駆動開発と単体テストのカンケイ by ソフトウェアテストPRESS vol1 (p111)

    さて、ここでTDDで作られるテストコードは、今までコーディング後に単体テストとして実施していたものを、先に書いただけと見ることも可能です。ということは、コーディングが終われば単体テストが終わったことになるのでしょうか?

    これは、半分正しいですが半分間違っています。

    (中略)

    実際のところ、TDDのテストファーストなテストで4〜5の観点のテストコードが漏れなく書かれていることは通常ないでしょう。なぜなら、テストコードを書くための工数が増えすぎて作業のスピード感が一気に落ちてしまい、本来のTDDの良さがなくなってしまうためです。

    引用中の4とは「境界値パターン」、5とは「ゼロ、NULLなどパターン」です。すなわち境界値分析とエラー推測的な観点でのテストケースといえます。そういったテストケースはTDDの場合、スピード感を損なうために通常はテストコードとして実装されず、単体テストとして別枠で工数をとって実施しましょう、という趣旨。

    これについは僕は少し考えが違ってます。というか僕はこの4と5のパターンのテストコードも実装します。指摘されている通り、スピード感が落ちてしまってTDDの良さが消えてしまいそうですが、それでも回帰的/スモーク的なテストができるというメリットはやはり捨てがたい。目的はTDDをすることではなくて、一定の品質を保つ製品をなるべく短期間・低コストで作成することだと思うので、「TDDの良さがなくなってしまうため」に4や5を組み込まないということはしない。

    ただ、前半部分のコーディング終了=単体テスト終了、っていうは間違ってない気がしてきた。何らかの指標があってそれを評価してはじめて単体テストが終了する、といったイメージかな。(テストカバレッジとか)

    # あくまで私見です。そんなにTDDの専門家といえるほどの知識も経験もないので。
    # ちゃんと勉強するためには、こんな本読まなきゃいけないのか。。。汗

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

    テスト駆動型開発(TDD)とグレーボックステスト

    最近、テスト駆動型開発(TDDあるいはBDD)についての雑誌を買ったり他のブログに感化されたりしたので軽い考察を。

    ==

    実装とテストを繰り返しながら設計をすすめる、といった形でコーディングするとき、みなさんはどんな手順ですすめますか?僕はこんな感じでやってます。(僕はC言語をベースに開発することが多いので、C言語をサンプルに)

    1. 骨組みだけの関数を作成する


    2. /**
       * func1(); ○△×をする関数
       * 引数
       * 復帰値
       * 0 正常
       * -1 エラー
       */
      int
      func1(
        const char* str1,
        char* ptr2,
        int num3 )
      {
        return(-1);
      }

      モジュール設計に基づいた関数の骨組みだけを記述する。ただし、復帰値がvoid以外ならばエラー復帰をまず記述しておく。


    3. テストスイートを作成する


    4. - 引数チェック
      - 正常系ケース
      - 限界値ケース
      - エラーケース
      - 異常系ケース

      テストスイートは複数のテストケースの集まりをあらわし、「引数チェック」「正常系ケース」「限界値ケース」「エラーケース」「異常系ケース」といったテストケースを作成する。引数チェックは、関数の引数にNULL、0、負の数、ヌル文字のポインタ(””)などを指定した場合に正しく引数エラーを返すかどうかを確認する。正常系ケースでは、尤もらしい動作をするであろう条件を指定する。最低でもひとつ、そしてコーディング後に追加するのもあり。限界値ケースは、条件の境界値(最大値、最小値)において正常な動作をすることを確認する。これは正常系ケースと兼ねてもいい。エラーケースは、尤もらしいエラーが発生する条件を指定し、エラーが発生することを確認するテストケース。異常系ケースは、今までの4群以外で、例えば非常に大きなデータや、言語上の限界値などを指定する。

      これらのテストケースをまとめる際、仕様書の欠陥などを同時にレビューするのもよい。


    5. テストを実行する
    6. 骨組みだけの関数をコンパイルし、テストスイートにある条件&評価でテストする。僕の場合はMinUnitというマクロをベースにテストコードを自動生成して、「-fprofile-arcs -ftest-coverage」をオプションにしてコンパイル&実行する。

      ちなみに、ほとんどテストケースは失敗に終わり、いわゆる「レッド」の状態。



      /* file: minunit.h */
      #define mu_assert(message, test) do { if (!(test)) return message; } while (0)
      #define mu_run_test(test) do { char *message = test(); tests_run++; \
                  if (message) return message; } while (0)
      extern int tests_run;

      -fprofile-arcs -ftest-coverage」をオプションにするのはあとでgcovでカバレッジ測定をするため。


    7. テストケースをクリアするように実装をすすめる
    8. 対象となる関数を実装していき、テストケースが「グリーン」にしていく。このとき、実装とともに必要と思われるテストケースを追加していくのもあり。


    9. テストスイートがクリアすることと、カバレッジ率を80%以上に保つ
    10. テストケースをすべてクリアすることと同時に、ステートメントカバレッジにも着目すること。僕の場合は、目安は80%~95%を目標にする。


    本当は「リファクタリング」も必要なのだが、とりあえずこんな感じで試しているところ。

    ==

    さて、こういったテストファーストで実装していくこと得られるメリットは、というと、、、

    1. ユニットテストケースがグレーボックスっぽい
    2. 境界値や異常系を考慮するので、グレーボックス的なアプローチができる。

    3. 引数チェックでブラックボックスっぽい
    4. 引数チェックは境界値やエラー推測といったブラックボックス的なアプローチができる。

    5. 仕様書の見える化、掘り起こしができる
    6. テストケースを作成するプロセスは、仕様を浮き彫りにする作業となる。

    ただし、いいことずくめではないと思っていて、引数が多くなったり、複雑な処理になるにしたがって、テストケースは膨れ上がる。テストをグリーンにするために実施カバレッジは少なくとも100%にする必要もある。だから、どこかで手を打たなくちゃならない。その基準は、まだよくわからない。


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

    lcovでテスト・カバレッジ状況をグラフィック化

    GNU CC についてくる gcov を使ってテストコマンドのテスト・カバレッジを測定してみた。このデータは lcov というツールでグラフィック化ができるらしい。

    Lcov

    ちなみに、「_sutest」ではじまるソースは単体テストコマンド用ソース。「util.c」は今作りかけなのでカバー率が低い。これ、日本語化とかうちの会社向けに改造しちゃおうかな。

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

    Stack Unit Testing Framework for C (SUtest)

    MinUnitを改良して、C言語用の単体テストツールを作ってみた。判定ロジックはMinUnitの3行マクロでそのまま使わせてもらって、

  • テストスイート読み込み

  • テストコマンド用ソース作成

  • テストコマンドコンパイル

  • テストコマンド実行&結果出力

  • という一連の作業を自動化してみた。

    テストスイートはテキスト形式の超簡単な形式で、どんどんテストケースを積み上げられる形。積み上げるのでStack Unit Testing Framework for C(SUtest)なんて名付けてみた。笑

    自分で試しに導入してみたんだが、コレが意外にも使える。社内で誰か試してみて感想が聞きたいぜ。

    テストスイートはこんな感じ。関数名、復帰値の変数型、条件式、引数、・・・

    /*
    # cat2string(); 文字列と数字を結合して文字列にする
    cat2string|char*|result == NULL|NULL|0
    cat2string|char*|result == NULL|NULL|1024
    cat2string|char*|result == NULL|NULL|-1
    cat2string|char*|result == NULL|NULL|-1024
    cat2string|char*|strcmp(result, "abc0") == 0|"abc"|0
    cat2string|char*|strcmp(result, "abc1024") == 0|"abc"|1024
    cat2string|char*|strcmp(result, "abc-1") == 0|"abc"|-1
    cat2string|char*|strcmp(result, "abc-1024") == 0|"abc"|-1024
    */


    んで、テストコマンド用ソースはこんな感じ。

    #include "hoge.h"
    #include "sutest.h"

    int tests_run = 0;

    static char* test_sutest0(){
        char* result = (char*)cat2string(NULL, 0);
        su_assert("error!!: LINE=[41]", result == NULL );
        return 0;
    }

    static char* test_sutest1(){
        char* result = (char*)cat2string(NULL, 1024);
        su_assert("error!!: LINE=[42]", result == NULL );
        return 0;
    }

    static char* test_sutest2(){
        char* result = (char*)cat2string(NULL, -1);
        su_assert("error!!: LINE=[44]", result == NULL );
        return 0;
    }

    static char* test_sutest3(){
        char* result = (char*)cat2string(NULL, -1024);
        su_assert("error!!: LINE=[45]", result == NULL );
        return 0;
    }

    static char* test_sutest4(){
        char* result = (char*)cat2string("abc", 0);
        su_assert("error!!: LINE=[47]", strcmp(result, "abc0") == 0 );
        return 0;
    }

    static char* test_sutest5(){
        char* result = (char*)cat2string("abc", 1024);
        su_assert("error!!: LINE=[48]", strcmp(result, "abc1024") == 0 );
        return 0;
    }

    static char* test_sutest6(){
        char* result = (char*)cat2string("abc", -1);
        su_assert("error!!: LINE=[50]", strcmp(result, "abc-1") == 0 );
        return 0;
    }

    static char* test_sutest7(){
        char* result = (char*)cat2string("abc", -1024);
        su_assert("error!!: LINE=[51]", strcmp(result, "abc-1024") == 0 );
        return 0;
    }

    int testcase_count = 63;

    static char* all_tests(){
        su_run_test(test_sutest0);
        su_run_test(test_sutest1);
        su_run_test(test_sutest2);
        su_run_test(test_sutest3);
        su_run_test(test_sutest4);
        su_run_test(test_sutest5);
        su_run_test(test_sutest6);
        su_run_test(test_sutest7);
        return 0;
    }


    int main(int argc, char **argv) {
        char *message = all_tests();
        if (message != 0) {
            printf("%s\n", message);fflush(stdout);
        }
        else {
            printf("ok\n");fflush(stdout);
        }
        printf("Testcase : %d\n", testcase_count);fflush(stdout);
        printf("Tests run : %d\n", tests_run);fflush(stdout);

        return message != 0;
    }

    このテストコマンド用ソースをコンパイルして、実行するのだ。
    # コンパイルが system() なんでちょいとしょぼい感じかも。

    自作のヘッダーを使ったり、memset()などを指定したりすればかなり守備範囲が広がる。

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

    MinUnit -- a minimal unit testing framework for C

    C言語のxUnit系テストツールを探していたら、こんなのを発見。

    MinUnit -- a minimal unit testing framework for C

    /* file: minunit.h */
    #define mu_assert(message, test) do { if (!(test)) return message; } while (0)
    #define mu_run_test(test) do { char *message = test(); tests_run++; \
        if (message) return message; } while (0)
    extern int tests_run;

    たったこれだけ。少し書き加えてチーム内のツールにできそう。

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