ウィリアムのいたずらの、まちあるき、たべあるき

ウィリアムのいたずらが、街歩き、食べ物、音楽等の個人的見解を主に書くブログです(たま~にコンピューター関係も)

【実務】GDBで単体テストの個人的見解

2022-10-03 08:14:06 | 個人的見解
前に、

って話をいきなり書いたけど、その前提となる、GDBで単体テストをする方法について、個人的な見解を書いてみる。

そもそも、「単体テスト」、「GDB」とは、ってことから・・


■「単体テスト」とは、

 ソフトウェアのテストの一つ。ソフトウェアのテストの分類は、いろいろあるけど、テスト対象(範囲)に着目すると

単体テスト:一つの一つの、モジュール・ユニット・関数について着目してテストする。
結合テスト:モジュールを結合させて、入力値が処理されて正しく出力するか確認する
総合テスト:システムとして、要求を満たすかどうかを確認する。業務に対応したテスト(機能要件の確認)や負荷テスト(非機能要件のテスト)などが、ソフトウェア開発者側で行われ、このテスト終了後、引き渡されると、お客さんのほうで、受入テストを行う。

 単体テストの特徴は、一つ一つのモジュールについてテストする際、ソースコードを参照して(開示して)テストする。これをホワイトボックステストという。(結合テスト以上では、ソースコードは開示されないでテストすることも多い。この場合のテストをブラックボックステストという)

 ホワイトボックステストでは、ソースコードを開示しているので、ソースコードの命令や分岐をどれだけ網羅したかという網羅率に基づいたテストができる。これには3つの基準がある。

命令網羅(C0):すべての命令をテストしたというもの
分岐網羅(C1):すべての(IF文、SWITCH文の)分岐をテストした
組み合わせ網羅(C2):すべての条件・組み合わせをテストしたというもの

普通、C0,C1 100%のテストが行われる。
C2は組み合わせが2つまでならまだしも、3つ以上になると・・爆発してしまう。なので、2つの組み合わせをすべて行うペアワイズ、2つの組み合わせは完璧、3つはできるだけ・・・という直行表による組み合わせ方法などがある。



■GDBとは
 単体テストのC0,C1 100%を達成するには、すべてのパスを通るテストをしないといけない。このテストを実現するためのツールとして(まあ、プログラムのデバッグ用でもあるんだけど)、GDBがある。
 GDBは、Linuxなどでも使える、コマンドを打っていく形のツール、ってことはGUIツールとちがって、コマンドをあらかじめ用意し、テスト自動化できるってことなんだけど(それが特徴なんだけど)、今日はその話はしないで、単純にテストする方法論を話す(自動化はまたいつか)
 普通、C言語で書かれたプログラムが対象・・・だと思う



■GDBによる単体テスト手順

GDBで単体テストする場合の手順は、こんなかんじ

(1)GDB用にコンパイルする
 たしか、
   cc -g ファイル名
 って、gオプション付けるんだと思った
 行番号情報付きで、コンパイルされる(普通が行番号がつかない)
 これで、テスト対象の実行ファイルを作成する

(2)GDB立ち上げ
 
 gdb テスト対象実行ファイル名
 で、テスト対象が実行できる状態になる

(3)テスト対象にブレークポイントを貼ってrun
 テストする対象は、たぶん関数だと思う。
 そこに飛ぶように、ブレークポイントを貼る

  b 関数名

 または
  b ファイル名:行きたい行

 で、ブレークポイントを貼ったら

 run

 と入力する。うまくいけば、ブレークポイントで止まるけど、
 そこに行く前に致命的エラーで落ちたり、終了してしまったり・・

※ちなみに、mainからたどれない関数は、ブレークポイントを貼っても、そこに行きようがないので、ブレークポイントで止まらないってか、テストできない

