81
61

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

GraphQLのクエリを自動的にテストする

Last updated at Posted at 2020-03-16

こんにちは。株式会社ビットジャーニーでエンジニアをしてる@pockeです。
ビットジャーニーではKibelaというWebアプリケーションを開発しています。

KibelaではPublicなWeb APIにGraphQLを使用して提供しています。
https://github.com/kibela/kibela-api-v1-document
また、このAPIはKibelaのWebアプリケーションの内部でも同じものが使用されています。

先日、このGraphQL APIを自動的に(ある程度)網羅的にテストするためのgem graphql-autotestをリリースしました。
https://github.com/bitjourney/graphql-autotest
この記事ではこのgraphql-autotestを紹介します。

なおgraphql-autotestはRubyで書かれていますが、任意の言語で実装されたGraphQLサーバーをテスト可能です(Rubyのコードを多少書く必要はありますが)。

「網羅的なテスト」とは

graphql-autotestの目的は、GraphQLのクエリをある程度網羅的にテストすることです。

私達は普段GraphQL APIを開発する際にユニットテストを書いています。
理想論を言えばすべてのフィールドにユニットテストを書きたいところですが、しばしば漏れが生じます。

graphql-autotestはそのような漏れを拾い上げるようなテストです。
まず、スキーマ定義から可能な限りすべてのフィールドを取得するようなクエリを自動的に書き出します。
そしてそのクエリを実行し、エラーが出ないことを確認します。

ポイントは、クエリを自動的に書き出す点です。
クエリの生成を自動化することで、新規に追加したフィールドにユニットテストを書き忘れていたとしても、graphql-autotestでそのフィールドをテストできます。

ただし、デフォルトではgraphql-autotestはクエリの実行がエラーを出さないことのみをチェックします。
「GraphQLサーバーの実装にtypoがあってNoMethodErrorが出ていた」と言ったケースは検出できますが、「1が返るべきところで2が返っていた」と言ったバグは検出できません。
ただし、クエリを実行したレスポンスに対して、それを検証する適当なコードを書いてやればある程度はテストできるでしょう。

なお、クエリの実行はgraphql gemを使っている場合にのみ可能です。Ruby以外の言語でGraphQLサーバーが実装されている場合は、生成されたクエリを実行する部分は別に実装する必要があります。

使い方

では、使い方を見ていきましょう。

Installation

まずこのgemをインストールします。

$ gem install graphql-autotest

もしくはBundlerを使っている場合、次のコードをGemfileに足してください。

Gemfile
gem 'graphql-autotest'

設定例

graphql gemを使っている場合

まずは、graphql gemを使っている場合の一番シンプルなコードを紹介します。

require 'graphql/autotest'

class YourSchema < GraphQL::Schema
end

runner = GraphQL::Autotest::Runner.new(
  schema: YourSchema,
  context: { current_user: User.first },
)

runner.report!

まず、YourSchemaとしてスキーマ定義をしています。
これは自身のスキーマ定義に置き換えてください。

そして、GraphQL::Autotest::Runnerのインスタンスを生成し、report!でクエリを生成、実行しています。
Runner.newの引数には定義したYourSchemaと、contextを渡しています。このcontextYourSchema.executeを呼ぶ際に渡されます。

これだけでスキーマ定義からクエリを自動生成して、実行し、エラーがないかをテストできます。
ただし、実用上ではデフォルトの設定では充分な結果が得られないことがあるでしょう。そのため後述するarguments_fetcherなどの設定が必要です。

graphql gemを使っていない場合

冒頭でも説明したとおり、このgemはgraphql gemを使ってサーバーを実装している場合以外でもクエリの自動生成ができます(クエリの実行はできません)。

クエリを自動生成するには、 https://github.com/kibela/kibela-api-v1-document/blob/master/schema.graphql のようなスキーマ定義が必要です。
このスキーマ定義がpath/to/schema.graphqlに保存されている場合、次のように書くとクエリを生成できます。

require 'graphql/autotest'

fields = GraphQL::Autotest::QueryGenerator.from_file(path: 'path/to/schema.graphql')

# 生成したクエリがすべて表示される
fields.each do |field|
  puts field.to_query
end

生成したクエリは適応なGraphQLクライアントに渡して実行すると良いでしょう。

設定方法

ここまでで、デフォルトの設定でクエリを生成、実行する方法を解説しました。
ですが前述したとおり、デフォルトの設定では不十分なケースもあります(Kibelaの場合はそうです)。
そのためgraphql-autotestではいくつかの設定項目を用意しています。

以下にその設定項目を解説します。

arguments_fetcher

arguments_fetcherでは、フィールドが引数を受け取る場合にどのような引数を渡すかを定義します。
arguments_fetcherはProcを指定します。指定したProcはフィールドの情報を引数として呼ばれ、Hashを返せばそれが引数として使われます。

デフォルトではgraphql-autotestでは必須の引数がない(== 引数なしで呼べる)フィールドのみをクエリとして書き出します。
そのため、必須の引数があるフィールドを取得するためにはarguments_fetcherを使用して、どのような引数を渡したら良いかを定義します。
また必須でない引数でも、必要があれば指定できます。

たとえば、KibelaではRelay Connectionに対するfirstlast引数が必須となっています。
このfirstを埋めるようなfetch_argumentsの定義は次のようになります。

fill_first = proc do |field|
  field.arguments.any? { |arg| arg.name == 'first' } && { first: 5 }
end

fields = GraphQL::Autotest::QueryGenerator.from_file(
  path: 'path/to/schema.graphql',
  arguments_fetcher: GraphQL::Autotest::ArgumentsFetcher.combine(
    fill_first,
    GraphQL::Autotest::ArgumentsFetcher::DEFAULT,
  ),
)

このarguments_fetcherをより設定することで、フィールドの網羅性を上げることができます。

skip_if

skip_ifでは、取得したくないフィールドをスキップできます。
どうしても自動的なクエリの生成ではエラーが出てしまうようなフィールドを無視するのに使えるでしょう。

skip_ifもProcを指定します。このProcはフィールドの情報を引数に呼ばれ、truthyな値を返せばそのフィールドはスキップされます。

skip_if = proc do |field|
  field.name == 'sensitiveField'
end

fields = GraphQL::Autotest::QueryGenerator.from_file(
  path: 'path/to/schema.graphql',
  skip_if: skip_if,
)

max_depth

max_depthには、クエリの最大の深さを指定します。

graphql-autotestではフィールドの循環参照があった場合に無限ループにならないよう制御しているため、理論上はmax_depthを指定しなくてもクエリの実行は終了します。
とはいえクエリが深すぎる場合、クエリの生成や実行に時間がかかってしまいます。
その場合はmax_depthオプションを使用して、深さを減らしてください。

デフォルトのmax_depthは10です。この値は適当です。

fields = GraphQL::Autotest::QueryGenerator.from_file(
  path: 'path/to/schema.graphql',
  max_depth: 5,
)

まとめ

GraphQLの網羅的なテストを実行するためのGemであるgraphql-autotestを紹介しました。

実際にKibelaではこのgemを実装する過程で複数のバグを発見しました。
バグがあったのはいずれもKibelaのWebアプリケーションでは使っていないフィールドでした。ですが、ユーザーに公開されているAPIでも取得されうるフィールドであることを考えると早めにバグを発見できたのは良かったと思います。

graphql-autotestを使って、GraphQLサーバーのテストを実施していただければ幸いです。

81
61
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
81
61

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?