テストのベストプラクティス

テストデザイン

GitLabにおけるテストは後付けではなく、第一級市民です。 機能の設計と同じように、テストの設計も考慮することが重要です。

機能を実装するときは、適切な機能を適切な方法で開発することを考え、スコープを管理しやすいレベルまで絞り込みます。 機能のテストを実装するときは、適切なテストを開発することを考えなければなりませんが、テストが失敗する可能性のある重要な方法を_すべて_カバーしなければなりません。

テストヒューリスティックは、この問題を解決するのに役立ちます。 バグがコード内で発現する一般的な方法の多くに、簡潔に対応しています。 テストを設計する際には、既知のテストヒューリスティックをレビューして、テスト設計に役立てましょう。 ハンドブックのテストエンジニアリングのセクションに、役に立つヒューリスティックのドキュメントがあります。

テスト速度

GitLabには膨大なテストスイートがあり、並列化しないと実行に何時間もかかることがあります。 正確で効果的で_、_かつ高速なテストを書く努力をすることが重要です。

テストのパフォーマンスに関して、いくつか留意すべき点があります:

  • instance_doublespy よりも高速です。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_ofallow_any_instance_of の使用は避けてください(「Gotchas」を参照)。
  • :each はデフォルトなので、hooksに指定しないでください。
  • beforeafter のフックでは、: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_debugCHROME_HEADLESS=0 と一緒に使うと、開いているブラウザを一時停止し、ページを再び開きません。 これはデバッグや要素の検査に使えます。

また、byebugbinding.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_helperlib/ 。 つまり、クラス/モジュールが 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 変数を定義しないでください。contextdescribe の深いネストのブロックでのみ使用されます。 変数が使用される場所にできるだけ近い場所で定義してください。
  • let 変数の定義を別の変数で上書きすることは避けましょう。
  • let 、他の変数の定義でしか使われないような変数は定義しないでください。 代わりにヘルパーメソッドを使いましょう。
  • let! 変数は、定義された順序で厳密に評価する必要がある場合にのみ使用しますlet 。 この let変数は、参照されるまで評価されない遅延変数であることをlet 忘れないで letください。

一般的な試験セットアップ

例えば、プロジェクトとそのプロジェクトのゲストが同じプロジェクトのイシューをテストする必要がある場合、1つのプロジェクトと1つのユーザでファイル全体をテストすることができます。

できる限り、before(:all)before(:context)を使って実装しないでください。もし実装するのであれば、これらのフックはデータベーストランザクションの外部で実行されるため、手動でデータをクリーンアップする必要があります。

代わりに、let_it_be 変数とtest-prof gembefore_all フックを使うことで実現できます。

let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }

before_all do
  project.add_guest(user)
end

この結果、このコンテキストのために作成されるのは、ProjectUserProjectMember の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の実際の動作を表しています:

  1. 指定したアクターのオーバーライドを有効にすることができます。
  2. 指定したアクタのオーバーライドを無効化(削除)し、デフォルト状態に戻すことができます。
  3. 指定されたアクターを明示的に無効にするモデルを作成する方法はありません。
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_flagsFeature.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.readRails.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_statusexpect(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.rbspec/support/carrierwave.rbなど、特定のドメインに関連するものでなければなりません。

ヘルパーモジュールが特定の種類のスペックにのみ適用される場合、config.include の呼び出しに修飾子を追加する必要があります。例えば、spec/support/helpers/cycle_analytics_helpers.rb:libtype: :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.rbFast unit testsspec/fast_spec_helper.rbspec/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

テストドキュメントに戻る