ImageMagickでUIテストの画面崩れのデグレチェックをCIで自動化するツールをつくってみた

こんにちは。てぃろです。

以前こんな記事を書きました。

その中で画像の比較チェックをする方法としてBeyond Compareを使う方法を進めていたのですが、様々な要因から使うことができなかったため別の方法を考えていました。

そこで、今回はImageMagickを使った方法を採用したのでこれを解説します。特に今回はCIでデグレを自動検知することに使えるようにするための工夫を入れています。

最初に結論としてどのようなスクリプトを作ったかをお見せします。

#!/bin/bash

# 終了コードの定義
EXIT_CODE=0

# 正解スクショ
MASTER=${UITEST_RESULT_PATH}/master

# テスト結果
TESTRESULT=${UITEST_RESULT_PATH}/result

# テスト結果のスクショのファイル名を取得
SCREENSHOTS=(`ls -t1 ${TESTRESULT} | sed -e 's/ /?/g' | tr '\n' ' '`)

# 差分画像の保存先
DIFF=${UITEST_RESULT_PATH}/diff/`date "+%Y%m%d%H%M%S"`
mkdir -p ${DIFF}

for i in ${SCREENSHOTS[@]}; do
  # ファイル名の復元
  FILE_NAME=`echo ${i} | sed -e 's/?/\ /g'`
  DIFF_FILE_NAME="diff-${FILE_NAME}"

  # 画像比較実行
  compare -verbose -metric AE "${MASTER}/${FILE_NAME}" "${TESTRESULT}/${FILE_NAME}" "${DIFF}/${DIFF_FILE_NAME}"

  # 差分発見時(compareの終了コードが0以外のとき)には終了コードを変更
  if [ $? -ne 0 ]; then
    EXIT_CODE=1
  fi
done

exit ${EXIT_CODE}

やっていることは非常に単純です。

  • 正解となる画像を用意しておき、
  • テスト結果のファイルひとつひとつと正解画像を比較して差分を取得し、
  • 差分の結果に応じて、終了コードを出し分ける

ということだけです。

上記スクリプトは見やすさ等のため主な処理のみを残して副次的な処理は削除していますのでそのままでは動作しないと思われます。引用される場合には、ご自身の環境に合わせて適切に修正をしてください。

以下ではこのスクリプトを作るまでの考え方やちょっとしたTipsを書いていきます。

やりたいことは、画像差分があることを検知しその差分を確認できるようにすること

前の記事にも書いた通り、今回このスクリプトを作るモチベーションはUIテストで出力したスクリーンショットから画面崩れやデグレを機械的に発見したい、ということです。

UIテストはその実行だけで非常に時間がかかるものです。今回はiOSアプリを対象にしていますが、それでも最低限7機種(ポイント別に分類した結果)でテストすることが必要です。

それだけの数のスクリーンショットをすべて人の目で見てチェックしてデグレを見つけるというのは現実的ではありません。特に現在のようにリリースサイクルが非常に短くなっているとなおさらです。

そこでスクリーンショットからデグレを見つけることを自動化したいのです。

スクリーンショットの画像比較に使えそうなアプリは前の記事で紹介しましたが、結局CIに組み込めないことでUIテストの完全な自動化ができないことやソフトウェアライセンスの管理やコストが必要になることから、最終的に採用に踏み切れませんでした。

そこで再調査し、以下で紹介するImageMagickというコマンドを利用することにしました。

詳しくは後述しますが、ImageMagickを使えばスクリーンショットの差分検知を自動化することができます。

画像を比較し差分を強調表示できるコマンド:ImageMagick

ImageMagickは画像を扱うツールです。これにはcompareというコマンドがあり、2つの画像を比較しそれらの差分を強調した画像を生成するということが可能です。

その使い方は非常に簡単で、以下のようにコマンドを打つだけでOKです。

compare original.jpg new.jpg diff.png

こうすると、original.jpgとnew.jpgの差分を強調した画像としてdiff.pngを生成することができます。生成画像の例は公式ドキュメントにありますので、詳しくはそちらをご覧ください。

CIで大量の画像に対して差分検知することを考えたときには、このコマンドを一つ一つ実行すれば良さそうに思われます。

しかし、ここでいうdiff.pngは差分があってもなくても生成されてしまうので、画像生成という観点から差分があるなしを検知することができそうにはありませんでした。

ただし、後述するように終了コードに着目すると差分検知は可能でした。

画像差分があったときにデグレ検知としてCIジョブを失敗させる

前述した通り、今回やりたいことはスクリーンショットの差分検知を自動化です。

これをCIで実現するには具体的にどうすればよいでしょうか?CIで差分を検知するとは、より具体的にいうと差分が発見されたときにジョブを失敗させることです。そしてジョブが失敗となるのは実行されたスクリプトの終了コードが0以外であるときです。

つまり、以下のことが実現できれば画像差分のあるなしでCIジョブの成功失敗をコントロール可能です。

  • 画像差分が発見されたときには、終了コードを1にする
  • 画像差分が発見されなかったときには、終了コードを0にする

実はImageMagickのコマンドであるcompareは、生成した画像に差分が存在していた場合には終了コードは1となり、差分がない場合は0になっています。

つまり、上述した差分画像生成のコマンドの終了コードを確認することによってジョブ全体を失敗させるべきかをコントロール可能となるのです。

