FactoryBotのtraitの使用上注意するところがあるので、Goodcheckに叩き込んでおく


はじめに

この記事は、本当にピンポイントでの、自分用の覚書になります。(記事に関連するタグのもの技術をこれからはじめたい、という方目線では書いていません)

ただし、こんなケースでは、何かお役に立てることがあるかもしれません。

  • コードを書く際に、フレームワーク標準ではなくカスタムクラスを使うケースがある
  • テストの時にだけ使うクラスがある
  • 上記のいずれも、利用上注意しないといけないことがある...
  • たまに使うと、その注意点を忘れてしまい、バグを踏んだりテストで失敗したりする

背景 / 困ったこと

わたしは個人でRedmineのプラグインを書いています。
細かいことは省きますが、プラグインはRedmine本体が無いと動きませんので、テスト自体もRedmineを前提にして書きます。
ところで、Redmine本体のバージョンアップが入ると、プラグインのテスト側にも影響が出るので、毎回テストが壊れたところを潰していく...といったことを乗り越えて生きています。

以前は、ほぼサーバサイドの処理中心で、テストもユニットテストやコントローラーのテストで済ませていましたが、だんだんとAjax利用、フロント側での処理を行うことが増えてきたので、Redmine3.xに対応をし出した頃から、Capybaraを使ったテストも入れるようになりました。

その場合、ブラウザ利用になるので、どうしてもログインフォームを踏んで認証を経ていろんな画面に遷移するという処理をします。
なんとか対応は行なっているのですが、Redmine本体の認証系にも調整が入ると、どうしても、このあたりのCapybaraのテストも影響を受ける場合があります。

使っているFactoryBotのコード

Capybaraでのテストの際、「まずユーザを作る」「作ったユーザに権限を付ける」「指定の画面にアクセスさせる」といった作業が必要になります。

できるだけ綺麗な状態でためすように、Redmineのテスト用にfixturesで作成されるユーザは使わずに、FactoryBotで作成するようにしています。


# こんな感じで使います
user = FactoryBot.create(:user, :password_same_login, ....)

コードの中段で、"trait" がありますが、これは、単純に User作成するだけでなく、ユーザのパスワードを固定ではなく、ユーザのログイン名と同じものをセットするという処理を行うオプションみたいなものです。


# frozen_string_literal: true

FactoryBot.define do
  factory :user do |u|
    # sequence -> exp. :login -> user1, user2.....
    u.sequence(:login)     { |n| "user#{n}" }
    u.sequence(:firstname) { |n| "User#{n}" }
    u.sequence(:lastname)  { |n| "Test#{n}" }
    u.sequence(:mail)      { |n| "user#{n}@badge.example.com" }
    u.language             { 'en' }
    # password = foo
    u.hashed_password      { '8f659c8d7c072f189374edacfa90d6abbc26d8ed' }
    u.salt                 { '7599f9963ec07b5a3b55b354407120c0' }

    # login and password is the same. (Note: login length should be longer than 7.)
    trait :password_same_login do
      after(:create) do |user|
        user.password = user.login
        user.auth_source_id = nil
        user.save
      end
    end

    trait :as_group do
      type { 'Group' }
      lastname { "Group#{n}" }
    end
  end
end

実は、Redmineのユーザ登録時、パスワードは8文字以上の長さにするバリデーションがデフォルトで入っています。
ユニットテスト系ではログインフォームを踏まないので、パスワードの長さでエラーになったりはしないのですが、ブラウザを使ってテストする際は、ユーザ作成&パスワードを8文字以上に設定していないと、(たしか)ユーザ作成時にロールバックしたりといったエラーが起こり、作成したつもりのユーザが作成されていないので失敗、ということがありました。

ということで、自分自身で FactoryBotの設定のところに パスワードは8文字以上の長さにするように! とコメントを入れているわけです。

エラーが発生する!

さて、久々に先日プラグインに機能を追加ました。もちろんテストもくっつけます。
実行上問題なさそうなE2Eテストもつけたので、プルリクエストを出してCIでテストを回します。

ですが、失敗している!
CircleCIの画面的にはこんな感じです。テストのところで、作成したUserのidをパラメーターにセットしてリクエストを出すのですが、それが無い...とのこと。

さて、なぜこうなったのか見当が付きません。

CircleCIのコンテナにsshログインして調べる...

ローカルPCでは、Capybaraの準備ができていないので、すぐの対応はちょっと面倒なので、こういうときはCircleCIのビルドを、SSHでログイン可能な指定付きでリトライします。

この設定でリトライすると、ビルドも実施されますが、しばらくの間実行した環境(コンテナ)の中にローカルPCからsshでアクセスして、中身を確認することができます。


