エンドツーエンドテストのベストプラクティス

注:これはテストガイドにあるベストプラクティスを拡張したものです。

UIよりもAPIを優先

エンドツーエンドのテストフレームワークは、ケースバイケースでリソースを作成する機能を持っています。 リソースは、可能な限り API 経由で作成されるべきです。

API経由でテストに必要なリソースを作成することで、時間とコストの両方を節約できます。

リソースについて詳しくはこちらをご覧ください。

余計な期待は避けましょう

テストを無駄のないものにするためには、必要なものだけをテストすることが重要です。

テストする必要のある内容とは関係のない、expect() ステートメントを追加しないようにしてください。

使用例:

#=> Good
Flow::Login.sign_in
Page::Main::Menu.perform do |menu|
  expect(menu).to be_signed_in
end

#=> Bad
Flow::Login.sign_in(as: user)
Page::Main::Menu.perform do |menu|
  expect(menu).to be_signed_in
  expect(page).to have_content(user.name) #=>  we already validated being signed in. redundant.
  expect(menu).to have_element(:nav_bar) #=> likely unnecessary. already validated in lower-level. test doesn't call for validating this.
end

#=> Good
issue = Resource::Issue.fabricate_via_api! do |issue|
  issue.name = 'issue-name'
end

Project::Issues::Index.perform do |index|
  expect(index).to have_issue(issue)
end

#=> Bad
issue = Resource::Issue.fabricate_via_api! do |issue|
  issue.name = 'issue-name'
end

Project::Issues::Index.perform do |index|
  expect(index).to have_issue(issue)
  expect(page).to have_content(issue.name) #=> page content check is redundant as the issue was already validated in the line above.
end

複数のファイルにテストを分割することを推奨

私たちのフレームワークには、仕様ファイルを並列に実行することで機能する、いくつかの並列化メカニズムが含まれています。

しかし、テストは specファイル単位で並列化され、test/example 単位では並列化されないため、既存のファイルに新しいテストが追加されても、より大きな並列化は実現できません。

とはいえ、既存のファイルに新しいテストを追加する理由は他にもあるでしょう。

たとえば、セットアップにコストがかかる状態をテストが共有する場合、そのセットアップを使用するテストが並列化できなくても、セットアップを一度だけ実行したほうが効率的な場合があります。

要約すると

  • To-Do: 高価なセットアップを共有するテストを除き、テストを別々のファイルに分割します。
  • 禁止: 並列化への影響を考慮せずに、既存のファイルに新しいテストを追加してください。

before(:context)after フックでのUIの使用を制限。

before(:context) フックの使用は、APIコール、非UIオペレーション、またはログインのような基本的なUIオペレーションのみでセットアップタスクを実行するように制限してください。

失敗時に自動的にスクリーンショットを保存するためにcapybara-screenshot ライブラリを使用しています。

capybara-screenshot スクリーンショットはRSpecのafter フックに保存されます。before(:context)で失敗した場合、after フックは呼び出されず、 したがってスクリーンショットは保存されません。

この事実を踏まえると、before(:context) の使用は、スクリーンショットが不要なオペレーションのみに限定すべきです。

同様に、after フックは非UIオペレーションにのみ使用されるべきです。 afterテストファイル内のフックにafter あるUIオペレーションは after、スクリーンショットを撮影するafter フックの前に実行されます。これは、UIステータスを障害発生ポイントから遠ざける結果となり、スクリーンショットが適切なタイミングで撮影されません。

テストがブラウザにログインしたままにならないようにします。

すべてのテストは、テスト開始時にログインできることを想定しています。

例をご覧ください:https://gitlab.com/gitlab-org/gitlab/-/issues/34736

理想的には、after(:context) (またはbefore(:context))ブロックで実行されるアクションはすべてAPI経由で実行されます。しかし、UI経由で実行する必要がある場合(API機能が存在しない場合など)は、ブロックの最後でログアウトするようにしてください。

after(:all) do
  login unless Page::Main::Menu.perform(&:signed_in?)

  # Do something while logged in

  Page::Main::Menu.perform(&:sign_out)
end

管理者アクセスが必要なタグテスト

本番環境に対して管理者アクセスを必要とするテストを実行することはありません。

Administrator アクセスを必要とする新しいテストを追加したら、RSpec メタデータ:requires_admin を適用して、Production やその他のテストを実行したくない環境に対して実行されるテストスイートに、そのテストが含まれないようにします。

注意: ローカルでテストを実行したり、パイプラインを設定したりする場合は、環境変数QA_CAN_TEST_ADMIN_FEATURESfalse に設定することで、:requires_admin タグを持つテストをスキップすることができます。

Commit リソースを優先します。ProjectPush

APIを使用する際には、可能な限りCommit

ProjectPush は、Git Command Line Interface(CLI) を使った生のシェルコマンドを使うのに対し、Commit リソースは HTTP リクエストを行います。

# Using a commit resource
Resource::Commit.fabricate_via_api! do |commit|
  commit.commit_message = 'Initial commit'
  commit.add_files([
    {file_path: 'README.md', content: 'Hello, GitLab'}
  ])
end

# Using a ProjectPush
Resource::Repository::ProjectPush.fabricate! do |push|
  push.commit_message = 'Initial commit'
  push.file_name = 'README.md'
  push.file_content = 'Hello, GitLab'
end
注意:ProjectPush を使う例外としては、SSH インテグレーションや Git CLI を使うテストがあります。