テストレベル

Testing priority triangle

e2e は、end-to-end を意味します。

2019-05-01現在、レベルごとのテストのディストリビューションは以下の通りです:

テストレベル コミュニティ版 エンタープライズ版 コミュニティ+エンタープライズ版
システムレベルでのブラックボックステスト(別名エンドツーエンドテストまたはQAテスト) 68 (0.14%) 31 (0.2%) 99 (0.17%)
システムレベルでのホワイトボックステスト(別名、システムテストまたは機能テスト) 5,471 (11.9%) 969 (7.4%) 6440 (10.9%)
インテグレーションテスト 8,333 (18.2%) 2,244 (17.2%) 10,577 (17.9%)
単体テスト 32,031 (69.7%) 9,778 (75.1%) 41,809 (71%)

単体テスト

正式な定義:https://en.wikipedia.org/wiki/Unit_testing

この種のテストは、ひとつのコード単位 (メソッド) が期待通りに動作する (入力が与えられれば予測可能な出力が得られる) ことを保証するものです。 これらのテストは可能な限り分離されるべきです。 たとえば、データベースに対して何もしないモデルメソッドは DB レコードを必要としないはずです。 データベースレコードを必要としないクラスは、可能な限りスタブ/ダブルを使うべきです。

コードパス テストパス テストエンジン 備考
app/assets/javascripts/ spec/javascripts/,spec/frontend/ カルマ&ジェスト 詳しくはFrontend Testing ガイドをご覧ください。
app/finders/ spec/finders/ アールエスペック  
app/graphql/ spec/graphql/ アールエスペック  
app/helpers/ spec/helpers/ アールエスペック  
app/models/ spec/models/ アールエスペック  
app/policies/ spec/policies/ アールエスペック  
app/presenters/ spec/presenters/ アールエスペック  
app/serializers/ spec/serializers/ アールエスペック  
app/services/ spec/services/ アールエスペック  
app/uploaders/ spec/uploaders/ アールエスペック  
app/validators/ spec/validators/ アールエスペック  
app/views/ spec/views/ アールエスペック  
app/workers/ spec/workers/ アールエスペック  
bin/ spec/bin/ アールエスペック  
config/ spec/config/ アールエスペック  
config/initializers/ spec/initializers/ アールエスペック  
config/routes.rb,config/routes/ spec/routing/ アールエスペック  
config/puma.example.development.rb,config/unicorn.rb.example spec/rack_servers/ アールエスペック  
db/ spec/db/ アールエスペック  
db/{post_,}migrate/ spec/migrations/ アールエスペック 詳細はTestingRailsmigrationsガイドをご覧ください。
Gemfile spec/dependencies/,spec/sidekiq/ アールエスペック  
lib/ spec/lib/ アールエスペック  
lib/tasks/ spec/tasks/ アールエスペック  
rubocop/ spec/rubocop/ アールエスペック  
spec/factories spec/factories_spec.rb アールエスペック  

フロントエンドのユニットテスト

ユニットテストは最も抽象度の低いテストであり、通常、ユーザーが直接認識できない機能をテストします。

graph RL plain[Plain JavaScript]; Vue[Vue Components]; feature-flags[Feature Flags]; license-checks[License Checks]; plain---Vuex; plain---GraphQL; Vue---plain; Vue---Vuex; Vue---GraphQL; browser---plain; browser---Vue; plain---backend; Vuex---backend; GraphQL---backend; Vue---backend; backend---database; backend---feature-flags; backend---license-checks; class plain tested; class Vuex tested; classDef node color:#909090,fill:#f0f0f0,stroke-width:2px,stroke:#909090 classDef label stroke-width:0; classDef tested color:#000000,fill:#a0c0ff,stroke:#6666ff,stroke-width:2px,stroke-dasharray: 5, 5; subgraph " " tested; mocked; class tested tested; end

ユニットテストを使用するタイミング

  • エクスポートされた関数やクラス:エクスポートされたものは、あなたがコントロールできない形で、さまざまな場所で再利用される可能性があります。 公開インターフェースの期待される振る舞いを、テストを用いて文書化する必要があります。
  • Vuexアクション: Vuexアクションは、トリガー元のコンポーネントに依存せず、一貫した方法で動作する必要があります。
  • Vuexの変異:複雑なVuexの変異については、問題解決を簡単にするために、Vuexストアの他の部分からテストを分離する必要があります。

