ククログ

株式会社クリアコード > ククログ > Cutterで画像を使ったテストを書くには

Cutterで画像を使ったテストを書くには

はじめに

今回は弊社が中心となって開発している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> という行です。 これが、テストに失敗したときに生成される差分画像です。

Cutterによる差分画像の例

差分画像を表示すると、4分割されているのがわかります。上半分がテストに使用した画像です。左が期待する結果、右が実際の結果の画像です。 下半分が違いを示す差分です。重ね合わせた違いがわかりますね。

gdkcut_pixbuf_assert_equalの第三引数は何なのか

ここまでで、Cutterで画像を使ったテストを書けるようになりました。でもgdkcut_pixbuf_assert_equalには第三引数があります。 これは何なのでしょうか。

リファレンスマニュアルを見てみましょう。gdk-pixbufサポート付きの検証には、「ピクセルの違いを検出するために使われるしきい値」とあります。

ピクセル値の違いが指定した範囲に収まれば、それは同じ画像であるとみなすということです。

では、thresholdをうまく使ってだいだい同じ画像とみなせるならテストを通るようにする、というのをやってみましょう。

例として、元画像を gdk-pixbuf-scale-simpleを使って縮小した画像同士を比較してみることにします。

gdk-pixbuf-scale-simpleは画像を縮小する方法をいくつか選択することができます。ここでは、GDK_INTERP_BILINEARGDK_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

縮小方法が違うので、テストが失敗しました。差分画像を見てみましょう。

BILINEARとHYPERの差分画像

これくらいなら、まぁ許容範囲だなぁという気もするので、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.pngNG-hyper.png程度の違いは許容するテストを書くことができました。

まとめ

今回はCutterというテスティングフレームワークの画像差分機能を使って簡単にテストを書く方法を紹介しました。 Cutterにはほかにもテスト環境を便利にする機能があります。まだCutterを使ったことがない人はチュートリアルからはじめるとよいでしょう。詳しく知りたい人はリファレンスマニュアルを参照してください。

GitHubに今回使用した サンプルのリポジトリ を置いてあるので、git cloneして手元でも動かして試すことができます。

git clone https://github.com/kenhys/cutter-with-image-test.git

動作を試すには、次のコマンドを実行してください。

% ./autogen.sh
% ./configure
% make
% cutter .
  1. load_fixture_imageという画像を読みこんでGdkPixbuf*として返す関数を定義してあることにします。本質的でないので詳細は割愛します。

  2. gdk-pixbuf-query-loadersを実行するとサポートされている画像形式を確認できます。

  3. 期待する結果と、実際の結果にファイル名だけ違う画像を使ったのであたりまえなのですが。