効率的で網羅的なテストのために

課題プログラムの理解 (5a):%コマンド入力の解釈と名簿管理機能の実装

今回は,コマンドとして入力された文字列を分解(解析)し,各コマンドとして実行する流れ(の一部)を完成させることが目標です.

前回までの詳細化の結果を思い出して.また,exec_command()の実装の話も思い出してから,先に進んでください.

回帰テストとは

作成の過程で,自分の作成物にデータ入力してみて動作をテストしたり,db-sampleに同様のデータ入力をして自分の作成物と比較したりすることでしょう.何度も何度も入力することでしょう. その度に延々 %P %C … や CSV を入力したくないですよね.テストデータを手でいちいち打ち込んだり,マウスでコピペするのは面倒な上に不正確です.ここでは,テストを効率化する方法を考えてみます.

プログラム作成中は,何度も何度も,嫌になるぐらい同じテストデータを入力します.例えば,%P コマンドのテストを考えます.

  • 先頭からN件表示機能を作って,テスト失敗,…,テスト成功!!

  • 後ろからN件表示機能を作って,テスト失敗,…,テスト成功!!

このとき,何か忘れていませんか.後ろからN件表示機能を追加したことによって,先頭からN件表示機能は,動かなくなったりしていないでしょうか.

本当は,以下のテストが必要です:

  • 先頭からN件表示機能を作って,テスト失敗,…,テスト成功!!

  • 後ろからN件表示機能を作って,テスト失敗,…,テスト成功!!

  • 先頭からN件表示機能をもう一度テストして成功!!

つまり,プログラムを変更したことによって前の機能に影響が出ていないか,再テストしています.このような再テストを回帰テスト(regression test)と呼びます.もう一度の部分は, 最初のテストと全く同じ入力にする必要があります.したがって,一連のテスト内容を全て覚えて(何かに記録して),プログラムを変更する度に全てのテストを実施しなればなりません.これって,手動でやりたくないですよね?

ここでは,この面倒をいくらか軽減する方法について説明しておきます.これを参考に十分なテストを実施して,皆さんのプログラムがよい物になる事を期待しています.

リダイレクションの利用:手入力なんてメンドクサイ

世の中には,プログラムを書換えた瞬間にコンパイルと回帰テストを実施して,結果を通知してくれるシステムもあります.興味のある人は,TDD (Test Driven Development) とテストの自動化について,調べてみるといいでしょう.

ここでは,完全自動化という訳にはいきませんが, UNIX のリダイレクションとシェルスクリプトを利用して,テストの工数を軽減する方法について説明します.これらの機能は,コンパイルなどを自動化するのにも便利です.ぜひ覚えておきましょう.

リダイレクションとは,コマンドにキーボードからデータを入力する代わりにファイルの内容を入力データとして与えたり,コマンドの実行結果を画面ではなくファイルに出力したりする方式のことです.では,ファイルからdb-sampleに入力を与えてみましょう.例えば,以下の内容でファイルtest01.txtを用意します.このファイルには,db-sample実行後に入力したい内容を書いておきます.

5100046,The Bridge,1845-11-2,14 Seafield Road Longman Inverness,SEN Unit 2.0 Open
%P 0
%Q

では,リダイレクションを利用してみましょう.

$ ./db-sample < test01.txt
Id    : 5100046
Name  : The Bridge
Birth : 1845-11-02
Addr. : 14 Seafield Road Longman Inverness
Comm. : SEN Unit 2.0 Open

キーボードから入力した例と同じ表示(出力)が得られました.つまり,これまでのように

$ ./db-sample

とする代わりに,

$ ./db-sample < test01.txt

とすることによって,キーボードから入力する代わりにtest01.txtから入力できました.これで,何度でも簡単に同じ入力でdb-sampleを実行できます.

今度は,実行結果を画面に出力するのではなく,ファイルresult.logに保存してみましょう.ここでは,キーボードから入力してやります.

$ ./db-sample > result.log
5100046,The Bridge,1845-11-2,14 Seafield Road Longman Inverness,SEN Unit 2.0 Open
%P
%Q

%P の実行結果は,画面ではなくresult.logに出力されています.エディタでresult.logを開いて見るか,以下に示すcatコマンドで中身を表示させてみましょう.

$ cat result.log
Id    : 5100046
Name  : The Bridge
Birth : 1845-11-02
Addr. : 14 Seafield Road Longman Inverness
Comm. : SEN Unit 2.0 Open

次の例は,入力と出力の両方を一度にリダイレクトした例です:

$ ./db-sample < test01.txt > result.log

同様にcatで確認できます.

まとめると,

  • キーボードの代わりにtest01.txtから入力

    $ ./db-sample < test01.txt
    
  • 画面の代わりにresult.logへ入力

    $ ./db-sample > result.log
    
  • バリエーションとして,ファイルに上書きしないで追記する方法もあります

    $ ./db-sample >> result.log
    
  • 両方いっぺんに

    $ ./db-sample < test01.txt > result.log
    

となります.

diff コマンドによる一致判定:目なんてシンジラレナイ

リダイレクションによって,以下のようにテストを進めることができます.

  1. 一連の入力(テストケース)をtest01.txtに記述する

  2. 自分の作成したmy-commandを実行し,結果をmy-result.logに保存する

    $ ./my-command < test01.txt > my-result.log
    
  3. my-result.logの中身を確認する

