Tcl/Tkで作ったGUIアプリは単体テストしにくいですよね。特に画面の状態遷移があるような本格的なアプリケーションだと、必要な機能を呼び出すまでにいくつかの手順が必要になって泣きそうになります。
よく考えればGUIを組み立てなくても必要なオブジェクトさえあればテストできるので、フレームを表示させないでテストしてみたら効率が上がったので紹介します。
やり方
- テスト対象のオブジェクト(buttonやlabel)に必要なフレームのみを生成する。packやgridはしない。
- テスト対象のオブジェクトを生成する。同じくpackやgridはしない。
- テストを実行する
- テスト用に作ったオブジェクトを destroy する。
サンプルソース
utest.tcl
#---------------------------------------------------------------------
# GUIアプリケーションのソース
proc gui { } {
# フレームの定義
frame .f
frame .f.foo
frame .f.baa
frame .f.huga
frame .f.huga.hum
pack .f
pack .f.foo .f.baa .f.huga .f.huga.hum
# ボタンの定義
button .f.foo.b -text "foo"
pack .f.foo.b
button .f.baa.b -text "baa"
pack .f.baa.b
button .f.huga.hum.b -text "huga.hum"
pack .f.huga.hum.b
}
# 全てのボタンのパスを取得
proc get_all_buttons { } {
return ".f.foo.b .f.baa.b .f.huga.hum.b"
}
# テストされる手続き
# get_all_buttons が返すボタンを全て無効にする
proc disalbe_all_buttons { } {
foreach b [get_all_buttons] {
$b configure -state disable
}
}
#----------------------------------------------------------------------
# テスト用のソース
# disalbe_all_buttonsのテスト
#
set err 0
set log ""
proc test_disable_all_buttons { } {
global err log
# ダミーで、ボタンとボタンに必要なフレームを作る
foreach f {.f .f.foo .f.baa .f.huga .f.huga.hum} {
frame $f
}
# テスト用にボタンを作る。
foreach b [get_all_buttons] {
button $b -state normal
}
# テストされる手続きの呼び出し
disalbe_all_buttons
# 結果のチェック
foreach b [get_all_buttons] {
set state [$b cget -state]
if { $state != "disabled" } {
append log "Error $b is not disabled ($state)\n"
incr err 1
} else {
append log "PASS $b\n"
}
}
# 一番上のフレームを削除して、ボタンとフレームを全て削除する。
destroy .f
}
# テストの実行
test_disable_all_buttons
# 結果の表示(通常はファイルに出す)
if { $err == 0} {
tk_messageBox -type ok -title "test終了" -message "$log"
exit 0
} else {
tk_messageBox -type ok -title "test終了(エラー)" -message "$log" -icon error
exit 1
}
これで、wish utest.tcl で単体テストが実行出来ます。このやり方で小分けにしてテストしたところ、今まで動いていたアプリも細かいバグをいくつか見つけました。もう一つwishとプロセス間通信をしながらテストするバッドノウハウがいくつかあるのですが、それはまた別の機会に。