reg-cli でビジュアルリグレッションテストを小さく始める

この記事は Misoca+弥生 Advent Calendar 2019 6日目の記事です。


モバイルアプリのビジュアルリグレッションテストを見やすくしたい

iOS では fastlane の snapshotAndroid では Spoon で各画面の画像を撮り、各アプリバージョンのスクリーンショットを Git リポジトリで管理し、Github 上または Github Desktop などで確認できるようにしている。

モバイルでのスクリーンショットの取得に関しては以下の記事を参照

tech.misoca.jp

一方 Web アプリケーションでは Capybara でスクリーンショットを撮り、reg-suit で比較画像の生成・S3 への publish、Slack への通知などを行ってビジュアルリグレッションテストを実現していて、社内で reg-suit の一定の実績があった。

Web アプリでのビジュアルリグレッションテストの取り組みについては以下の発表によくまとまっている。

slides.com

モバイルアプリでも reg-suit で比較結果を生成すれば、確認方法も統一できるし、reg-suit のきれいで一覧性のある diff 画面で確認できれば効率的だろうと考えてやってみた。

小さくはじめて最速で成果を評価したい

reg-suit の動作は以下のフェーズに分かれている

  1. 正解画像の取得
  2. 正解画像と手元の画像の比較
  3. 比較結果と次回正解画像の publish

reg-suit の代表的な使い方をした場合、
「1. 正解画像取得」では、reg-suit から使いやすいブランチ構造にあわせる必要があり、 「3.比較結果の publish」では S3 のバケットを用意したりする必要がある。

今やりたいのは既にある画像の比較だけなので、「2. 正解画像と手元の画像の比較」で十分な効果が得られそうだと考えていた。
そもそも、reg-suit を採用するかどうかチームメンバーに見てもらってからだな、と思っていたのでなおさら重い作業や運用を考えることは省きたかった。

「2. 正解画像と手元の画像の比較」では、reg-cli というコマンドラインツールが使われている。
そこで、今回は小さく始めるために reg-suit ではなく reg-cli を直接使うことにした。

reg-cli でのビジュアルリグレッションテスト

上記のとおり既に Git リポジトリに各バージョンのスクリーンショットは用意してあるので、こんな感じで生成した。

# 作業フォルダの準備
rm -rf .reg
mkdir .reg

# before の画像セットの取得
$ git checkout <beforeのリビジョン>
$ cp -R ./capture .reg/before
$ git checkout -

# after の画像セットの取得
$ git checkout <afterのリビジョン>
$ cp -R ./capture .reg/after
$ git checkout -

# reg-cli の実行
yarn add reg-cli --no-progress
yarn -s run reg-cli .reg/after .reg/before diff -R index.html -S -M 0.05

これで index.html ファイルが生成され、こんな感じで比較結果を得られる。

f:id:suer:20191201014324p:plain:w500

Jenkins で半自動化

メンバーに共有したところ

f:id:suer:20191202092641p:plain:w200

こういうコメントをもらえたので、方向性は良さそうという手応えを得ることができた。

この段階では自分の手元でやっていたのだが、次の段階として誰でも比較結果を作れるように Jenkins で半自動化することにした。

とはいっても上記で実行しているコマンドを Jenkins にやらせるだけなので・・・

ビルドパラメータで前後のリビジョンを指定できるようにして

f:id:suer:20191201173120p:plain:w400

こんな感じで上記コマンドをスクリプトとして追加していって *1

f:id:suer:20191201173256p:plain:w400

比較結果を保存対象にしただけ。

f:id:suer:20191201231956p:plain:w400

これでコミットハッシュを指定すれば任意の比較結果を得ることができるようになった。

f:id:suer:20191201173505p:plain:w400

まとめ

やりたいことにフォーカスして、ベイビーステップで振り返りながら進めるようにするには、reg-suit に対する reg-cli のように一段プリミティブなところから始めるのがよさそうだなと思った。

ちなみに今回は既に画像取得できてるところから始めたが、本当は画像取得の部分が一番たいへんなんだよなぁ。


明日、Misoca+弥生 Advent Calendar 2019 の7日目のエントリは @mallowlabs が「RSpec の話」を書くようです。楽しみですね!

*1:細かい話として reg-cli の実行は差分があるときに 0 以外のステータスコードを返してビルドが失敗扱いとなってしまうのを防ぐために yarn コマンドに || true をつけて回避する必要がある

Redmine プラグインで controller を load すると別のプラグインに影響がある

Module#prepend での本体のヘルパーを上書き

従来 Redmineプラグインで本体の動作を変えたい場合は alias_method_chain を使いましょうということになっていた。
Redmine 4 系から Rails 5.x ベースとなって alias_method_chain が廃止されてからは Module#prepend を使いましょうということになった。

prepend を使えば既存の動作を上書きすることができるようになる。

# 本体のモジュール
module ProjectsHelper
  def foo
    'foo'
  end
end

# プラグインで追加するパッチ
module ProjectsHelperPatch
  def foo
    super + 'bar'
  end
end

# プラグイン内でこれを実行
ProjectsHelper.prepend(ProjectsHelperPatch)

