- テストデザイン
- テスト速度
- アールエスペック
テストのベストプラクティス
テストデザイン
GitLabにおけるテストは後付けではなく、第一級市民です。 機能の設計と同じように、テストの設計も考慮することが重要です。
機能を実装するときは、適切な機能を適切な方法で開発することを考え、スコープを管理しやすいレベルまで絞り込みます。 機能のテストを実装するときは、適切なテストを開発することを考えなければなりませんが、テストが失敗する可能性のある重要な方法を_すべて_カバーしなければなりません。
テストヒューリスティックは、この問題を解決するのに役立ちます。 バグがコード内で発現する一般的な方法の多くに、簡潔に対応しています。 テストを設計する際には、既知のテストヒューリスティックをレビューして、テスト設計に役立てましょう。 ハンドブックのテストエンジニアリングのセクションに、役に立つヒューリスティックのドキュメントがあります。
テスト速度
GitLabには膨大なテストスイートがあり、並列化しないと実行に何時間もかかることがあります。 正確で効果的で_、_かつ高速なテストを書く努力をすることが重要です。
テストのパフォーマンスに関して、いくつか留意すべき点があります:
-
instance_double
やspy
よりも高速です。FactoryBot.build(...)
-
FactoryBot.build(...)
と.build_stubbed
は.create
よりも速い。 -
build
,build_stubbed
,attributes_for
,spy
,instance_double
で済むオブジェクトはcreate
にしないでください。 データベースの永続化には時間がかかります! - テストが有効であるために_実際に_必要でない限り、(RSpec の
:js
を通じて)JavaScript を必要とする機能としてマークしないでください。 ヘッドレスブラウザのテストは遅いです!
アールエスペック
RSpecテストを実行するには
# run test for a file
bin/rspec spec/models/project_spec.rb
# run test for the example on line 10 on that file
bin/rspec spec/models/project_spec.rb:10
# run tests matching the example name has that string
bin/rspec spec/models/project_spec.rb -e associations
# run all tests, will take hours for GitLab codebase!
bin/rspec
Guardを使用して変更を継続的に監視し、一致するテストのみを実行します:
bundle exec guard
スプリングとガードを併用する場合は、スプリングの代わりにSPRING=1 bundle exec guard
。
Factory Doctorを使用して、テストに時間がかかる原因となる不要なデータベース操作のケースを見つけます。
# run test for path
FDOC=1 bin/rspec spec/[path]/[to]/[spec].rb
一般的なガイドライン
- トップレベルの
RSpec.describe ClassName
ブロックを1つ使用します。 - クラスメソッドの記述には
.method
を、インスタンスメソッドの記述には#method
を使います。 -
context
を使ってブランチロジックをテストします。 - テストの順番をクラス内の順番に合わせるようにしてください。
- 改行でフェーズを区切り、4フェーズテストのパターンに従うようにしてください。
- ハードコーディングではなく、
Gitlab.config.gitlab.host
。'localhost'
- シーケンスで生成された属性の絶対値に対してアサートしないでください(Gotchas参照)。
-
expect_any_instance_of
やallow_any_instance_of
の使用は避けてください(「Gotchas」を参照)。 -
:each
はデフォルトなので、hooksに指定しないでください。 -
before
、after
のフックでは、:context
にスコープしてください。:all
- 指定された要素に作用する
evaluate_script("$('.js-foo').testSomething()")
(またはexecute_script
) を使う場合は、 その要素が実際に存在することを確認するために、 あらかじめ Capybara matcher (たとえばfind('.js-foo')
) を使ってください。 -
focus: true
、実行したい仕様の一部を切り分けます。 - テストに複数の期待値がある場合は、
:aggregate_failures
を使用してください。 -
空のテスト記述ブロックでは、テストが自明な場合は
it do
ではなくspecify
を使用してください。 - 実際には存在しないID/IID/アクセスレベルが必要な場合は、
non_existing_record_id
/non_existing_record_iid
/non_existing_record_access_level
を使用してください。123、1234、あるいは999を使用すると、CI実行のコンテキストでこれらのIDが実際にデータベースに存在する可能性があるため、もろいです。
適用範囲
simplecov
これらは CI 上で自動的に生成されますが、ローカルでテストを実行するときには生成されません。マシンで spec ファイルを実行するときに部分的なレポートを生成するには、環境変数SIMPLECOV
を設定します:
SIMPLECOV=1 bundle exec rspec spec/models/repository_spec.rb
カバレッジ・レポートはアプリ・ルートのcoverage
フォルダに生成され、ブラウザなどで開くことができます:
firefox coverage/index.html
カバレッジレポートを使用して、テストがコードの 100% をカバーしていることを確認します。
システム/機能テスト
- フィーチャースペックの名前は
ROLE_ACTION_spec.rb
、例えばuser_changes_password_spec.rb
とします。 - シナリオのタイトルは、成功経路と失敗経路を表すものを使用してください。
- 成功裏に」のような、何の情報も加えないシナリオタイトルは避けましょう。
- シナリオのタイトルは、長編のタイトルを繰り返すようなものは避けてください。
- 必要なレコードだけをデータベースに作成
- 幸せな道とそうでない道をテストしてください。
- その他のすべての可能な経路は、ユニットテストまたはインテグレーションテストでテストされるべきです。
- ActiveRecordモデルの内部ではなく、ページに表示されるものをテストしてください。例えば、レコードが作成されたことを検証したい場合、
Model.count
が1つ増えることではなく、その属性がページに表示されることを期待してください。 - DOM要素を探すのはいいのですが、乱用するとテストがもろくなるのでやめましょう。
カピバラのデバッグ
ブラウザの動作を観察して、Capybaraテストをデバッグする必要がある場合があります。
ライブデバッグ
カピバラを一時停止し、ブラウザでウェブサイトを表示するには、仕様でlive_debug
メソッドを使用します。 現在のページは自動的にデフォルトのブラウザで開かれます。 最初にサインインする必要があるかもしれません (現在のユーザーの認証情報が端末に表示されます)。
テスト実行を再開するには、いずれかのキーを押します。
使用例:
$ bin/rspec spec/features/auto_deploy_spec.rb:34
Running via Spring preloader in process 8999
Run options: include {:locations=>{"./spec/features/auto_deploy_spec.rb"=>[34]}}
Current example is paused for live debugging
The current user credentials are: user2 / 12345678
Press any key to resume the execution of the example!
Back to the example!
.
Finished in 34.51 seconds (files took 0.76702 seconds to load)
1 example, 0 failures
注:live_debug
は、JavaScript が有効な仕様でのみ動作します。
:js
spec を表示可能なブラウザで実行します。
CHROME_HEADLESS=0
でスペックを実行してください:
CHROME_HEADLESS=0 bin/rspec some_spec.rb
テストはすぐに終わりますが、これで何が起きているのかがわかるでしょう。live_debug
をCHROME_HEADLESS=0
と一緒に使うと、開いているブラウザを一時停止し、ページを再び開きません。 これはデバッグや要素の検査に使えます。
また、byebug
やbinding.pry
を追加して、実行を一時停止し、テストのステップを踏むこともできます。
スクリーンショット
失敗時に自動的にスクリーンショットを撮るためにcapybara-screenshot
gem を使っています。 CIでは、これらのファイルをジョブのアーティファクトとしてダウンロードできます。
また、以下のメソッドを追加することで、テストのどの時点でも手動でスクリーンショットを撮ることができます。 不要になったら必ず削除してください!詳しくはhttps://github.com/mattheworiordan/capybara-screenshot#manual-screenshots をご覧ください。
カピバラが「見ている」ものをスクリーンショットするために、:js
specにscreenshot_and_save_page
を追加し、ページソースを保存します。
カピバラが “見た “ものをスクリーンショットし、自動的に画像を開くために、:js
仕様にscreenshot_and_open_image
を追加します。
これによって作成されるHTMLダンプにはCSSがありません。 その結果、実際のアプリケーションとはまったく異なる見た目になってしまいます。 CSSを追加する小さなハックがあり、デバッグが簡単になります。
高速ユニットテスト
一部のクラスはRailsからうまく分離されているため、Rails環境やBundlerの:default
グループのgemロードによって追加されるオーバーヘッドなしでテストできるはずです。このような場合は、テストファイルでrequire 'spec_helper'
の代わりにrequire 'fast_spec_helper'
:
- 宝石のロードがスキップされます
- Railsアプリの起動がスキップされる問題
- GitLab ShellとGitalyのセットアップはスキップされます。
- テストリポジトリのセットアップはスキップされます。
fast_spec_helper
また、lib/
ディレクトリ lib/
内にあるクラスの自動ロードもサポートして fast_spec_helper
いますfast_spec_helper
lib/
。 つまり、クラス/モジュールが lib/
ディレクトリlib/
内のコードのみを使用して lib/
fast_spec_helper
いる限りfast_spec_helper
、依存関係を明示的にロードする必要は fast_spec_helper
ありません。また、Rails環境でよく使用されるコア拡張機能を含む、すべてのActiveSupport拡張機能もロードします。
gems を使用しているコードや、依存関係がlib/
にない場合、require_dependency
を使用して依存関係をロードしなければならないことがあります。
例えば、re2
ライブラリを使用しているGitlab::UntrustedRegexp
クラスを呼び出しているコードをテストしたい場合、re2
gem を必要とするライブラリ内のファイルにrequire_dependency 're2'
を追加してこの要件を明示するか、仕様そのものに追加するかのどちらかですが、前者が望ましいでしょう。
fast_spec_helper
を使用したテストのロードには、通常のspec_helper
では 30 秒以上かかるところ、約 1 秒かかります。
let
変数
GitLabのRSpecスイートは、重複を減らすためにlet
(バージョンlet!
)の変数を多用してきました。しかし、これは時に明快さを犠牲にすることになるので、今後の使用のガイドラインを決める必要があります:
-
let!
インスタンス変数よりもlet
変数の方が望ましい。let!
変数よりも 変数の方が望ましい。let
変数よりも 変数の方が望ましい。 -
let
を使用して、スペックファイル全体の重複を減らします。 - ひとつのテストで使用する変数の定義には
let
を使用せず、テストのit
ブロック内部でローカル変数として定義します。 - トップレベルの
describe
ブロックの内部でlet
変数を定義しないでください。context
やdescribe
の深いネストのブロックでのみ使用されます。 変数が使用される場所にできるだけ近い場所で定義してください。 -
let
変数の定義を別の変数で上書きすることは避けましょう。 -
let
、他の変数の定義でしか使われないような変数は定義しないでください。 代わりにヘルパーメソッドを使いましょう。 -
let!
変数は、定義された順序で厳密に評価する必要がある場合にのみ使用しますlet
。 このlet
変数は、参照されるまで評価されない遅延変数であることをlet
忘れないでlet
ください。
一般的な試験セットアップ
例えば、プロジェクトとそのプロジェクトのゲストが同じプロジェクトのイシューをテストする必要がある場合、1つのプロジェクトと1つのユーザでファイル全体をテストすることができます。
できる限り、before(:all)
やbefore(:context)
を使って実装しないでください。もし実装するのであれば、これらのフックはデータベーストランザクションの外部で実行されるため、手動でデータをクリーンアップする必要があります。
代わりに、let_it_be
変数とtest-prof
gemのbefore_all
フックを使うことで実現できます。
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
before_all do
project.add_guest(user)
end
この結果、このコンテキストのために作成されるのは、Project
、User
、ProjectMember
の1つだけになります。
let_it_be
before_all
コンテキストの後始末は、トランザクションのロールバックを使って自動的に処理されます。
let_it_be
ブロックの内部で定義されたオブジェクトを変更した場合、必要に応じてオブジェクトをリロードするか、reload
オプションを指定して、例ごとにリロードする必要があることに注意してください。
let_it_be(:project, reload: true) { create(:project) }
また、refind
オプションを指定して、新しいオブジェクトを完全にロードすることもできます。
let_it_be(:project, refind: true) { create(:project) }
時間に敏感なテスト
Timecopは、Ruby ベースのテストで時間的な制約があるものを検証するために利用できます。 時間的な制約があるものを実行したり検証したりするテストでは、Timecop を使って一過性のテスト失敗を防ぐ必要があります。
使用例:
it 'is overdue' do
issue = build(:issue, due_date: Date.tomorrow)
Timecop.freeze(3.days.from_now) do
expect(issue).to be_overdue
end
end
テストにおける機能フラグ
すべての機能フラグは、Ruby ベースのテストではデフォルトで有効になるようにスタブされています。
テスト内で機能フラグを無効にするには、stub_feature_flags
ヘルパーを使います。 たとえば、ci_live_trace
機能フラグをテスト内でグローバルに無効にするには、 ヘルパーを使います:
stub_feature_flags(ci_live_trace: false)
Feature.enabled?(:ci_live_trace) # => false
機能フラグが一部のアクターに対してのみ有効で、他のアクターに対しては有効でないテストを設定したい場合、ヘルパーに渡されるオプションでこれを指定できます。 たとえば、特定のプロジェクトに対してci_live_trace
機能フラグを有効にします:
project1, project2 = build_list(:project, 2)
# Feature will only be enabled for project1
stub_feature_flags(ci_live_trace: project1)
Feature.enabled?(:ci_live_trace) # => false
Feature.enabled?(:ci_live_trace, project1) # => true
Feature.enabled?(:ci_live_trace, project2) # => false
これはFlipperGateの実際の動作を表しています:
- 指定したアクターのオーバーライドを有効にすることができます。
- 指定したアクタのオーバーライドを無効化(削除)し、デフォルト状態に戻すことができます。
- 指定されたアクターを明示的に無効にするモデルを作成する方法はありません。
Feature.enable(:my_feature)
Feature.disable(:my_feature, project1)
Feature.enabled?(:my_feature) # => true
Feature.enabled?(:my_feature, project1) # => true
Feature.disable(:my_feature2)
Feature.enable(:my_feature2, project1)
Feature.enabled?(:my_feature2) # => false
Feature.enabled?(:my_feature2, project1) # => true
stub_feature_flags
対Feature.enable*
テスト環境で機能フラグを有効にするには、stub_feature_flags
を使用するのが好ましいです。 この方法は、単純なユースケースに対して、シンプルでよく記述されたインターフェイスを提供します。
しかし、場合によっては、機能フラグのパーセンテージロールアウトのような、より複雑な動作をテストする必要があります。 これは、.enable_percentage_of_time
と.enable_percentage_of_actors
# Good: feature needs to be explicitly disabled, as it is enabled by default if not defined
stub_feature_flags(my_feature: false)
stub_feature_flags(my_feature: true)
stub_feature_flags(my_feature: project)
stub_feature_flags(my_feature: [project, project2])
# Bad
Feature.enable(:my_feature_2)
# Good: enable my_feature for 50% of time
Feature.enable_percentage_of_time(:my_feature_3, 50)
# Good: enable my_feature for 50% of actors/gates/things
Feature.enable_percentage_of_actors(:my_feature_4, 50)
定義された状態を持つ各機能フラグは、テスト実行時に永続化されます:
Feature.persisted_names.include?('my_feature') => true
Feature.persisted_names.include?('my_feature_2') => true
Feature.persisted_names.include?('my_feature_3') => true
Feature.persisted_names.include?('my_feature_4') => true
スタビングゲート
Feature.enabled?
とFeature.disabled?
に引数として渡されるゲートは、FeatureGate
を含むオブジェクトであることが必要です。
スペックでは、stub_feature_flag_gate
、カスタムゲートを素早く作ることができます:
gate = stub_feature_flag_gate('CustomActor')
stub_feature_flags(ci_live_trace: gate)
Feature.enabled?(:ci_live_trace) # => false
Feature.enabled?(:ci_live_trace, gate) # => true
クリーンなテスト環境
GitLab のひとつのテストによって実行されるコードは、多くのデータにアクセスしたり変更したりする可能性があります。 テスト実行前の準備や実行後の後始末を入念に行わないと、あるテストによってデータが変更され、後続のテストの挙動に影響を及ぼす可能性があります。 これは絶対に避けなければなりません。 幸い、既存のテストフレームワークはほとんどのケースに対応しています。
テスト環境が汚染された場合、一般的な結果はテストの欠陥です。 汚染は多くの場合、順序依存性として現れます。つまり、仕様 A の後に仕様 B を実行すると確実に失敗しますが、仕様 B の後に仕様 A を実行すると確実に成功します。 このような場合、rspec --bisect
(あるいは手動で仕様ファイルをペアワイズバイセクト) を使用して、どの仕様に問題があるかを特定することができます。 問題の解決には、テストスイートがどのようにして環境を清浄に保つかを理解する必要があります。 各データストアについての詳細は、この先をお読みください!
SQLデータベース
これはdatabase_cleaner
gem で管理されています。各 spec はトランザクションで囲まれており、テストが完了するとロールバックされます。一部の spec では、完了後にすべてのテーブルに対してDELETE FROM
クエリを発行します。これにより、作成された行を複数のデータベース接続から表示できるようになります。これは、ブラウザで実行する spec やマイグレーション spec などで重要です。
よく知られているTRUNCATE TABLES
アプローチではなく、これらのストラテジーを使用することの結果の 1 つは、主キーやその他のシーケンスが仕様間でリセットされないことです。そのため、仕様 A でプロジェクトを作成し、仕様 B でプロジェクトを作成した場合、最初のプロジェクトはid=1
になり、2 番目のプロジェクトはid=2
になります。
偶発的なコンフリクトを避けるため、このようなカラムの値を手動で指定することも避けるべきです。 その代わりに未指定のままにしておき、行が作成された後に値を調べます。
Redis
GitLabはRedisにキャッシュされたアイテムとSidekiqジョブの2つのデータを保存します。
ほとんどの仕様では、Railsキャッシュは実際にはインメモリストアです。 これは仕様間で置き換えられるので、Rails.cache.read
やRails.cache.write
への呼び出しは安全です。ただし、仕様が直接Redisを呼び出す場合は、適切な:clean_gitlab_redis_cache
、:clean_gitlab_redis_shared_state
または:clean_gitlab_redis_queues
特性を自分自身に付ける必要があります。
バックグラウンド・ジョブ / Sidekiq
デフォルトでは、Sidekiqジョブはジョブ配列にキューイングされ、処理されません。テストがSidekiqジョブをキューイングし、それらを処理する必要がある場合、:sidekiq_inline
特性を使用できます。
この:sidekiq_might_not_need_inline
特性は:sidekiq_might_not_need_inline
、:sidekiq_might_not_need_inline
Sidekiqのインラインモードがフェイクモードに変更:sidekiq_might_not_need_inline
された:sidekiq_might_not_need_inline
ときに:sidekiq_might_not_need_inline
、:sidekiq_might_not_need_inline
Sidekiqが実際にジョブを処理する必要があるすべてのテストに追加されました。 この特性を持つテストは、Sidekiqがジョブを処理することに依存しないように修正さ:sidekiq_might_not_need_inline
れるか、バックグラウンドジョブの処理が必要/期待される場合は、:sidekiq_might_not_need_inline
その:sidekiq_might_not_need_inline
特性を:sidekiq_inline
。
perform_enqueued_jobs
の使用は、Sidekiq ワーカーがApplicationJob
/ActiveJob::Base
を継承していないため、遅延メール配送のテストにのみ有用です。DNS
DNS は開発者の内部ネットワークによってイシューが発生する可能性があるため、テストスイートでは (!22368の時点で) DNS リクエストをスタブしています。spec/support/dns.rb
にある RSpec ラベルをテストに適用することで、DNS スタブを回避することができます:
it "really connects to Prometheus", :permit_dns do
また、より具体的な制御が必要な場合は、DNSブロックはspec/support/helpers/dns_helpers.rb
、これらのメソッドを他の場所で呼び出すことができます。
ファイルシステム
ファイルシステムのデータは、大まかに「リポジトリ」と「その他すべて」に分けられます。リポジトリはtmp/tests/repositories
に保存されます。このディレクトリは、テスト実行の開始前と終了後に空にされます。 スペックの間は空にされないため、作成されたリポジトリはプロセスの有効期間中、このディレクトリに蓄積されます。 削除にはコストがかかりますが、注意深く管理しないと汚染につながる可能性があります。
これを避けるために、テスト・スイートではハッシュ化されたストレージが有効になっています。 これは、リポジトリにプロジェクト ID に依存する一意のパスが与えられることを意味します。 プロジェクト ID は仕様間でリセットされないため、各仕様がディスク上に独自のリポジトリを取得することが保証され、仕様間の変更が見えなくなります。
プロジェクトIDを手動で指定したり、tmp/tests/repositories/
ディレクトリの状態を直接検査したりする場合は、実行前と実行後の両方でディレクトリをクリーンアップする必要があります。 一般的に、これらのパターンは完全に避けるべきです。
アップロードなど、データベースオブジェクトにリンクされた他のクラスのファイルも、一般的に同じ方法で管理されます。 仕様でハッシュストレージが有効になっている場合、それらはIDによって決定された場所にディスクに書き込まれるため、競合が発生することはありません。
projects
ファクトリに:legacy_storage
特性を渡すことで、ハッシュストレージを無効にする仕様もあります。 このような仕様では、プロジェクトのpath
やそのグループを決してオーバーライドしてはいけません。 デフォルトのパスにはプロジェクト ID が含まれるため、競合することはありませんが、2 つの仕様が同じパスで:legacy_storage
プロジェクトを作成すると、ディスク上の同じリポジトリが使用され、テスト環境の汚染につながります。
例えば、tmp/test-file.csv
ファイルを作成するようなコードを実行する場合、スペックはクリーンアップの一環としてそのファイルが削除されるようにしなければなりません。
永続的なインメモリ・アプリケーションの状態
rspec
つまり、spec 間でアクセス可能な Ruby オブジェクトを変更することで、互いに影響を与え合うことができます。実際には、グローバル変数や定数(Ruby クラスやモジュールなどが含まれます。
グローバル変数は通常、変更すべきではありません。 どうしても必要な場合は、このようなブロックを使って、変更後に確実にロールバックすることができます:
around(:each) do |example|
old_value = $0
begin
$0 = "new-value"
example.run
ensure
$0 = old_value
end
end
仕様で定数を変更する必要がある場合は、stub_const
ヘルパーを使用して、変更がロールバックされるようにする必要があります。
ENV
定数の内容を変更する必要がある場合は、代わりにstub_env
ヘルパー・メソッドを使うことができます。
ほとんどのRubyインスタンスは仕様間で共有されませんが、クラスやモジュールは共有されるのが一般的です。 クラスやモジュールのインスタンス変数、アクセサ、クラス変数、その他のステートフルなイディオムは、グローバル変数と同じように扱うべきです。 特に、期待値や依存性注入をスタブと一緒に使うことで、修正の必要性を避けることができます。 他に選択肢がない場合は、上記のグローバル変数の例と同じようなaround
ブロックを使うことができますが、これはできるだけ避けるべきです。
テーブルベース / パラメータ化されたテスト
このスタイルのテストは、ひとつのコードにさまざまな入力を与えて実行するものです。 テストケースを一度だけ指定し、入力の表とそれぞれに期待される出力を並べることで、 テストを読みやすくコンパクトにすることができます。
RSpec::Parameterizedのgemを使用します。 テーブル構文を使用し、Rubyの入力範囲の等式をチェックする簡単な例は次のようになります:
describe "#==" do
using RSpec::Parameterized::TableSyntax
where(:a, :b, :result) do
1 | 1 | true
1 | 2 | false
true | true | true
true | false | false
end
with_them do
it { expect(a == b).to eq(result) }
it 'is isomorphic' do
expect(b == a).to eq(result)
end
end
end
where
ブロックの入力として単純な値のみを使用してください。 プロック、ステートフル オブジェクト、FactoryBot が作成したオブジェクトなどを使用すると、予期しない結果になることがあります。プロメテウスのテスト
Prometheus のメトリクスは、テスト実行ごとに保持される場合があります。 メトリクスが各例の前にリセットされるようにするには、RSpec テストに:prometheus
タグを追加します。
マッチャー
カスタムマッチャーは、RSpec の意図を明確にしたり、RSpec に期待される複雑さを隠したりするために作成されるべきものです。これらはspec/support/matchers/
の下に置かれるべきものです。 マッチャーは、特定の種類の仕様(例:フィーチャー、リクエストなど)にのみ適用される場合はサブフォルダーに置くことができますが、複数の種類の仕様に適用される場合は、サブフォルダーに置くべきではないでしょう。
be_like_time
データベースから返される時刻はRubyの時刻オブジェクトとは精度が異なることがあるので、specで比較するときには柔軟な許容範囲が必要です。be_like_time
、時刻が互いに1秒以内であることを比較することができます。
使用例:
expect(metrics.merged_at).to be_like_time(time)
have_gitlab_http_status
have_http_status
やexpect(response.status).to
よりも、have_gitlab_http_status
のほうがいいでしょう。 なぜなら、前者はステータスが不一致のときにレスポンスボディも表示できるからです。 テストが壊れ始めたときに、ソースを編集してテストを再実行することなく、その原因を知りたいときに、これはとても便利です。
特に500内部サーバーエラーが表示されるときに便利です。
数値表現の206
よりも、:no_content
のような名前付きの HTTP ステータスを優先してください。
使用例:
expect(response).to have_gitlab_http_status(:ok)
クエリパフォーマンスのテスト
クエリのパフォーマンスをテストすることで、以下のことが可能になります:
- コードのブロック内にN+1個の問題が存在しないことを保証します。
- コードのブロック内のクエリの数が気づかないうちに増えないようにします。
クエリーレコーダー
QueryRecorder
を使用すると、指定したコードブロック内で実行されたデータベースクエリの数をプロファイリングしてテストできます。
詳しくは、QueryRecorder
のセクションをご覧ください。
GitalyClient
Gitlab::GitalyClient.get_request_count
を使用すると、指定されたコードのブロックによって行われたGitalyクエリの数をテストすることができます:
詳しくは、Gitaly Request Counts
のセクションをご覧ください。
共有コンテキスト
1つの仕様ファイルでのみ使用される共有コンテキストは、インラインで宣言することができます。 複数の仕様ファイルで使用される共有コンテキスト:
-
spec/support/shared_contexts/
。 - 特定の種類のスペックにのみ適用される場合はサブフォルダに入れることができますが(例:フィーチャー、リクエストなど)、複数の種類のスペックに適用される場合はサブフォルダに入れるべきではありません。
各ファイルは1つのコンテキストのみを含み、spec/support/shared_contexts/controllers/githubish_import_controller_shared_context.rb
のような説明的な名前を持つ必要があります。
共有例
1つの仕様ファイルにのみ使用される共有例は、インラインで宣言することができます。 複数の仕様ファイルで使用される共有例は、インラインで宣言することができます:
-
spec/support/shared_examples/
。 - 特定の種類のスペックにのみ適用される場合はサブフォルダに入れることができますが(例:フィーチャー、リクエストなど)、複数の種類のスペックに適用される場合はサブフォルダに入れるべきではありません。
各ファイルは1つのコンテキストのみを含み、spec/support/shared_examples/controllers/githubish_import_controller_shared_example.rb
のような説明的な名前を持つ必要があります。
ヘルパー
ヘルパーは通常、特定の RSpec の例の複雑さを隠すためのいくつかのメソッドを提供するモジュールです。 他の spec と共有することを意図していない場合は、RSpec ファイルの中でヘルパーを定義することができます。そうでない場合は、spec/support/helpers/
の下に置く必要があります。 特定の種類の spec (例: 機能、リクエストなど) にのみ適用される場合は、ヘルパーをサブフォルダに置くことができますが、複数の種類の spec に適用される場合は、サブフォルダに置くべきではありません。
ヘルパーはRailsの命名規則/名前空間規則に従うべきです。 例えば、spec/support/helpers/cycle_analytics_helpers.rb
:
module Spec
module Support
module Helpers
module CycleAnalyticsHelpers
def create_commit_referencing_issue(issue, branch_name: random_git_name)
project.repository.add_branch(user, branch_name, 'master')
create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name)
end
end
end
end
end
ヘルパーはRSpecの設定を変更してはいけません。 例えば、上記のヘルパーモジュールはインクルードしてはいけません:
RSpec.configure do |config|
config.include Spec::Support::Helpers::CycleAnalyticsHelpers
end
工場
GitLabはfactory_botをテストフィクスチャの代わりとして使っています。
- ファクトリーの定義は
spec/factories/
にあり、対応するモデルの複数形を使って命名されます (User
のファクトリーはusers.rb
で定義されます )。 - 1つのファイルにトップレベルのファクトリー定義は1つだけであるべきです。
- FactoryBotのメソッドはすべてのRSpecグループに混在しています。 つまり、
FactoryBot.create(...)
の代わりにcreate(...)
を呼び出すことができます(呼び出すべきです)。 - traitsを使って定義や用法を整理します。
- ファクトリーを定義する際には、結果のレコードがバリデーションを通過するために必要でない属性を定義しないでください。
- ファクトリーからインスタンスを作成する場合は、 テストで必要とされない属性を指定しないようにしましょう。
- ファクトリーは
ActiveRecord
オブジェクトに限定する必要はありません。
備品
すべての器具はspec/fixtures/
の下に置いてください。
リポジトリ
例えばマージリクエストをマージするなど、いくつかの機能をテストする場合、テスト環境に特定の状態のGitリポジトリが存在する必要があります。 GitLabは特定の一般的なケースのためにgitlab-test
リポジトリを維持します - リポジトリのコピーがプロジェクトファクトリの:repository
特性で使用されていることを確認できます:
let(:project) { create(:project, :repository) }
できる限り、:repository
の代わりに、:custom_repo
を使用することを検討してください。 これにより、プロジェクトのリポジトリのmaster
ブランチに表示されるファイルを正確に指定することができます。 たとえば、次のようになります:
let(:project) do
create(
:project, :custom_repo,
files: {
'README.md' => 'Content here',
'foo/bar/baz.txt' => 'More content here'
}
)
end
デフォルト権限と指定された内容の2つのファイルを含むリポジトリが作成されます。
設定
RSpec設定ファイルは、RSpecの設定(すなわちRSpec.configure do |config|
ブロック)を変更するファイルです。これらはspec/support/
の下に置く必要があります。
各ファイルは、spec/support/capybara.rb
、spec/support/carrierwave.rb
など、特定のドメインに関連するものでなければなりません。
ヘルパーモジュールが特定の種類のスペックにのみ適用される場合、config.include
の呼び出しに修飾子を追加する必要があります。例えば、spec/support/helpers/cycle_analytics_helpers.rb
が:lib
とtype: :model
のスペックにのみ適用される場合、次のように記述します:
RSpec.configure do |config|
config.include Spec::Support::Helpers::CycleAnalyticsHelpers, :lib
config.include Spec::Support::Helpers::CycleAnalyticsHelpers, type: :model
end
のみで構成される設定ファイルの場合、spec/spec_helper.rb
に直接config.include
追加するconfig.include
ことができます。
非常に汎用的なヘルパーについては、そのファイルで使用されるspec/support/rspec.rb
ファイルに含めることを検討してspec/fast_spec_helper.rb
ください。spec/fast_spec_helper.rb
ファイルについての詳細はspec/fast_spec_helper.rb
Fast unit testsspec/fast_spec_helper.rb
をspec/fast_spec_helper.rb
参照してくださいspec/fast_spec_helper.rb
。
テスト環境のロギング
Gitaly、Workhorse、Elasticsearch、Capybaraなど、テスト実行時にテスト環境用のサービスが自動的に設定・起動されます。 CIで実行する場合、またはサービスをインストールする必要がある場合、テスト環境はセットアップ時間に関する情報をログに記録し、以下のようなログメッセージを生成します:
==> Setting up Gitaly...
Gitaly set up in 31.459649 seconds...
==> Setting up GitLab Workhorse...
GitLab Workhorse set up in 29.695619 seconds...
fatal: update refs/heads/diff-files-symlink-to-image: invalid <newvalue>: 8cfca84
From https://gitlab.com/gitlab-org/gitlab-test
* [new branch] diff-files-image-to-symlink -> origin/diff-files-image-to-symlink
* [new branch] diff-files-symlink-to-image -> origin/diff-files-symlink-to-image
* [new branch] diff-files-symlink-to-text -> origin/diff-files-symlink-to-text
* [new branch] diff-files-text-to-symlink -> origin/diff-files-text-to-symlink
b80faa8..40232f7 snippet/multiple-files -> origin/snippet/multiple-files
* [new branch] testing/branch-with-#-hash -> origin/testing/branch-with-#-hash
==> Setting up GitLab Elasticsearch Indexer...
GitLab Elasticsearch Indexer set up in 26.514623 seconds...
ローカルで実行しているときやアクションを実行する必要がないときは、この情報は省略されます。 これらのメッセージを常に表示したい場合は、以下の環境変数を設定してください:
GITLAB_TESTING_LOG_LEVEL=debug