こんにちは、mizzy です。業務委託でアカツキさんのお手伝いをしています。お手伝いの一環として、aktsk/atgen という、Go用HTTP APIテストコードジェネレータを開発しています。
この記事では、Atgenの概要や開発の背景、今後の予定などについてご紹介します。
Atgen とは
HTTP API Test Code Generatorの略で、HTTP APIをテストするためのコードを自動生成する、Go製のコマンドラインツールです。
YAMLで記述されたテストすべきHTTPリクエスト/レスポンスと、Goで書かれたテストコードのテンプレートから、実際にテストを実行するためのコードを生成します。
atgen/example at master · aktsk/atgen に動かすことができるサンプルがありますので、READMEの通りに動かしてみると、どんなツールなのか掴んでいただけるかと思います。
なぜつくっているのか
Goはその性質上、コードが冗長になりがちです。特に、HTTP APIをテストする場合、リクエストを出してレスポンスをチェックする、という同じような記述のテストコードをたくさん書くことになり、更に冗長になってしまいます。
コードが冗長になると、コピペも多くなりますが、コピペした後に修正すべき部分の修正が漏れることもあります。テストが失敗すれば修正漏れに気づくこともできますが、通ってしまうと漏れに気づかず、誤った内容でテストしているがそれに気づけない、ということになりかねません。
また、テストコードが冗長だと、全体の見通しが悪くなり、どのエンドポイントがテストされていて、どのエンドポイントがテストされていないのか、把握するのが難しくなります。
そこで、同じようなコードを書く手間を省き、コピペによるミスを防ぎ、APIエンドポイントのどこまでをカバーできているのかを把握しやすくするために、Atgenを開発することにしました。
開発にあたって検討したこと
このツールが、なぜこのようなつくりになっているのか、はコードを読んでもわからないですし、作者の自分も忘れそうなので、ここに記録しておきます。
YAMLにしたがってテストを実行するのではなく、テストするコードを生成するのはなぜか
ツールの方向性として、YAMLに基づいてテストコードを生成するのではなく、直接テストを実行する、という方向性も考えました。しかし、一度テストコードに落とし込んでから実行することで、テストが失敗した場合に、該当するコードを直接読むことができ、原因を調査しやすいだろうと考え、テストを直接実行するツールではなく、テストコードを生成するツールにしました。
テンプレート処理にtext/templateを使わずにAST書き換えを行っているのはなぜか
AST書き換えをせずにtext/templateを使った方がツール自体の実装は簡単です。しかし、{{ .Var }}
のようなtext/template記法がテンプレートコード中に入ると、Go的には不正な構文となるため、go fmt
でエラーになるなど、ツールやエディタの言語サポート機能を活かせなくなり、テンプレートコードを書く時ににとても不便です。なので、 text/templateは使わずにAST書き換えで処理をする、という形にしました。
あと、私がAST書き換えを行うようなツールを一度作ってみたいと思っていたから、という理由もあります。
テスト対象のリクエスト/レスポンスの定義にOpenAPI準拠のYAML/JSONを使わないのはなぜか
Atgenを導入しているプロジェクトでは、swagger.yamlでAPIの仕様が定義されているので、これをテストの定義にも使い回せないか、と最初は考えました。しかし、仕様の記述とテストの記述は似ているようで違っていて、特に以下の点が異なると考えています。
- リクエストやレスポンスのパラメータの違い
- 仕様ではパラメータの型が定義されるが、具体的な値は定義されない
- テストでは具体的な値を記述する必要がある
- コンテキストの有無の違い
- 仕様では各リクエスト/レスポンスが独立した形で記述される
- テストでは、このパラメータでPOSTしてから、GETするとこういうパラメータが返ってくる、みたいな形で、複数のリクエスト/レスポンスの関連性を記述する必要がある
このような違いがあるため、OpenAPI準拠のYAMLでテストの定義までカバーするのは無理がありそうだ、と判断して、無理に寄せることはせずに、あえて別のフォーマットにしています。
また、Go用のツールなので、Goの慣習にしたがい、Goの機能を活かしたコードを生成できるようなフォーマットにしたい、という理由もあります。
実戦投入してみての所感
既存プロジェクトのテストコードとほぼ同等なコードをこのツールで生成できるようになったので、生成したテストコードをCircleCIで回すようにしてみました。実践投入してまだ間もないですが、現在のところの所感は以下の通りです。
- テスト定義をYAMLで記述することで、テスト項目全体の見通しがよくなり、APIエンドポイントのどこまでカバーされているのか、把握しやすくなる。
- 見通しがよくなることで、レビュアーがレビューしやすくなり、テストの間違いや漏れを見つけやすくなる。
- 反面、前処理などテストによって微妙に処理が異なるところがあり、ひとつのテンプレートで吸収しようとすると、ifでの条件分岐が多くなり、コードの見通しが悪くなる。
- 書き換えのためのルールを把握してテンプレートコードを記述しないといけないので、普通にテストコードを書くよりも面倒。
- このように、テスト項目のメンテナビリティは向上するが、コードのメンテナビリティは低下する。
- が、テストは書くことよりもメンテナンスすることの方が大変なので、トータルで見ると運用コストを下げてくれそう。
- テスト項目のメンテナビリティを保ちつつ、コードのメンテナビリティをいかに下げるか、という方向でツールを改善していくのが今後の課題。
今後の予定
まだツールとしては発展途上で、やることはたくさんありますが、以下の様な方向で考えています。
- ドキュメントの充実
- 特に書き替えルールがわかりにくいので、その辺りを重点的に。
- 使い方をわかりやすくする
- YAMLだけでできる範囲を広げ、できるだけコードを書く量を減らす。
- OpenAPI準拠YAML/JSONとの連携
- OpenAPI準拠YAML/JSONとテスト定義YAMLを突き合わせ、テストされてないエンドポイントを出力する、といった機能
または、もっと違う方向性で実装しなおした方が良いのでは、とも考えています。というのも、Atgenは汎用的に使えるようにするため、テスト対象のAPIサーバのコードには一切関知しない、というスタンスで実装しています。ですが、APIサーバのコード内で定義されている型などをうまく利用した方が、よりわかりやすい形でテストコードが書けます。とは言え、テストコードジェネレータが特定のAPIサーバのコードに依存してしまうと、汎用性が失われてしまいます。これを解決するために、テストコードジェネレータではなく、テストコードジェネレータの開発を支援するライブラリとして実装する、といった方向性もあるのではないか、とぼんやりとですが考えています。
謝辞
ツールの実装にあたって、tenntenn さんによる Go AST に関する Qiita 記事 や motemen さんによる GoのためのGo がとても参考になりました。この場を借りてお礼を申し上げます。