画像の差分はピクセル単位で検知しよう

そもそもUIのデグレを検知したいというとき、デグレとは何をもってデグレというのか?を定義する必要があります。

画像比較に使用するコマンドであるcompareには、画像比較アルゴリズムがたくさん実装されていて、どれか一つのアルゴリズムを指定して使用していくことになります。処理としては、それぞれのアルゴリズムで全く同一の画像であると判断されたときに終了コードが0になる、という形になります。

今回はUIが崩れていることを確認したいので、ピクセル単位で何かしらのズレがあるときにそれをデグレとして検知したいと思います。そのためにはアルゴリズムのAEを使用しましょう。

その他どのようなアルゴリズムが使えるかは公式ドキュメントをご覧ください。

他のアルゴリズムについては別途調べていただければわかりますが、輝度や明度、彩度を考慮していたり、回転に頑健であるなど、様々な悪条件を考慮した画像比較アルゴリズムだったりします。今回は画像の中から特定のオブジェクトを見つけるとか、実は回転してるけど同じ画像を見つけたいとか、そういった要件ではありません。

テストで全く同じ条件で同じ画像が出力されるはずであるとき、本当にズレがないことを確認することが目的なのでピクセル単位のズレを検知することが最も要件にあったアルゴリズムだと言えるでしょう。

ただし、UIにリッチなアニメーションを使っていた場合、タイミングによっては多少のズレが発生する可能性も否定できません。そういったときには-fuzzというオプションの使用を検討しましょう。

-fuzzを使うことで見た目にわからない程度の差分(つまりデグレ検知の観点では無視して良い差分)を無視してくれるようになります。詳しくは以下の記事を参照ください。

ImageMagickをM1 Macにインストールする

ではここからは、実際にImageMagickでスクリプトを作成するためのTipsを紹介していきます。まずインストールについてです。

M1 Macの場合には、公式ドキュメントにあるとおりにインストールしようとしてもCPUのアーキテクチャが違うのでそのままインストールできません。

以下のようにarchコマンドを追加する必要があります。

arch arm64 brew install imagemagick

しかし今回私の環境ではこれで最初はインストールに失敗しました。

失敗した原因は、使用しているライブラリもM1 Macのアーキテクチャでビルドされていなかったからのようです。どのライブラリが対象であるかは各々の環境で違うと思いますが、エラー文で出力されますので、よく読めばわかると思います

そこで対象となっていたライブラリを一つ一つ上記のarchコマンドをつかってM1 Macのアーキテクチャ用にインストールしてみてください。

それらの個別のインストールを終えてから、再度Imagemagickのインストールを実行すると成功しました。

空白入りのファイル名を指定して画像比較を実行するには?

fastlaneでUIテストを実行した場合のスクリーンショットのファイル名は、先頭にシミュレーターの名前が付与されます。(XXXXXはテストコード内で指定可能な文字列)

iPhone 8-ScreenShot_XXXXX.png

このようにiPhoneと8の間に空白が入ってしまいます。この空白がくせ者です…!ファイル名をlsをつかって取得しファイル名のリスト(配列)を保持しておきたいのに、空白で区切られてしまうことでファイル名が適切に取得できないのです。

そこで今回は以下のようにsedを使って空白を別の文字に置換させるように処理を追加しました。

SCREENSHOTS=(`ls -At1 ${TESTRESULT} | sed -e 's/ /?/g' | tr '\n' ' '`)

パイプのあとの3つ目の処理は改行を空白に置き換えています。

lsを上記のコマンドで実行すると1件1行で表示されます。つまり最初はファイル名の区切り文字は改行コードであるわけです。これをしておかないと、ファイル名の区切り文字が空白になってしまうので、sedで空白が置換されてしまうことになります。

そこで、最初は改行コードにしておき、あとから空白に置き換えることで、ファイル名同士の区切り文字を空白とすることができます。

今回はBashの処理で工夫しましたが、別の方法として設定で配列の区切り文字を変更することもできるようです。CIで実行することもあって自分以外も使う環境なので環境に影響を与えないこのようなやりかたをしましたが、今回のやり方が回りくどいというような人は設定変更のほうを試してみてはいかがでしょうか。

参考:Bashで区切り文字を変更する

加えて、compareコマンドを実行するときにも一工夫必要です。

もともとのファイル名は空白が入っていますので、compareコマンドでファイル名を指定する場合には空白の入ったそのファイル名を指定する必要があります。

そのために、別の文字で置換された空白をもとに戻すことと、compareコマンドで指定するときにファイル名を”(ダブルクォート)で囲う必要があります。

詳しくは上記のスクリプト本文をご覧ください。

最後に

今回はUIテストの画面崩れのための画像比較の自動化に関するTips集でした。

こういったケースは結構需要がありそうですが、これというツールが見つけられなかったのでスクリプトを書くに至りました。

会社の環境制約でテストに関わるSaaSサービスなどがあまり使えないので、今の最新サービスでこのあたりがカバーされているのか気になるところではあります。車輪の再発明みたいなことをしてたならちょっと悲しいです…。

とはいえ、お手軽なスクリプトではあると思うので、同じように画像比較をサクッとやりたい人がいてその方の参考になればうれしいです。