GitLab QAのリソースクラス
リソースは主にブラウザUIのステップを使用して作成されますが、APIやCLIを使用して作成することもできます。
リソースクラスを適切に実装するには?
すべてのリソースクラスはResource::Base
を継承する必要があります。
リソースクラスを定義するために実装が必須なメソッドは1つだけです。 これは#fabricate!
メソッドで、ブラウザのUIを介してリソースを構築するために使用されます。 このメソッドでは、Webページと対話するためにPageオブジェクトのみを使用する必要があることに注意してください。
これは想像上の例です:
module QA
module Resource
class Shirt < Base
attr_accessor :name
def fabricate!
Page::Dashboard::Index.perform do |dashboard_index|
dashboard_index.go_to_new_shirt
end
Page::Shirt::New.perform do |shirt_new|
shirt_new.set_name(name)
shirt_new.create_shirt!
end
end
end
end
end
API実装の定義
リソースクラスは、公開GitLab API経由でリソースを作成できるように、以下の3つのメソッドを実装することもできます:
-
#api_get_path
既存のリソースを取得するためのGET
パス。 -
#api_post_path
新しいリソースを作成するためのPOST
パス。 -
#api_post_body
: 新しいリソースを作成するためのPOST
本体 (Ruby ハッシュ)。
多くのAPIリソースがページ分割されていることに注意してください。 期待する結果が見つからない場合は、複数のページがあるかどうかを確認してください。
Shirt
リソース・クラスを使って、これら3つのAPIメソッドを追加してみましょう:
module QA
module Resource
class Shirt < Base
attr_accessor :name
def fabricate!
# ... same as before
end
def api_get_path
"/shirt/#{name}"
end
def api_post_path
"/shirts"
end
def api_post_body
{
name: name
}
end
end
end
end
Project
リソースは、ブラウザ UI と API 実装の良い実例です。
リソース属性
例えば、プロジェクトにはグループが必要です。
リソース属性を定義するには、attribute
メソッドを、他のリソースクラスを使用するブロックと一緒に使用して、リソースを製作します。
これにより、リソースオブジェクトのメソッドから他のリソースにアクセスできるようになります。 通常は#fabricate!
,#api_get_path
,#api_post_path
,#api_post_body
で使用します。
Shirt
リソース・クラスに、project
属性を追加してみましょう:
module QA
module Resource
class Shirt < Base
attr_accessor :name
attribute :project do
Project.fabricate! do |resource|
resource.name = 'project-to-create-a-shirt'
end
end
def fabricate!
project.visit!
Page::Project::Show.perform do |project_show|
project_show.go_to_new_shirt
end
Page::Shirt::New.perform do |shirt_new|
shirt_new.set_name(name)
shirt_new.create_shirt!
end
end
def api_get_path
"/project/#{project.path}/shirt/#{name}"
end
def api_post_path
"/project/#{project.path}/shirts"
end
def api_post_body
{
name: name
}
end
end
end
end
すべてのアトリビュートは遅延的に構築されることに注意してください。 つまり、特定のアトリビュートを最初に構築したい場合は、そのアトリビュートを使用していなくても、最初にアトリビュート・メソッドを呼び出す必要があります。
製品データ属性
一度作成したリソースに、WebページやAPIレスポンスで確認できる属性を設定したい場合があります。 例えば、プロジェクトを作成したら、そのリポジトリのSSH URLを属性として保存したい場合があります。
ここでも、attribute
メソッドをブロックで使用し、ページオブジェクトを使用してページ上のデータを取得することができます。
Shirt
リソース・クラスを例に、:brand
属性を定義してみましょう:
module QA
module Resource
class Shirt < Base
attr_accessor :name
attribute :project do
Project.fabricate! do |resource|
resource.name = 'project-to-create-a-shirt'
end
end
# Attribute populated from the Browser UI (using the block)
attribute :brand do
Page::Shirt::Show.perform do |shirt_show|
shirt_show.fetch_brand_from_page
end
end
# ... same as before
end
end
end
すべての属性が遅延的に構築されていることに再度注意してください。 つまり、他のページに移動した後にshirt.brand
を呼び出すと、期待したページではなくなってしまうため、正しくデータを取得できません。
考えてみてください:
shirt =
QA::Resource::Shirt.fabricate! do |resource|
resource.name = "GitLab QA"
end
shirt.project.visit!
shirt.brand # => FAIL!
上記の例は失敗します。なぜなら、現在プロジェクトページにいて、シャツページからブランドデータを構築しようとしているからです。 これを解決する方法は2つあります。1つは、プロジェクトに再度アクセスする前にブランドを取得することです:
shirt =
QA::Resource::Shirt.fabricate! do |resource|
resource.name = "GitLab QA"
end
shirt.brand # => OK!
shirt.project.visit!
shirt.brand # => OK!
この属性はインスタンスに保存されるため、次の呼び出しはすべて、以前に構築されたデータを使用して問題ありません。 これがあまりにもろいと思われる場合は、ファブリケーションを終了する直前にデータをeagerlyに構築することもできます:
module QA
module Resource
class Shirt < Base
# ... same as before
def fabricate!
project.visit!
Page::Project::Show.perform do |project_show|
project_show.go_to_new_shirt
end
Page::Shirt::New.perform do |shirt_new|
shirt_new.set_name(name)
shirt_new.create_shirt!
end
populate(:brand) # Eagerly construct the data
end
end
end
end
populate
メソッドは引数を繰り返し、各属性をそれぞれ呼び出します。ここでpopulate(:brand)
はbrand
と同じ効果をもたらします。 populate メソッドを使うことで、意図がより明確になります。
欠点は、データを使用する必要がない場合でも、リソースが作成されるときに常にデータを作成することです。
あるいは、ブランドデータを作成する前に、正しいページであることを確認することもできます:
module QA
module Resource
class Shirt < Base
attr_accessor :name
attribute :project do
Project.fabricate! do |resource|
resource.name = 'project-to-create-a-shirt'
end
end
# Attribute populated from the Browser UI (using the block)
attribute :brand do
back_url = current_url
visit!
Page::Shirt::Show.perform do |shirt_show|
shirt_show.fetch_brand_from_page
end
visit(back_url)
end
# ... same as before
end
end
end
これは、ブランドを構築する前にシャツのページにあることを確認し、状態を壊さないように前のページに戻ります。
APIレスポンスに基づく属性の定義
GET
またはPOST
リクエストからの API レスポンスに基づいて、リソース属性を定義したい場合があります。 例えば、API 経由でシャツを作成すると、次のように返されます。
{
brand: 'a-brand-new-brand',
style: 't-shirt',
materials: [[:cotton, 80], [:polyamide, 20]]
}
の場合、style
をそのままリソースに保存し、main_fabric
属性で最初のmaterials
項目の最初の値をフェッチしたいと思うかもしれません。
Shirt
リソース・クラスを取り上げ、:style
と:main_fabric
属性を定義してみましょう:
module QA
module Resource
class Shirt < Base
# ... same as before
# @style from the instance if present,
# or fetched from the API response if present,
# or a QA::Resource::Base::NoValueError is raised otherwise
attribute :style
# If @main_fabric is not present,
# and if the API does not contain this field, this block will be
# used to construct the value based on the API response, and
# store the result in @main_fabric
attribute :main_fabric do
api_response.&dig(:materials, 0, 0)
end
# ... same as before
end
end
end
属性の優先順位に関する注意事項
- リソースインスタンス変数が最優先されます。
- APIレスポンスからの属性は、ブロックからの属性(通常はブラウザUIからの属性)よりも優先されます。
- 属性に値がない場合、
QA::Resource::Base::NoValueError
エラーが発生します。
テストでのリソースの作成
テストの中でリソースを作成するには、 リソースクラスの.fabricate!
メソッドをコールします。 リソースクラスが API の作成に対応している場合は、 デフォルトでこのメソッドが使用されることに注意しましょう。
ここでは、Shirt
リソース・クラスでサポートされている API ファブリケーション・メソッドを内部で使用する例を示します:
my_shirt = Resource::Shirt.fabricate! do |shirt|
shirt.name = 'my-shirt'
end
expect(page).to have_text(my_shirt.name) # => "my-shirt" from the resource's instance variable
expect(page).to have_text(my_shirt.brand) # => "a-brand-new-brand" from the API response
expect(page).to have_text(my_shirt.style) # => "t-shirt" from the API response
expect(page).to have_text(my_shirt.main_fabric) # => "cotton" from the API response via the block
明示的にブラウザUIファブリケーションメソッドを使用したい場合は、代わりに.fabricate_via_browser_ui!
:
my_shirt = Resource::Shirt.fabricate_via_browser_ui! do |shirt|
shirt.name = 'my-shirt'
end
expect(page).to have_text(my_shirt.name) # => "my-shirt" from the resource's instance variable
expect(page).to have_text(my_shirt.brand) # => the brand name fetched from the `Page::Shirt::Show` page via the block
expect(page).to have_text(my_shirt.style) # => QA::Resource::Base::NoValueError will be raised because no API response nor a block is provided
expect(page).to have_text(my_shirt.main_fabric) # => QA::Resource::Base::NoValueError will be raised because no API response and the block didn't provide a value (because it's also based on the API response)
また、.fabricate_via_api!
メソッドを呼び出すことで、API ファブリケーション・メソッドを明示的に使用することもできます:
my_shirt = Resource::Shirt.fabricate_via_api! do |shirt|
shirt.name = 'my-shirt'
end
この場合、結果はResource::Shirt.fabricate!
を呼び出すのと同じようになります。
どこに助けを求めますか?
より詳しい情報が必要な場合は、Slack の#quality
チャンネルで助けを求めてください(内部、GitLab チームのみ)。
チームメンバーでなくても貢献したい場合は、GitLab CE issue trackerに~QA
のラベルを付けてイシューを登録してください。