# 本体のコントローラ
class ProjectsController
  include ProjectsHelper
end

# ProjectsHelperPatch#foo が呼ばれる。 super は ProjectsHelper#foo を呼ぶ。
puts ProjectsController.new.foo # foobar

ロードする順番を変えてみる

順番を変えてみる。

# 本体のモジュール
module ProjectsHelper
  def foo
    'foo'
  end
end

# 本体のコントローラ(prepend よりも前にもってきた)
class ProjectsController
  include ProjectsHelper
end

# プラグインで追加するパッチ
module ProjectsHelperPatch
  def foo
    super + 'bar'
  end
end

# プラグイン内でこれを実行
ProjectsHelper.prepend(ProjectsHelperPatch)

# ProjectsHelperPatch#foo が呼ばれず、 ProjectsHelper#foo が直接呼ばれる
puts ProjectsController.new.foo # foo

ProjectsController の定義を prepend よりも先に移動した。
ProjectsController には ProjectsHelperPatch が差し込まれる前の ProjectsHelper を include していることになる。
結果 ProjectsHelperPatch#foo は呼び出されず ProjectsHelper#foo が直接呼び出されるので ProjectsHelperPatch によって動作を上書きできていない。

RedmineRedmine プラグインのロード順

Redmine プラグインRedmine 本体コードの app/ 以下よりも先にロードされる。(config/initializers/30-redmine.rb) なので、プラグインのなかで prepend すれば app/ 以下のコードを上書きできる。

プラグインプラグインのフォルダ名順にロードされる。

プラグインで Controller をロードすると後続で helper を prepend しても有効にならない

プラグインでこういうコードを書いたとする。

ProjectsController.prepend(ProjectsControllerPatch)  # (1)

別のプラグインで ProjectsController が include している ProjectsHelper を prepend していたとする。

ProjectsHelper.prepend(ProjectsHelperPatch) # (2)

(2) の prepend は (1) より先か後かで有効になったりならなかったりする。
(1) が先に実行された場合はこの時点で include される helper には (2) は prepend されていないので ProjectsController には (2) の動作が付加されない。

フォルダ名順にロードされるので、フォルダ名によって動いたり動かなかったりする。
(2) を開発していてロードされない原因が関係ない (1) のプラグインのせいだと気づくのは無理があるので非常にやっかい。

まとめ

Redmine プラグインで Controller をロードしてはいけない。
わたしです → https://github.com/suer/redmine_recent_project_accesses/commit/24c5d3939385d8e1ce966a750d12749b9d7902b9#diff-242d0b4ac43f99fbb7c9023c1aca7166R4

rsync でタイムスタンプの違いは無視したい

rsync するときタイムスタンプが違ってもファイルに違いがなければコピーされないようにしたい。

$ rsync --checksum ...

ファイルのチェックサムが一緒ならそのファイルはコピーしなくなる。

ファイルがたくさんある場合は checksum の計算だけで時間と CPU 負荷がかかって大変なことになるので注意。

プロセスがどこにログを吐いているか調べる

知らないフレームワークを使っている Web アプリがどこにログを吐いているか調べるために、そのフレームワークの設定方法を調べるより

$ lsof -p プロセスID

とかしてそれっぽいファイルを探すほうが速かった。

Visual Studio Code をコマンドラインで起動する

確認バージョン

設定

  1. Visual Studio Code を起動する
  2. Cmd+Shift+P (Windows の場合は Ctrl+Shift+P) でコマンドパレットを開く
  3. shell と入力すると Shell Command: Install 'code' command in PATH というのが出てくるのでこれを選択する f:id:suer:20190612202941p:plain:w400
  4. Success と表示されたら成功

コマンド

$ code [パス]

で起動できる。

パスの部分にはファイルのほかディレクトリも指定できるので、プロジェクトトップに cd してから

$ code .

とかしている。

Google スライドの幅の設定

Google スライドでスライドを作ったらデフォルトが PowerPoint より横長だったので変更する。

特にデフォルトのままで PowerPoint からインポートすると違和感が半端ない。

「ファイル(File)」メニュー > 「ページ設定(Page setup)」

f:id:suer:20190611235146p:plain

標準(4:3)を選択すると PowerPoint からインポートしても違和感がなくなった。 幅が広いな〜と思ったら設定してみるといいかもしれない。

iPhone のブラウザで 1Passwordから ID/パスワードを入力できるようにする設定

初回起動時に有効にするか聞かれたのかもしれないけど設定されてなくてどこから設定すればいいか困ったのでメモ。

確認バージョン

  • iOS: 12.3.1
  • 1Password: 7.3.2

手順

  • パスワードとアカウント
  • パスワードを自動入力
  • 1passwordを選んで有効化

f:id:suer:20190610202511p:plain:w200

ブラウザでログインフォームにフォーカス時にキーボードの上に「パスワード」が表示され、タップすると 1Password から入力できるようになる。

f:id:suer:20190610202711p:plain:w200

↓「パスワード」をタップ

f:id:suer:20190610202925p:plain:w200