« 2010年6月 | トップページ | 2010年8月 »

2010年7月

Common Lispのグローバル変数

Common Lispではグローバル変数(スペシャル変数)を定義する方法が2つある。
一つはdefparameterフォームを使う方法で、もう一つはdefvarフォームを使う方法だ。

この二つの使い分けを、Lispが得意とするプロトタイピングの観点から整理してみた。
つまり、

  • ソースコードを修正しても、修正前の値が維持される変数はdefvarで定義する
  • ソースコードを修正した影響がすぐに反映される変数はdefparameterで定義する

と考える。
プロトタイピングでは、設計しながら実装とリファクタリングをしていくので、こういう場面にはよく出くわす。

今、こんなソースコードを作っているとする。

(defparameter *delta* 1)
(defvar *count* 0)

(defun increment-widget-count()
  (incf *count* *delta*))

これをコンパイルしてロードする。

CL-USER> (compile-file "/work/param.lisp")
CL-USER> (load "param")

*delta*は1だ。

CL-USER> *delta*
1

*count*は0だ。

CL-USER> *count*
0

関数を3回実行すると、countは3になる。

CL-USER> (increment-widget-count)
1
CL-USER> (increment-widget-count)
2
CL-USER> (increment-widget-count)
3

ここでソースコードの*delta*を2に修正する。

(defparameter *delta* 2)
(defvar *count* 0)

(defun increment-widget-count()
  (incf *count* *delta*))

このファイルを再コンパイルしてロードする。

CL-USER> (compile-file "/work/param.lisp")
CL-USER> (load "param")

*delta*を確認すると、修正が反映されて2になっている。

CL-USER> *delta*
2

*count*を確認すると、3のままだ。

CL-USER> *count*
3

関数を3回実行すると、countは今度は2づつ増えて9になる。

CL-USER> (increment-widget-count)
5
CL-USER> (increment-widget-count)
7
CL-USER> (increment-widget-count)
9
CL-USER> *count*
9

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

PythonとRubyとLispのコレクション処理

コレクションの処理をするとき、Pythonではリスト内包表現を使うが、Rubyではブロックを使う。
例えば、コレクションcolの各要素を10倍した新しいコレクションを作るとき、Pythonなら次のように書く。

col = [1, 2, 3, 4, 5]
[x * 10 for x in col]

Rubyなら次のように書く。

col = [1, 2, 3, 4, 5]
col.collect{|x| x * 10}

両方とも、コレクションcolの各要素をxに代入して、x*10を評価した結果を新しいコレクションにする、という意味だ。

ところで同じコレクション処理をLispではどう書くのか?
いろいろな書き方があるが、loopマクロを使うと次のように書ける。

(setf col '(1 2 3 4 5))
(loop for x in col collect (* x 10))

オブジェクト指向のRubyとはかなり異なるが、Pythonのリスト内容表現とはよく似ていると感じる。

最近読んでいるPractical Common Lispでは、Lispを使ったモダンなプログラミング手法が紹介されていておもしろい。
Lispというと、古くてマイナーな言語と思われているかもしれない。
しかし、PythonやRubyをつかって現代的な開発をしているプログラマには、Lispの柔軟性は思いのほか気に入られるのではないかと考えている。

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

gcovでカバレッジテストをしてみる

「知識ゼロから学ぶソフトウェアテスト」の21ページから24ページにかけて書かれている、ステートメントカバレッジ(C0)とブランチカバレッジ(C1)の例が、gcovではどのように解析されるのかを確認してみた。

下の関数をテストする。

func(int con1, int con2)
{
int x = 1;

if (con1 == 0) {
x = x + 1;
}

if (con2 > 1) {
x = x * 2;
}

printf ("x = %d\n", x);
}

ステートメントカバレッジでは命令文(ステートメント)の実行状況を確認する。
開発者なら、

x = x +1
や、
x = x * 2

のコードが正しく実装できているかが気になり、例えば下のようなコードを使って、con1=0かつcon2=2の条件で、関数funcを実行してみるのではないだろうか。

main()
{
func(0, 2);
}

mainとfuncをtmp1.cというファイルに保存してgcovで解析する。

>gcc -fprofile-arcs -ftest-coverage -o tmp1 tmp1.c
>./tmp1
x = 4
>gcov -b tmp1.c
File 'tmp1.c'
Lines executed:100.00% of 11
Branches executed:100.00% of 4
Taken at least once:50.00% of 4
Calls executed:100.00% of 2
tmp1.c:creating 'tmp1.c.gcov'

命令文は100%実行されているが、ブランチ(分岐)で分かれるバスの50%が抜けていることがわかる。
このため、開発者がcon2>1と書くべきところをcon2>0と書いてしまったバグはこのテストでは見つけられない。

そこで、すべてのブランチの条件判定結果がTRUE、FALSEを少なくとも一回づつになるようにテストケースを書く。今の場合は、con1=0かつcon2=2のテストケースと、con1=1かつcon2=1のテストケースを書けば良いので、例えば下のようなコードを書く。

main()
{
func(0, 2);
func(1, 1);
}

このmainとfuncをtmp2.cというファイルに保存してgcovで解析する。

>gcc -fprofile-arcs -ftest-coverage -o tmp2 tmp2.c
>./tmp2
x = 4
x = 1
>gcov -b tmp2.c
File 'tmp2.c'
Lines executed:100.00% of 12
Branches executed:100.00% of 4
Taken at least once:100.00% of 4
Calls executed:100.00% of 3
tmp2.c:creating 'tmp2.c.gcov'

今度はブランチから分かれるすべてのパスを通っており、開発者がcon2>1と書くべきところをcon2>0と書いてしまったバグも見つけることができる。

このように、ステートメントカバレッジはテストとしては弱いものであるので、単体テストではブランチテストも合わせて行い、できるだけ高いカバレッジ率になるようにテスト計画を立てるのが良い。


知識ゼロから学ぶ ソフトウェアテストBook知識ゼロから学ぶ ソフトウェアテスト


著者:高橋 寿一

販売元:翔泳社
Amazon.co.jpで詳細を確認する


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

« 2010年6月 | トップページ | 2010年8月 »