さて,どうやって確認しましょうか?1つ考えられるのは,db-sampleで同じことをやってみて,同じ結果になるか比較する方法でしょう.

たとえば,こうなります.

$ ./db-sample  < test01.txt > result.log
$ ./my-command < test01.txt > my-result.log
$ diff result.log my-result.log

diffコマンドは,2つのファイルを比較して差分を表示してくれるコマンドです.ここでdiffコマンドの結果画面に何も出力(表示され)なければ,両者の実行結果はまったく同じということになります.つまり,テスト成功ということです.

これで,何度でも同じ試験をすることができます.何種類かのテストを実施する必要があれば,test02.txt,text03.txtを作ってもいいでしょう.

あるいは,db-sampleの代わりに,my-commandバージョン1 と,その後の機能追加版my-commandバージョン2でもいいでしょう.

$ ./my-command-v1 < test01.txt > my-result-v1.log
$ ./my-command-v2 < test01.txt > my-result-v2.log

diff コマンドも工夫をすると,便利なオプションがいくつかあります.例えば,

$ diff -qs my-result-v1.log my-result-v2.log

のように,diff-qオプションと-sを付けてみると,どういう結果になるでしょうか?(2つのオプションを合わせて,-qsと書いてます.)

あるいは,以下はどうでしょうか?

$ diff -u --color=auto my-result-v1.log my-result-v2.log

その他のdiffの便利な使い方に興味があれば,man diffで調べてください.

シェルスクリプトによる自動化:何回でもテスト

ここまでくると,上記のようにいくつものコマンドを間違わずに入力することすら面倒になります.

シェルスクリプトの出番です.シェルスクリプトとは,シェルが実行する一連のコマンドをテキストファイルに書いたものです.バッチファイルと呼ばれることもあります.さっそくやってみましょう.

test-all.shというファイルを以下の内容で作ります:

#!/bin/sh

./db-sample  < test01.txt > result.log
./my-command < test01.txt > my-result.log
diff -qs result.log my-result.log

test-all.shdb-sampleのときと同じく実行権を付与して:

$ chmod +x test-all.sh

シェルスクリプトを実行します:

$ ./test-all.sh

画面に何も出なければ,テスト成功です.必要に応じて,プログラミング演習1までの実装項目テストをtest01.txt,プログラミング演習2までの実装項目テストをtest02.txt,とするなど,必要なテストを複数用意してもいいでしょう.

また,gccによって正しくコンパイルができていることも確認したほうが,安全ですね.

以上を踏まえると,以下のようなスクリプト例が考えられます.

Listing 14 test-all.sh: An example of test script for Exercises on Programming 1 and 2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/sh
# コメントは,# から行末まで
# || exit 1 は,gcc が文法エラーでコンパイルに失敗したら,
#           以降の処理を中断して終了という意味
#
gcc -Wall -o my-command my-command.c || exit 1

./db-sample  < test01.txt > result.log
./db-sample  < test02.txt >> result.log

./my-command < test01.txt > my-result.log
./my-command < test02.txt >> my-result.log

diff -qs result.log my-result.log
diff -u result.log my-result.log > diff-result.log

動作の概要を以下に示します.

  1. my-command.cgccでコンパイルする

  2. コンパイルが成功したら次に進む,失敗したら,その場で終了

  3. db-sampleについて,test01.txt,test02.txtを実行し,その出力結果をresult.logに保存

  4. my-commandについて,test01.txt,test02.txtを実行し,その出力結果をmy-result.logに保存

  5. diffresult.logmy-result.logの差分を表示

test02.txtのテスト結果は,>>を使って追記していることに注意してください.

例えば,%C%P (正の値)の結果をテストするためのtest01.txtは,以下のようなテキストファイルとして,Emacsで作成することになるでしょう.

Listing 15 An example of test01.txt
1
2
3
4
5
6
7
8
5100046,The Bridge,1845-11-2,14 Seafield Road Longman Inverness,SEN Unit 2.0 Open
5100127,Bower Primary School,1908-1-19,Bowermadden Bower Caithness,01955 641225 Primary 25 2.6 Open
5100224,Canisbay Primary School,1928-7-5,Canisbay Wick,01955 611337 Primary 56 3.5 Open
5100321,Castletown Primary School,1913-11-4,Castletown Thurso,01847 821256 01847 821256 Primary 137 8.5 Open
5100429,Crossroads Primary School,1893-2-24,Dunnet Thurso,01847 851629 01847 851629 Primary 29 2.4 Open
%C
%P 3
%Q

なお,上記は,あくまで一例である,ということを強調しておきます.課題として実装すべきコマンドや機能は,網羅されてませんし,入力の要件も満たしていないことでしょう.

皆さんは,演習課題説明資料PDFを熟読し,要求や仕様として満たすべき機能を理解する必要があります.仕様の理解は,すでに実施してきたはずです.仕様を理解することなく,妥当なプログラムなんて書けませんから.

演習課題説明資料 (p2-theme-all.pdf),特に,その付録Aに書かれた仕様は,何度も何度も読み返す必要があるはずです.ドキッとしている人は,見落としがないか,改めて読み直してください.

注釈

Windows環境で自習している人は,テスト用ファイルの作成時には,改行コードの種別,ならびに,ファイル行末の改行コードの有無に注意してください.

まともなエディタであれば,改行コードに関する設定項目や処理方法が用意されているはずです.