Test and Debug

Menu


Test

作成したプログラムあるいは、関数が正しく動くかどうかを調べること。

ちゃんとしたアプリケーションを作ると、Test と code は、同程度あるいは Test の方が大きくなる。


Coverage

すべての code はテストされなければならない。Test で、どの部分の code が実行されたかを調べるツール。

Linux では malloc は必ず成功するし、BSD では write は必ず成功する。したがって、それらの失敗を調べる部分の code は決して実行されない。つまり、Coverage 100% には一般的にならない。

Gcov などのツールがある。


問題9.0

llvm-cov をつかってみる  Excercise 9.0


Unit Test

関数などの一部分をテストすること、あるいは、そのテスト。

Coverage をあげるためには必須

JUnit CPPUnit などのツールで、自動化するのが良いとされている。


Model Checking

あらゆる可能な入力、実行順序を数え上げて、必要な性質が満たされているかどうかを調べること。

状態を明示的に記録していくツールや、抽象的な状態に変換して調べる方法などがある。

SPIN や JavaPathfinder が有名。

Model Checking で、Error が発見できないことを確認できれば、「恒真ならば証明がある」という完全性定理を使って、証明があることはわかる。しかし、それは証明が実際に構成できることを必ずしも意味しない。


UI の Test

人間を対象にした Test


複雑で巨大なアプリケーションの Test

最初から Unit Test しまくる。

既に巨大なコードがあった場合は?

全部 Test するのは諦める。


証明すれば Test は不要?


そもそも Test 項目はどうやって作るのか?

bug を見つけるために test を書く。

Application がちゃんと動作することを保証する Test を書く。「ちゃんと動作」とはどういうことか?


仕様記述

Application がちゃんと動作していることを定義する論理式。プログラム中では assert とか書かれる。

assert は証明されるべきだが、証明できない場合もある。

一般的に、すべての可能な入力が実際にあるわけではない。プログラムの正しさの証明は、限定された状態に対して行われる。


入力変数と出力変数

assert は入力と出力を区別しない。

    入力 : 基本的にあらゆる可能な値が来る (∀ x)
    出力 : 性質を満たす一つの値           (∃ x)


Test駆動

code を書いたら、そこを Test する code を書く。

先に Test を書く。

Test できることを考慮しながらプログラムを書く。

大きなプログラムの一部を変更する場合は、変更した部分に問題が出る。全体を動かさないと、そこがチェックできないような変更は、地雷になってしまう。

プログラム全体を Unit Test 可能なように設計する。あるいは、そういう Frame work を用いる。


Debug

Test で発見された間違ったコードを探すこと。Test は結果が間違っていることを示すが、間違っている部分を指定することはない。

Unit Test は場所を特定するのに貢献する。


Debugger

gdb や printf などで、実行を観測する手段を確保する。


バグを再現する

エラーを確実に生じるような、入力あるいは環境を作る。


Conditional break point

break point を通過する回数

式の値

gdb のこれらの使い方を確認する。デバッガにこの機能があることを調べる。

極めて遅くなる場合がある。Haredware supported conditinal break point

Memory の特定の値を監視する Hardware support

プログラム中に if を書いて、その中に break point を仕掛ける。


2分法

あるプログラムの実行列

     f1 → f2 → f3 → f4 → f5 → f6 → f7 → f8

があった時に、出力がおかしいかどうかを、f4 の後で調べる。おかしかったら、その前にエラーがああり、そうでなければ、その後にエラーがある。

この方法で、逐次実行されるプログラムのデバッグは確実に成功する。但し、

* エラーが確実に再現されること* エラーが各段階でチェックできること

が必要である。

printf debug は、この手法の一部として使われることが多い。

conditonal break point を使って、2分法を行う。


up and down

gdb でエラーを見つけた後、where で stack frame を表示すると関数呼び出しの履歴が出る。

     f1 called from
     f2 called from
     f3 called from
     f4 called from

ここで、frame を up して行き、入力や途中の変数がおかしくなったところを見つける。二分法の一種。


Segmentation fault

メモリの存在しない場所にアクセスするエラー Segementation vaiolation ( SEGV とも )。0 をアクセスする場合が多い。(0をアクセスできる OS も、かつてはあった)

C や C++ では、malloc に関連した bug が出ることが多い。「malloc に bug なし」

確実に再現できる場合は2分法を使うが、

* malloc した時点ではエラーは出ない* free した時点は、次に malloc したところで SEGV する

あるいは、

* 間違った pointer が破壊したデータ構造にアクセスすることで SEGV する

最近では、コードを破壊しようとすると、その時点で SEGV するが、組み込み系などではチェックされない場合もある。

man malloc すると、malloc が使うデータ構造を調べてくれる。

Valgraind などのツール。


再現しないバグの捕まえ方

全く再現しないなら問題ない。たまに出るから困る。

    再現するまで実行を繰り返す script / gdb command / program を書く


並列実行関連の bug

再現性が低いのが特徴。

共有変数への lock しない access が原因なことが多い。lock しまくると性能が劇的に落ちる。

* 共有変数へのアクセスを最小限にする

モデル検証を使う。


Memory Leak

急速に起きる場合は再現性があるので比較的簡単に見つかる。

そうでない場合は、まず再現性を確保する。わざと使用可能メモリを制限するなど。

測定 Tool を使う。

    Objective C : Instruments の Leak
    Java : Visual VM


性能低下

再現性に問題がある。

* 測定しろ、話はそれからだ


最適化

 -g を付けると出なくなる bug、-O を付けると出る bug がある。

おかしいと思ったら

    Assembler出力 や memory dump を見ることを躊躇しない (binary hacking)

gcc は疑っても良い。


問題9.1

記述問題

Excercise 9.1


Shinji KONO / Wed Jul 6 14:02:17 2022