ユニットテストを使用しない場合

  • エクスポートされていない関数やクラス:モジュールからエクスポートされていないものは、非公開か実装の詳細とみなすことができ、テストする必要はありません。
  • 定数:定数の値をテストすることは、それをコピーすることを意味します。
  • Vueコンポーネント:計算されたプロパティ、メソッド、およびライフサイクルフックは、コンポーネントの実装の詳細とみなすことができ、暗黙のうちにコンポーネントテストでカバーされ、テストする必要はありません。 詳細については、Vueの公式ガイドラインを参照してください。

ユニットテストでモックするもの

  • テスト対象のクラスの状態: クラスのメソッドを使うのではなく、テスト対象のクラスの状態を直接変更することで、テストのセットアップにおける副作用を避けることができます。
  • その他のエクスポートされたクラス: テストシナリオが指数関数的に大きくなるのを防ぐために、すべてのクラスは分離してテストされなければなりません。
  • パラメータとして渡される単一の DOM 要素: ページ全体ではなく、単一の DOM 要素に対してのみオペレーションを行うテストでは、これらの要素を作成するほうが HTML フィクスチャ全体をロードするよりも安上がりです。
  • すべてのサーバーリクエスト: フロントエンドのユニットテストを実行するとき、バックエンドに到達できないかもしれません。
  • 非同期のバックグラウンドオペレーション: バックグラウンドオペレーションは、停止したり待機したりすることができないため、次のテストでも実行され続け、副作用を引き起こします。

ユニットテストでモックしてはいけないもの

  • エクスポートされていない関数やクラス: エクスポートされていないものはすべて、モジュールの非公開とみなすことができ、エクスポートされたクラスや関数を通して暗黙的にテストされます。
  • テスト対象のクラスのメソッド: テスト対象のクラスのメソッドをモックすることで、実際のメソッドではなくモックがテストされます。
  • ユーティリティ関数 (純粋な関数、あるいはパラメータを変更するだけの関数): ステートを持たないため副作用がない関数は、テストではモックしないほうが安全です。
  • 完全な HTMLページ: ユニットテストでは、完全なページの HTML を読み込まないようにしましょう。

フロントエンドコンポーネントのテスト

コンポーネントテストは、ユーザ入力、他のコンポーネントから発生するイベント、またはアプリケーションの状態などの外部信号に応じて、ユーザが認識できる単一のコンポーネントの状態を対象としています。

graph RL plain[Plain JavaScript]; Vue[Vue Components]; feature-flags[Feature Flags]; license-checks[License Checks]; plain---Vuex; plain---GraphQL; Vue---plain; Vue---Vuex; Vue---GraphQL; browser---plain; browser---Vue; plain---backend; Vuex---backend; GraphQL---backend; Vue---backend; backend---database; backend---feature-flags; backend---license-checks; class Vue tested; classDef node color:#909090,fill:#f0f0f0,stroke-width:2px,stroke:#909090 classDef label stroke-width:0; classDef tested color:#000000,fill:#a0c0ff,stroke:#6666ff,stroke-width:2px,stroke-dasharray: 5, 5; subgraph " " tested; mocked; class tested tested; end

コンポーネントテストを使用する場合

  • Vueコンポーネント

コンポーネントテストを使用しない場合

  • Vueアプリケーション: Vueアプリケーションには多くのコンポーネントが含まれることがあります。 コンポーネントレベルでのテストには多大な労力が必要です。 そのため、フロントエンドのインテグレーションレベルでテストされます。
  • HAMLテンプレート: HAMLテンプレートはマークアップのみを含み、フロントエンド側のロジックを含みません。 そのため、完全なコンポーネントではありません。

コンポーネントテストでモックするもの

  • DOM:実際のDOM上でのオペレーションは、仮想DOM上よりも大幅に遅くなります。
  • テスト対象のコンポーネントのプロパティと状態: クラスのテストと同様、(コンポーネントのメソッドに依存するのではなく)プロパティを直接変更することで、副作用を避けることができます。
  • Vuexストア:副作用を避け、コンポーネントテストをシンプルにするために、Vuexストアはモックに置き換えられます。
  • すべてのサーバーリクエスト: ユニットテストと同様、コンポーネントテストを実行するときにはバックエンドに到達できないことがあります。
  • 非同期のバックグラウンドオペレーション: ユニットテストと同様に、バックグラウンドオペレーションは停止したり待機したりすることができません。 これは、バックグラウンドオペレーションが次のテストでも実行され続け、副作用を引き起こすことを意味します。
  • 子コンポーネント:すべてのコンポーネントは個別にテストされるため、子コンポーネントはモックされます。 以下も参照してください。shallowMount()