(事前に自分の公開鍵をCircleCIに登録する必要があります)

中に入って直にrspecのコマンドを渡してテストを回して、合わせてログを眺めながらの確認...。

そしてわかったのが、「テストに使うユーザ作成に失敗している&パスワードの長さの問題でコケている」でした。

あれれと思ってよく見ると、以下のように作っている。。。


# trait: password_same_login なので、パスワードも "manager" になっちゃう
user = FactoryBot.create(:user, :password_same_login, login: 'manager')

自分でFactoryBotのコメントに書いてある「パスワードは8文字以上にしないとダメ!」というものを、わすれちゃってたからでした....。

人間は忘れるのでGoodcheckに書き出して心置きなく忘れるようにする

めでたくCircleCIの中に入ってデバッグして理由がわかったので、修正したらテストは通りました...。
結果的に自分で定義しておいたFactoryBotの設定で、自分が使い方を忘れていた、ということになります。
前回の大きめの修正がかなり前だったので、この辺の注意点をすっかり忘れていたわけです。

Goodcheckを使ってみる

さて、わたしはテストにはCircleCI, コードのスタイルチェックにはSiderを使っています。
もちろん手元でRubocopを回すので、わりとプルリクエストを上げてからのSiderのチェックで引っかかることは減ってきました。

その代わり、どうしても今回のような「使用上の注意」は抜けてしまいます。
そのために、エラー確認してコミット何回かして試して、あげくにはCircleCIにssh login...というのは非常に大変です。(楽しいですが)

ということで、「使い方を注意する処理を書いたら、Siderに指摘してもらおう」ということで、今回はGoodcheckを使うことにしました。

もしかしたら、もう今後メンテしないかもしれませんが、心置きなく忘れるために登録はしておくと、気分的にも安心です。

Goodcheckは、パターンマッチベースで設定を書いておくと、コードに該当するパターンを見つけたら指摘メッセージを出してくれるツールです。
(Siderの独自解析ツールの一つです)

goodcheck.ymlのなかみ

こんな感じで設定します。
ルールは、rspecのファイル中に、":password_same_login," の文字列があったら確認メッセージを出す、というもの。


rules:
  - id: akiko.redmine.user_password
    pattern: 'FactoryBot.create(:user, :password_same_login, login:' 
    message: |
      FactoryBotで trait: ":password_same_login" を使ってログイン名とパスワードを同じユーザを作ってテストする場合は、ログイン名( = パスワード)の長さは8以上になるようにして下さい。

      "admin" や "manager" では8文字未満のため失敗します。(Status GreenでもCircleCIのログも確認してね!)
    glob:
      - "**/*_spec.rb"

  - id: akiko.redmine.spec.FactoryBotは省略しない
    pattern: " create(:"
    message: |
      FactoryBotを使う場合は、FactorryBot.create(:symbol, .... ) のようにFactoryBotを省略しないで下さい。

      慣れていないと、ActiveRecordのcreate() メソッドと混乱しやすいので。
    glob:
      - "**/*_spec.rb"

標準のOSSのLinterでは、わたしが定義しているマイルールなんてものは知りませんので、こうして自分で予防線を張ることにしました。

ちゃんと指摘されるのか実験

ということで、やってみました。
修正したプルリクエストを出すと、Sider側で解析の際に、Goodcheckでもスキャンをしてくれます。

今回は、非常にシンプルな設定でひっかけてみましたが、以下のように指摘が上がってきました。

今回は、この指摘に照らし合わせて、"test-manager" がパスワードになりますが、8文字以上の基準を満たすので問題なし。
コードを修正しなくとも、確認の意味で「Close」を押せばOKになります。

実際、CircleCI側のテストもこのプルリクエストの修正でOKになりました。

まとめ / 個人開発でも忘れるものは忘れる!

非常に駆け足で、勢いで書いた記事ですが、ようは「個人開発でも忘れるものは忘れる」というこです。
そして、「この原因なんだっけ...?」というのも時間がかかったりします。
結果的にデバッグや調査に夢中になるあまり、家事育児がおろそかになることも少なくありません...。(わたしはこんなことしていられない....)

コード本体は、皆さんが公開して下さるベストプラクティスやエディタの機能に従って、ある程度綺麗になったり上手く書けるようになってきました。(技術って素晴らしい!)
ただし、その周辺の設定やテストコードなどは、ある意味仕様や環境構築など、人が考えないといけないものが結構詰まっています。

非常に小さな例ですが、こんなシーンでも使えるかな...ということで、Goodcheckの紹介も兼ねて書いてみました。