はじめに
今回は弊社が中心となって開発しているCutterという、書きやすさ・デバッグのしやすさを重視したC言語・C++言語用のテスティングフレームワークを使って、画像を使ったテストを簡単に書く方法を紹介します。
画像を使ったテストを書くとき
たとえば、画像を生成するアプリケーションを開発していて、機能追加によってその機能が壊れていないことを保証するにはどうすればよいでしょうか。 これは、以前生成した画像と比較するテストで検証できれば良いですね。
Cutterによるテストでは、テスティングフレームワークの機能のひとつとして画像差分をサポートしています。 そのため、画像だからといって特別なことをせず、フレームワークの枠内で簡単にテストを書くことができます。
では、実際にどんな風に簡単にテストを書くことができるのかみてみましょう。
Cutterをインストール
まずは、Cutterをインストールしましょう。 各種ディストリビューション向けのインストールのドキュメントがあります。そちらを参考にしてください。
Ubuntuの場合には、以下の手順で必要なパッケージをインストールすることができます。
% sudo apt-get -y install software-properties-common
% sudo add-apt-repository -y universe
% sudo add-apt-repository -y ppa:cutter-testing-framework/ppa
% sudo apt-get update
% sudo apt-get -y install cutter-testing-framework
普通のCutterのテストを書く
Cutterでテストを書くには、test_XXX
という名前の関数を定義します。
CUT_EXPORT void test_equal(void)
{
}
このようにすると、Cutterがテストを実行するときに、定義されたテストをtest_XXX
自動検出して実行します。
画像を比較するassertionを追加する
画像の場合、Cutterのテストに画像を比較するためのassertionを追加するだけです。
gdkcut_pixbuf_assert_equal
というのがそのためのassertionです。1
CUT_EXPORT void test_equal(void)
{
GdkPixbuf *expected, *actual;
expected = load_fixture_image("base.png");
actual = load_fixture_image("copy.png");
gdkcut_pixbuf_assert_equal(expected, actual, 0);
}
ここでのポイントはGdkPixbuf*で比較していることです。gdk-pixbufは幅広い画像形式をサポートしているので、読み込みさえできてしまえば、あとはとても簡単ですね。2
期待する結果をexpected
に、実際の処理で得られた結果をactual
として比較することができます。
画像を含むテストを実行する
テストを定義したので実行してみましょう。以下はカレントディレクトリ以下にある test_equal
という名前のテストを実行しています。
% cutter . -n '/test_equal/'
.
Finished in 0.020999 seconds (total: 0.001533 seconds)
1 test(s), 3 assertion(s), 0 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
100% passed
無事テストが通りました。 3
画像差分機能を使って確認する
では、画像に差分がでたときにどう表示されるのでしょうか。 次のような、失敗するテストを書いてみます。
CUT_EXPORT void test_diff(void)
{
GdkPixbuf *expected, *actual;
expected = load_fixture_image("OK.png");
actual = load_fixture_image("NG.png");
gdkcut_pixbuf_assert_equal(expected, actual, 0);
}
実行してみましょう。
% cutter . -n '/test_diff/'
F
===============================================================================
Failure: test_diff
<expected == actual> (0)
expected: <#<GdkPixbuf:0x155f680 colorspace=<#<GdkColorspace: rgb(GDK_COLORSPACE_RGB:0)>>, n-channels=<3>, has-alpha=<FALSE>, bits-per-sample=<8>, width=<100>, height=<100>, rowstride=<300>, pixels=<((gpointer) 0x156fef0)>>>
actual: <#<GdkPixbuf:0x155f6d0 colorspace=<#<GdkColorspace: rgb(GDK_COLORSPACE_RGB:0)>>, n-channels=<3>, has-alpha=<FALSE>, bits-per-sample=<8>, width=<100>, height=<100>, rowstride=<300>, pixels=<((gpointer) 0x1578210)>>>
threshold: <0>
diff image: <test-sample.c-45.png>
test-sample.c:45: test_diff(): gdkcut_pixbuf_assert_equal(expected, actual, 0)
===============================================================================
Finished in 0.184523 seconds (total: 0.014664 seconds)
1 test(s), 2 assertion(s), 1 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
0% passed
確かに失敗しましたが、これだけだとわかりにくいですね。ここでのポイントは diff image: <test-sample.c-45.png>
という行です。
これが、テストに失敗したときに生成される差分画像です。
差分画像を表示すると、4分割されているのがわかります。上半分がテストに使用した画像です。左が期待する結果、右が実際の結果の画像です。 下半分が違いを示す差分です。重ね合わせた違いがわかりますね。
gdkcut_pixbuf_assert_equalの第三引数は何なのか
ここまでで、Cutterで画像を使ったテストを書けるようになりました。でもgdkcut_pixbuf_assert_equalには第三引数があります。 これは何なのでしょうか。
リファレンスマニュアルを見てみましょう。gdk-pixbufサポート付きの検証には、「ピクセルの違いを検出するために使われるしきい値」とあります。
ピクセル値の違いが指定した範囲に収まれば、それは同じ画像であるとみなすということです。
では、thresholdをうまく使ってだいだい同じ画像とみなせるならテストを通るようにする、というのをやってみましょう。
例として、元画像を gdk-pixbuf-scale-simpleを使って縮小した画像同士を比較してみることにします。
gdk-pixbuf-scale-simple
は画像を縮小する方法をいくつか選択することができます。ここでは、GDK_INTERP_BILINEAR
とGDK_INTERP_HYPER
でそれぞれ縮小したサンプルでテストします。
まずは、thresholdを0にしてテストしてみましょう。
% cutter . -n '/test_bilinear_and_hyper/'
F
===============================================================================
Failure: test_bilinear_and_hyper
<expected == actual> (0)
expected: <#<GdkPixbuf:0xa40280 colorspace=<#<GdkColorspace: rgb(GDK_COLORSPACE_RGB:0)>>, n-channels=<3>, has-alpha=<FALSE>, bits-per-sample=<8>, width=<80>, height=<80>, rowstride=<240>, pixels=<((gpointer) 0xa50eb0)>>>
actual: <#<GdkPixbuf:0xa402d0 colorspace=<#<GdkColorspace: rgb(GDK_COLORSPACE_RGB:0)>>, n-channels=<3>, has-alpha=<FALSE>, bits-per-sample=<8>, width=<80>, height=<80>, rowstride=<240>, pixels=<((gpointer) 0xa56a70)>>>
threshold: <0>
diff image: <test-sample.c-81.png>
test-sample.c:81: test_bilinear_and_hyper(): gdkcut_pixbuf_assert_equal(expected, actual, 0)
===============================================================================
.
Finished in 0.022689 seconds (total: 0.007011 seconds)
2 test(s), 5 assertion(s), 1 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
50% passed
縮小方法が違うので、テストが失敗しました。差分画像を見てみましょう。
これくらいなら、まぁ許容範囲だなぁという気もするので、thresholdを調整します。
CUT_EXPORT void test_bilinear_and_hyper_threshold(void)
{
GdkPixbuf *expected, *actual;
expected = load_fixture_image("NG-bilinear.png");
actual = load_fixture_image("NG-hyper.png");
gdkcut_pixbuf_assert_equal(expected, actual, 30);
}
では、thresholdを30にしたテストを実行してみます。
% cutter . -n '/test_bilinear_and_hyper_threshold/'
.
Finished in 0.016168 seconds (total: 0.002432 seconds)
1 test(s), 3 assertion(s), 0 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
100% passed
テストが通りましたね。これで、NG-bilinear.png
とNG-hyper.png
程度の違いは許容するテストを書くことができました。
まとめ
今回はCutterというテスティングフレームワークの画像差分機能を使って簡単にテストを書く方法を紹介しました。 Cutterにはほかにもテスト環境を便利にする機能があります。まだCutterを使ったことがない人はチュートリアルからはじめるとよいでしょう。詳しく知りたい人はリファレンスマニュアルを参照してください。
GitHubに今回使用した サンプルのリポジトリ を置いてあるので、git cloneして手元でも動かして試すことができます。
git clone https://github.com/kenhys/cutter-with-image-test.git
動作を試すには、次のコマンドを実行してください。
% ./autogen.sh
% ./configure
% make
% cutter .