コンポーネントテストでモックしてはいけないもの

  • テスト対象のコンポーネントのメソッドや計算されたプロパティ: テスト対象のコンポーネントの一部をモックすることで、実際のコンポーネントではなくモックがテストされます。
  • Vueから独立した関数とクラス:すべてのプレーンなJavaScriptコードは、すでにユニットテストでカバーされており、コンポーネントテストでモックする必要はありません。

インテグレーションテスト

正式な定義:https://en.wikipedia.org/wiki/Integration_testing

この種のテストは、実際のアプリ環境 (すなわちブラウザ) のオーバーヘッドなしに、アプリケーションの個々の部分がうまく連携して動作することを保証します。 これらのテストはリクエスト/レスポンスレベルでアサートすべきです: ステータスコード、ヘッダ、ボディ。 権限、リダイレクト、どのビューがレンダリングされるかなどをテストするのに便利です。

コードパス テストパス テストエンジン 備考
app/controllers/ spec/controllers/ アールエスペック N+1 テストでは、リクエスト仕様を使用します。
app/mailers/ spec/mailers/ アールエスペック  
lib/api/ spec/requests/api/ アールエスペック  
app/assets/javascripts/ spec/javascripts/,spec/frontend/ カルマ&ジェスト 詳細は下記

フロントエンドインテグレーションテスト

インテグレーションテストは、単一のページ上のすべてのコンポーネント間の相互作用をカバーします。 その抽象化レベルは、ユーザが UI とどのように相互作用するかに匹敵します。

graph RL plain[Plain JavaScript]; Vue[Vue Components]; feature-flags[Feature Flags]; license-checks[License Checks]; plain---Vuex; plain---GraphQL; Vue---plain; Vue---Vuex; Vue---GraphQL; browser---plain; browser---Vue; plain---backend; Vuex---backend; GraphQL---backend; Vue---backend; backend---database; backend---feature-flags; backend---license-checks; class plain tested; class Vue tested; class Vuex tested; class GraphQL tested; class browser tested; linkStyle 0,1,2,3,4,5,6 stroke:#6666ff,stroke-width:2px,stroke-dasharray: 5, 5; classDef node color:#909090,fill:#f0f0f0,stroke-width:2px,stroke:#909090 classDef label stroke-width:0; classDef tested color:#000000,fill:#a0c0ff,stroke:#6666ff,stroke-width:2px,stroke-dasharray: 5, 5; subgraph " " tested; mocked; class tested tested; end

インテグレーションテストを使うタイミング

  • ページバンドル (app/assets/javascripts/pages/index.js ファイル ): ページバンドルをテストすることで、対応するフロントエンドコンポーネントがうまくインテグレーションされることを保証します。
  • ページバンドル外のVueアプリケーション: Vueアプリケーション全体をテストすることで、対応するフロントエンドのコンポーネントがうまくインテグレーションされることを確認します。

インテグレーションテストでモックするもの

  • HAMLビュー (代わりにフィクスチャを使う): HAMLビューのレンダリングには、実行中のデータベースを含むRails環境が必要です。
  • すべてのサーバリクエスト: ユニットテストやコンポーネントテストと同様、コンポーネントテストを実行するとき、バックエンドに到達できないかもしれません。
  • ページ上で知覚できない非同期のバックグラウンドオペレーション: ページに影響を与えるバックグラウンドオペレーションは、このレベルでテストされなければなりません。 他のすべてのバックグラウンドオペレーションは、停止したり待機したりすることができないので、次のテストでも実行され続け、副作用を引き起こします。

インテグレーションテストでモックしてはいけないもの

  • DOM: 実際の DOM 上でテストを行うことで、コンポーネントが意図した環境で動作することを保証します。 DOM テストの一部は、クロスブラウザテストに委ねられます。
  • コンポーネントのプロパティまたは状態: このレベルでは、すべてのテストはユーザが行うアクションのみを実行できます。 たとえば、コンポーネントの状態を変更するには、クリックイベントが発生します。
  • Vuexストア: ページ全体のフロントエンドコードをテストする場合、VueコンポーネントとVuexストアの相互作用も対象となります。

コントローラーテストについて

理想的な世界では、コントローラは薄いものであるべきです。 しかし、そうでない場合は、コントローラのテストではなく、JavaScriptを使用しないシステムテストや機能テストを書くことができます。 太いコントローラをテストするには、通常、次のようなスタブをたくさん使用します:

controller.instance_variable_set(:@user, user)

で、Rails 5で非推奨となったメソッドを使用しています(#23768)。

カルマについて

Karmaはユニットテストとインテグレーションテストの両方のカテゴリに属しています。 KarmaはJavaScriptのテストを実行する環境を提供するので、ユニットテスト(単一のJavaScriptメソッドのテストなど)を実行することも、インテグレーションテスト(複数のコンポーネントで構成されるコンポーネントのテストなど)を実行することもできます。

システムレベルのホワイトボックステスト(旧称:システム/フィーチャーテスト)

正式な定義:

この種のテストは、GitLabRailsアプリケーション(例えば、gitlab-foss/gitlab)がブラウザの観点から期待通りに動作することを保証します。

注意してください:

  • アプリケーションの内部に関する知識は必要です。
  • テストに必要なデータは、通常 RSpec ファクトリを使用して直接作成します。
  • 期待値は多くの場合、データベースやオブジェクトの状態に設定されます。

これらのテストは、以下の場合にのみ使用されるべきです:

  • テストされる機能/コンポーネントが小さい
  • オブジェクト/データベースの内部状態をテストする必要があります。
  • それ以下のレベルではテストできません

たとえば、あるページのパンくずをテストするには、システムテストを書くのが理にかなっています。

ハッピーパスだけをテストしてください。しかし、よりよいテストによって、より低いレベルで発見できなかったリグレッションについては、必ずテストケースを追加してください(たとえば、リグレッションが発見された場合、可能な限り低いレベルでリグレッションテストを追加してください)。

テストパス テストエンジン 備考
spec/features/ カピバラ+RSpec テストに:js メタデータがある場合、ブラウザのドライバはPoltergeistになり、そうでない場合はRackTestになります。

フロントエンド機能テスト

フロントエンドのインテグレーションテストとは対照的に、機能テストはフィクスチャを使う代わりに実際のバックエンドに対してリクエストを行います。 これはデータベースのクエリが実行されることを意味し、このカテゴリを大幅に遅くします。

こちらも参照してください。

graph RL plain[Plain JavaScript]; Vue[Vue Components]; feature-flags[Feature Flags]; license-checks[License Checks]; plain---Vuex; plain---GraphQL; Vue---plain; Vue---Vuex; Vue---GraphQL; browser---plain; browser---Vue; plain---backend; Vuex---backend; GraphQL---backend; Vue---backend; backend---database; backend---feature-flags; backend---license-checks; class backend tested; class plain tested; class Vue tested; class Vuex tested; class GraphQL tested; class browser tested; linkStyle 0,1,2,3,4,5,6,7,8,9,10 stroke:#6666ff,stroke-width:2px,stroke-dasharray: 5, 5; classDef node color:#909090,fill:#f0f0f0,stroke-width:2px,stroke:#909090 classDef label stroke-width:0; classDef tested color:#000000,fill:#a0c0ff,stroke:#6666ff,stroke-width:2px,stroke-dasharray: 5, 5; subgraph " " tested; mocked; class tested tested; end

機能テストを使用するタイミング

  • バックエンドを必要とし、フィクスチャを使ってテストできないユースケース。
  • ページバンドルの一部ではなく、グローバルに定義された動作。

関連メモ

完全な環境がロードされることを確認するために、:js フラグがテストに追加されます:

scenario 'successfully', :js do
  sign_in(create(:admin))
end

各テストのステップは、(capybara methods)を使って書かれています。

XHR (XMLHttpRequest) 呼び出しでは、wait_for_requests をステップ間で使用する必要があるかもしれません:

find('.form-control').native.send_keys(:enter)

wait_for_requests

expect(page).not_to have_selector('.card')

システムテストを書かないことを検討

もし、低レベルのコンポーネントがうまく動作する自信があれば(ユニットテストとインテグレーションテストが十分であれば、そうなるはずです)、システムテストレベルでの徹底的なテストと重複する必要はないはずです。

テストを追加するのはとても簡単ですが、テストを削除したり改善したりするのはとても大変です。

これらのベストプラクティスに従うべき理由は以下の通りです:

  • システム・テストは、ヘッドレス・ブラウザでアプリケーション・スタック全体をスピンアップさせるため、実行に時間がかかり、JSドライバをインテグレーションするとさらに遅くなります。
  • システムテストを JavaScript ドライバで実行する場合、 テストはアプリケーションとは別のスレッドで実行されます。 これはデータベース接続を共有しないことを意味し、 実行中のアプリケーションがデータを見るためには、 テストはトランザクションをコミットする必要があります (逆も同様です)。 この場合、単純にトランザクションをロールバックする (他の種類のテストで使用されている、より高速な方法) のではなく、 spec ごとにデータベースを切り捨てる必要があります。 しかしこれはトランザクションよりも遅いので、 必要な場合にのみ切り捨てを使用したいものです。

システムレベルでのブラックボックステスト、別名エンドツーエンドテスト

正式な定義:

GitLabは、GitLab ShellGitLab WorkhorseGitalyGitLab PagesGitLab Runner、GitLab Railsといった複数のパーツから構成されています。 これらのパーツはすべて、OmnibusGitLabによって設定され、パッケージ化されています。

QAフレームワークとインスタンスレベルのシナリオはGitLabRailsの一部なので、コードベース(特にビュー)と常に同期しています。

注意してください:

  • アプリケーション内部の知識は必要ありません。
  • テストに必要なデータは、GUI または API を使用してのみ作成できます。
  • ブラウザのページとAPIのレスポンスに対してのみ期待できます。

すべての新機能にはテスト計画が必要です。

テストパス テストエンジン 備考
qa/qa/specs/features/ Capybara+RSpec+ カスタムQAフレームワーク テストは、対応する製品カテゴリーの下に配置する必要があります。

詳しくはエンドツーエンド・テストをご覧ください。

qa/spec は、アプリケーションのユニットテストやエンドツーエンドテストと混同しないように、QA フレームワーク自身のユニットテストを含んでいることに注意してください。

スモークテスト

スモークテストは、いつでも(特にデプロイ前のマイグレーションの後に)実行できる迅速なテストです。

これらのテストはUIに対して実行され、基本的な機能が動作していることを確認します。

詳しくはスモークテストをご覧ください。

GitLab QA オーケストレーター

GitLab QA orchestratorは、指定されたバージョンのGitLab RailsのDockerイメージをビルドし、それに対してエンドツーエンドのテスト(つまりCapybaraを使用)を実行することで、これらすべてのピースがうまくインテグレーションされていることをテストできるツールです。

詳しくはGitLab QA orchestrator READMEをご覧ください。

EE固有のテスト

EE固有のテストも同じ構成ですが、ee/spec フォルダの下にあります。

正しいレベルでテストするには?

人生で多くのことがそうであるように、テストの各レベルで何をテストするかを決めることはトレードオフです:

  • ユニットテストは通常安価であり、家の地下室のように考えるべきです。 コードが正しく動作していることを確信するためには、ユニットテストが必要です。 しかし、インテグレーションテストやシステムテストを行わずにユニットテストだけを実行すると、全体像を見逃して しまうかもしれません!
  • インテグレーションテストは少し高価ですが、乱用しないようにしましょう。 多くの内部をスタブしているインテグレーションテストよりも、システムテストの方が良い場合が多いのです。
  • システムテストは(ユニットテストに比べて)高価であり、JavaScriptドライバを必要とする場合はなおさらです。Speedセクションのガイドラインに従うようにしてください。

もう一つの見方は、「検査費用」について考えることです。これはこの記事でよく説明されていますが、基本的な考え方は、検査費用には以下が含まれるということです:

  • テストを書くのにかかる時間
  • スイートが実行されるたびにテストを実行するのにかかる時間。
  • テストを理解するのにかかる時間
  • テストが壊れ、根本的なコードに問題がない場合、修正にかかる時間。
  • もしかしたら、テスト可能なコードにするためにコードを変更するのにかかる時間かもしれません。

たとえば、スタイリング、アニメーション、エッジケース、あるいはバックエンドに関与しない小さなアクションをテストする場合、Jasmineを使ってインテグレーションテストを書くべきです。


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