Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

Go用HTTP APIテストコードジェネレータを開発しているというお話

こんにちは、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 がとても参考になりました。この場を借りてお礼を申し上げます。