- 単体テスト
- インテグレーション・テスト
- システムレベルのホワイトボックステスト (以前はシステム/機能テストとして知られていました)
- システムレベルでのブラックボックステスト、別名エンドツーエンドテスト
- EE固有のテスト
- 正しいレベルでテストするには?
テストレベル
この図は、私たちが使用する各テストタイプの相対的な優先順位を示しています。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/frontend/ | Jest | 詳細はFrontend Testing guideセクションを参照してください。 |
app/finders/ | spec/finders/ | RSpec | |
app/graphql/ | spec/graphql/ | RSpec | |
app/helpers/ | spec/helpers/ | RSpec | |
app/models/ | spec/models/ | RSpec | |
app/policies/ | spec/policies/ | RSpec | |
app/presenters/ | spec/presenters/ | RSpec | |
app/serializers/ | spec/serializers/ | RSpec | |
app/services/ | spec/services/ | RSpec | |
app/uploaders/ | spec/uploaders/ | RSpec | |
app/validators/ | spec/validators/ | RSpec | |
app/views/ | spec/views/ | RSpec | |
app/workers/ | spec/workers/ | RSpec | |
bin/ | spec/bin/ | RSpec | |
config/ | spec/config/ | RSpec | |
config/initializers/ | spec/initializers/ | RSpec | |
config/routes.rb ,config/routes/
| spec/routing/ | RSpec | |
config/puma.example.development.rb | spec/rack_servers/ | RSpec | |
db/ | spec/db/ | RSpec | |
db/{post_,}migrate/ | spec/migrations/ | RSpec | 詳細はTesting Railsマイグレーションガイドをご覧ください。 |
Gemfile |
spec/dependencies/ ,spec/sidekiq/
| RSpec | |
lib/ | spec/lib/ | RSpec | |
lib/tasks/ | spec/tasks/ | RSpec | |
rubocop/ | spec/rubocop/ | RSpec | |
spec/support/ | spec/support_specs/ | RSpec |
フロントエンドユニットテスト
ユニットテストは最も抽象度の低いテストであり、通常はユーザーが直接認識できない機能をテストします。
ユニットテストを使うタイミング
- エクスポートされた関数やクラス:エクスポートされたものはすべて、あなたがコントロールできない方法でさまざまな場所で再利用される可能性があります。公開インターフェイスの期待されるふるまいを、テストによって文書化しなければなりません。
- Vuexアクション:Vuexアクションは、どのコンポーネントからトリガーされても、一貫した方法で動作しなければなりません。
- Vuexの変異:複雑なVuexの変異については、問題解決を簡単にするために、テストをVuexストアの他の部分から分離する必要があります。
ユニットテストを使用しない場合
- エクスポートされていない関数やクラス:モジュールからエクスポートされないものは、非公開か実装の詳細とみなすことができ、テストする必要はありません。
- 定数:定数の値をテストするということは、それをコピーするということです。
- Vue コンポーネント:計算されたプロパティ、メソッド、およびライフサイクルフックは、コンポーネントの実装の詳細とみなすことができます。詳細については、Vueの公式ガイドラインを参照してください。
ユニットテストでモックするもの
- テスト対象のクラスの状態:クラスのメソッドを使うのではなく、テスト対象のクラスの状態を直接変更することで、テストのセットアップにおける副作用を避けることができます。
- 他のエクスポートクラス:テストシナリオが指数関数的に大きくなるのを防ぐため、すべてのクラスを分離してテストする必要があります。
- パラメータとして渡される単一の DOM 要素:ページ全体ではなく単独の DOM 要素に対してのみオペレーションを行うテストの場合、これらの要素を作成するほうが HTML フィクスチャ全体を読み込むよりも安上がりです。
- すべてのサーバーリクエスト:フロントエンドのユニットテストを実行するとき、バックエンドに到達できないかもしれません。
- 非同期のバックグラウンドオペレーション:バックグラウンドオペレーションは停止したり待機したりすることができないため、次のテストでも実行され続け、副作用を引き起こします。
ユニットテストでモックしてはいけないもの
- エクスポートされていない関数やクラス:エクスポートされないものはすべてモジュールの非公開とみなすことができ、エクスポートされたクラスや関数を通して暗黙のうちにテストされます。
- テスト対象のクラスのメソッド:テスト対象のクラスのメソッドをモックすることで、実際のメソッドではなくモックがテストされます。
- ユーティリティ関数 (純粋な関数、あるいはパラメータを変更するだけの関数):もしその関数に副作用がないのなら、テストではその関数をモックしないほうが安全です。
- 完全な HTML ページ:ユニットテストでフルページの HTML を読み込むのは避けましょう。
フロントエンドコンポーネントのテスト
コンポーネントテストは、ユーザー入力、他のコンポーネントから発生するイベント、あるいはアプリケーションの状態などの外部信号によって、ユーザーが認識できる単一のコンポーネントの状態を対象とします。
いつコンポーネントテストを使うか
- Vueコンポーネント
コンポーネントテストを使用しない場合
- Vue アプリケーション:Vueアプリケーションには多くのコンテナが含まれます。コンポーネントレベルでのテストは、労力がかかりすぎます。そのため、フロントエンドのインテグレーションレベルでテストします。
- HAMLテンプレート:HAMLテンプレートはマークアップのみを含み、フロントエンド側のロジックを含みません。そのため、完全なコンポーネントではありません。
コンポーネントテストでモックするもの
- 副作用:外部の状態を変化させるようなもの(たとえばネットワークリクエスト)は、すべてモックすべきです。
-
子コンポーネント:すべてのコンポーネントは個別にテストされるので、子コンポーネントはモックされるべきです。以下も参照ください
shallowMount()
コンポーネントテストでモックしてはいけないもの
- テスト対象のコンポーネントのメソッドや計算されたプロパティ:テスト対象のコンポーネントの一部をモックすることで、実際のコンポーネントではなくモックがテストされます。
- Vuex:Vuexをモックしないことで、壊れやすく偽陽性のテストを避けることができます。変異を使用してVuexを適切な状態に設定します。Vuexのアクションではなく、副作用をモックします。
インテグレーション・テスト
正式な定義:https://en.wikipedia.org/wiki/Integration_testing
この種のテストは、実際のアプリ環境(ブラウザなど)のオーバーヘッドなしに、アプリの個々の部分がうまく連動することを保証します。これらのテストは、リクエスト/レスポンスレベルでアサートしなければなりません: ステータスコード、ヘッダ、ボディ。例えば、権限、リダイレクト、APIエンドポイント、どのビューがレンダリングされるのか、などをテストするのに便利です。
コードパス | テストパス | テストエンジン | 備考 |
---|---|---|---|
app/controllers/ |
spec/requests/ ,spec/controllers
| RSpec | リクエスト仕様は、従来のコントローラ仕様よりも優先されます。API エンドポイントでは、リクエスト仕様を推奨します。 |
app/mailers/ | spec/mailers/ | RSpec | |
lib/api/ | spec/requests/api/ | RSpec | |
app/assets/javascripts/ | spec/frontend/ | Jest | 詳細は下記 |
フロントエンドのインテグレーション・テスト
インテグレーションテストは、ひとつのページにおけるすべてのコンポーネント間のインタラクションを扱います。その抽象化レベルは、ユーザーが UI とどのようにやりとりするかに匹敵します。
インテグレーションテストを使うタイミング
-
ページバンドル (
app/assets/javascripts/pages/
のindex.js
ファイル ):ページバンドルをテストすることで、対応するフロントエンドコンポーネントがうまくインテグレーションされることを保証します。 - ページバンドル以外の Vue アプリケーション:Vue アプリケーション全体をテストすることで、対応するフロントエンドコンポーネントがうまくインテグレーションされることを確認します。
インテグレーションテストでモックするもの
- HAMLビュー (代わりにフィクスチャを使いましょう):HAMLビューのレンダリングには、実行中のデータベースを含むRails環境が必要です。
- すべてのサーバリクエスト:ユニットテストやコンポーネントテストと同様に、コンポーネントテストを実行するときはバックエンドに到達できない可能性があるので、すべての送信リクエストをモックする必要があります。
- ページ上で認識できない非同期のバックグラウンドオペレーション:ページに影響を与えるバックグラウンドオペレーションは、このレベルでテストしなければなりません。他のすべてのバックグラウンドオペレーションは停止したり待機したりすることができないので、次のテストでも実行され続け、副作用を引き起こします。
インテグレーションテストでモックしてはいけないもの
- DOM :実際の DOM 上でのテストは、コンポーネントが意図した環境で動作することを保証します。DOM テストの一部は、クロスブラウザ・テストに委ねられています。
- コンポーネントのプロパティや状態 :このレベルでは、すべてのテストはユーザーが行うアクションのみを実行します。たとえば、コンポーネントの状態を変更するには、クリックイベントを発生させます。
- Vuexのストア :ページ全体のフロントエンドコードをテストする場合、VueコンポーネントとVuexストアの相互作用も対象となります。
コントローラのテストについて
GitLabはcontroller specからrequest specに移行しつつあります。
理想的な世界では、コントローラは薄いものであるべきです。しかし、そうでない場合は、コントローラのテストの代わりに JavaScript を使わないシステムテストや機能テストを書くこともできます。ファットコントローラのテストでは、通常、次のようなスタブを多用します:
controller.instance_variable_set(:@user, user)
やRails 5で非推奨になったメソッドを使うようなテストです。
システムレベルのホワイトボックステスト (以前はシステム/機能テストとして知られていました)
正式な定義:
この種のテストは、GitLabRailsアプリケーション(例えば、gitlab-foss
/gitlab
)がブラウザの観点から期待通りに動作することを保証します。
注意してください:
- アプリケーション内部の知識が必要です。
- テストに必要なデータは、通常 RSpec ファクトリを使用して直接作成します。
- 期待値は、多くの場合データベースやオブジェクトの状態に対して設定されます。
これらのテストは、次のような場合にのみ使用します:
- テストされる機能/コンポーネントが小さい場合。
- オブジェクト/データベースの内部状態をテストする必要がある場合。
- 下位レベルではテストできません
たとえば、あるページのパンくずをテストするために、システムテストを書くことは理にかなっています。
ハッピーパスのみをテストしますが、よりよいテストでより低いレベルで捕らえられなかったリグレッションのテストケースを追加するようにしてください (たとえば、リグレッションが見つかった場合、リグレッションテストは可能な限り低いレベルで追加すべきです)。
テストパス | テストエンジン | 備考 |
---|---|---|
spec/features/ | カピバラ+RSpec | テストに:js メタデータがある場合、ブラウザのドライバはPoltergeistになります。 |
フロントエンド機能テスト
フロントエンドのインテグレーションテストとは対照的に、機能テストはフィクスチャを使う代わりに実際のバックエンドに対してリクエストを行います。これはデータベースのクエリが実行されることを意味し、このカテゴリを大幅に遅くします。
こちらも参照してください。
- RSpec テストガイドライン
- テストのベストプラクティスのシステム/機能テスト。
機能テストを使うとき
- バックエンドを必要とし、フィクスチャを使ってテストできないユースケース。
- ページバンドルの一部ではなく、グローバルに定義されたふるまい。
関連する注意事項
完全な環境がロードされていることを確認するために、:js
フラグがテストに追加されています:
scenario 'successfully', :js do
sign_in(create(:admin))
end
各テストの手順は、(Capybaraメソッド)を使って書かれています。
XHR(XMLHttpRequest)呼び出しでは、ステップ間でwait_for_requests
:
find('.form-control').native.send_keys(:enter)
wait_for_requests
expect(page).not_to have_selector('.card')
システムテストを書かないことを検討してください
もし低レベルのコンポーネントがうまく動作する自信があるのであれば(ユニットテストとインテグレーションテストが十分であればそうすべきです)、システムテストレベルでの徹底的なテストと重複する必要はありません。
テストを追加するのはとても簡単ですが、削除したり改善したりするのはとても大変です。
これらのベストプラクティスに従うべき理由は、次のとおりです:
- システムテストは、ヘッドレスブラウザでアプリケーションスタック全体をスピンアップするため、実行に時間が かかります。
- システムテストを JavaScript ドライバで実行すると、 テストはアプリケーションとは別のスレッドで実行されます。これはデータベース接続を共有しないことを意味し、実行中のアプリケーションがデータを見るためには、 テストがトランザクションをコミットしなければなりません (逆も同様です)。この場合、トランザクションをロールバックするのではなく、 spec ごとにデータベースを切り捨てる必要があります (他の種類のテストでは、このほうが高速です)。しかしこれはトランザクションよりも遅いので、必要なときだけ切り捨てるようにしましょう。
システムレベルでのブラックボックステスト、別名エンドツーエンドテスト
正式な定義:
GitLabはGitLab Shell、GitLab Workhorse、Gitaly、GitLab Pages、GitLab Runner、GitLab Railsといった複数のピースから構成されています。これら全てのピースはOmnibus GitLabによって設定され、パッケージ化されています。
QAフレームワークとインスタンスレベルのシナリオはGitLab Railsの一部であり、コードベース(特にビュー)と常に同期しています。
注意してください:
- アプリケーション内部の知識は必要ありません。
- テストに必要なデータは、GUI または API を使用してのみ作成できます。
- ブラウザのページとAPIのレスポンスに対してのみ期待できます。
すべての新機能にはテスト計画が必要です。
テストパス | テストエンジン | 備考 |
---|---|---|
qa/qa/specs/features/ | Capybara+RSpec+ カスタムQAフレームワーク | テストは、対応する製品カテゴリの下に配置する必要があります。 |
詳細については、エンドツーエンド・テストを参照してください。
qa/spec
には QA フレームワーク自身のユニットテストが含まれており、アプリケーションのユニットテストや エンドツーエンドテストと混同しないように注意してください。
スモークテスト
スモークテストは、いつでも (特にデプロイ前のマイグレーションの後に) 実行できる簡単なテストです。
これらのテストはUIに対して実行され、基本的な機能が動作していることを確認します。
詳細はSmoke Testsを参照してください。
GitLab QA オーケストレーター
GitLab QA orchestratorは、指定したバージョンのGitLab RailsのDockerイメージをビルドし、それに対して(Capybaraを使って)エンドツーエンドのテストを実行することで、これらすべてのピースがうまくインテグレーションされていることをテストできるツールです。
詳しくはGitLab QA orchestrator READMEをご覧ください。
EE固有のテスト
EE固有のテストも同じ構成ですが、ee/spec
フォルダの下にあります。
正しいレベルでテストするには?
人生で多くのことがそうであるように、テストの各レベルで何をテストするかを決めることはトレードオフです:
- ユニットテストは通常安価なので、家の地下室のように考えるべきです。あなたのコードが正しく動作していることを確信するために必要です。しかし、インテグレーションテストやシステムテストを行わずにユニットテストだけを実行すると、全体 像を見逃してしまうかもしれません!
- インテグレーションテストは少し高価ですが、乱用しないようにしましょう。システムテストは、多くの内部をスタブしているインテグレーションテストよりも優れていることがよくあります。
- システムテストは (ユニットテストに比べて) 高価で、JavaScript のドライバが必要ならなおさらです。スピードのセクションのガイドラインに従うようにしてください。
別の見方として、”テストのコスト” について考えてみましょう:
- テストを書くのにかかる時間
- スイートが実行されるたびにテストを実行するのにかかる時間。
- テストを理解するのにかかる時間
- テストが壊れ、根本的なコードに問題がない場合、それを修正するのにかかる時間
- もしかしたら、テスト可能なコードにするためにコードを変更するのにかかる時間。
フロントエンド関連のテスト
たとえば、スタイリング、アニメーション、エッジケース、もしくはバックエンドに関与しない小さなアクションをテストする場合、フロントエンドのインテグレーションテストを使うべきです。