(4)関数まで行ったら、テストしたい箇所を通過したり、
  値を確認したり

  関数にたどり着いたら、
   n
  って打っていけば、次々命令を順次実行していく
  p 変数名
  ってやると、その変数の値を表示する。

  単体テストは
   「●●の処理を実行すること」
  だけど、それはnコマンドを入力して、テストで確認する行が
  表示された後(このときはまだ実行されていない)、nと打って、
  次の行に行けば、処理されたことになる。

  また、
   「変数●●が、XXの値になっていること」
  を確認したいなら、p 変数 コマンドを入力して、値を確認する

 なお、ループなどがあって、いちいちnで順次処理を追っていきたくない場合は、
   b 行番号
   c
 で、その行まで飛ぶので、for文とかで、値をわざわざ確認する必要がない場合は、for文を抜けたところにブレークポイントを貼って、cで飛ばす



■網羅率100%をするために(1)・・・変数への値設定

 単体テストでは、網羅率100%にする。
 この場合、入力値を変化させただけでは、絶対に調べられないパスが出てくる。例えば、「ある関数の返り値がFALSEのとき、プログラムを抜ける」という分岐があり、その関数がFALSEを絶対返さない場合(順次命令を実行して、返り値TRUEで復帰する場合など)、その分岐には、どんなことをしてもいかない。網羅率100%にならない。

 そこでgdbでは、値を設定して、本来行かない分岐を無理やり生かせることができる。例えば、上記の例、TRUEしか返さない関数を関数func_true()とすると、

  rc=func_true();
       if (rc == FALSE)
      {
             return(FALSE);
     }

って感じになっているけど、このとき「rc=func_true();」が表示されたときに

set rc=FALSE

 または

p rc=FALSE

と「set 変数名=設定したい値」
か「p 変数名=設定したい値、式」
ってすると、変数に設定した値がはいる。

上記の例だとrcはFALSEになるので、
IF文でFALSEの側に分岐して テストすることができる



■網羅率100%をするために(2)・・・関数の中に入ったり戻り値設定

上記の例、rcの変数を使わないで、
     if (func_true() == FALSE)
      {
             return(FALSE);
     }
って書いてあった場合、どうやって分岐させるかだけど、
この場合、func_true()の関数の中に入って、返り値を変更させる。

関数に入るには
関数が表示されたときに
(上記の例だと「if (func_true() == FALSE)」が表示されたときに)

s

コマンドを打つと、入ることができる。
(sはステップインの意味)

で、関数の中に入ったら、出る方法は2つある

1つは
fin
と入力する。この場合、関数の中の処理を実行して呼び出し元に戻る

もう一つは

return 返り値

として、指定した返り値で戻ることができる。

 finで戻るかreturnで戻るかの判断だけど
・ステップインした関数を実行して問題ない場合は、finを実行する。
 自分が意識している変数以外で設定必要な変数があった場合、
 returnだと、設定しないけど、finなら設定してくれるかもしれないから

・ステップインした関数を実行すると異常終了してしまったり、
 意図した返り値にならない場合は、returnで戻る

上記の例(FALSEのパスのテストをする場合)の場合は。。。
どちらを使うかわかったら、この違いを理解している
(このエントリの最後に正解を書いておく)



■そのほかの利用法

 単体テストではなく、デバッグとしてGDBを利用することも多い
ってか、そっちが普通か?
 Segmentation Faultで落ちる場合、落ちたところで
  where
コマンドを打てば、どういう感じでどこで落ちたかわかる。
(落ちなくても、whereをすれば、どこにいるかわかる)

 またList 行番号で、その行番号前後のソースコードを
表示してくれる。
 デバッグの場合、落ちると、コアダンプというのができて・・・
・・・という話があるんだけど、今日はデバッグの話でなく、
単体テストの話なので、この辺でとめておく



途中で書いた「GDBで自動化」については、気が向いたら、いつか書こうと思います

※上記の例の場合、finかreturnか
→return
 理由:この例は、関数がFALSEで返ったときのパスをテストしたいという話だった。しかし、この関数を実行してもTRUEしか返らないという話。なので、finを行うと、どんなことをやっても(FALSEにならないから)FALSEのパスはチェックできない。

 そこで、return FALSEと打って、返り値をFALSEにしてFALSEのパスをチェックする



  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする