こんにちは @color_box です。 仕事からアイディアを得てgemを作ったのでそれについて書きます。
rspec-all_records_validator
rspec-all_records_validator というgemを作りました。DB内の全レコードに対してvalidationを実行するgemです。
これを導入するとある種のバグをE2Eテストで検出可能になります。RailsとRSpecが入っているプロジェクトを前提としています。
作った背景
仕事で遭遇したとあるバグが、このgemを作成した背景にあります。
Active Recordのバリデーションにおいて、関連の個数を確認するようなバリデーションはすり抜けの可能性があります。
例として下記のようなクラスを仮定します。Human
オブジェクトはdog
を一つ以上関連として持つべき、というバリデーションが設定されています。
class Human < ApplicationRecord has_many :dogs validates :dogs, length: {minimum: 1} end class Dog < ApplicationRecord belongs_to :human end
この時、下記のようなコードを実行するとbreeder
のレコードはinvalidになります。
dog = Dog.new breeder = Human.create!(dogs: [dog]) dog.reload => #<Dog id: 1, human_id: 1> dog_owner = Human.new dog_owner.dogs = breeder.dogs dog_owner.save! dog.reload => #<Dog id: 1, human_id: 2> breeder.reload.invalid? => true
breeder
からdog_owner
へdogs
オブジェクトが移動してしまっており、breeder
はdogs
を持たなくなるためinvalidになります。
invalid であるにも関わらずbreeder
のvalidationは実行されないためそれに気づけません。
基本的にDBの中に永続化されているレコードは全てvalidであるはずなので、このようにinvalidなデータがDBに残り続けると別の箇所でバグを発生させます。
テスト実行後にバリデーションを実行できると、このような状況を防ぐことが可能です。rspec_all_record_validatorはそのようなバリデーション実行を簡易に追加できるgemです。E2Eテスト終了時、全レコードのvalidationをチェックすることでこの手のバグを検知しています。
ただ、E2Eテストの実行後に全てのレコードのvalidationを実行するため、当然ビルド時間が増えます。そういった課題は今後改善していければと思います。
まとめ
今回、仕事での気づきを元に作成したgemについて、その背景や作った理由などを書かせていただきました、この記事やgemがどなたかのお仕事の役に立てば幸いです。