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

開発環境インフラを ECS 移行している話

 この記事は Akatsuki Advent Calendar 2018 の24日目の記事です。 前回は id:NeoCat さんのElixirのサーバアプリケーションをDatadog APMでトレースするでした。

はじめに

はじめまして、運用中ゲームタイトルでサーバーエンジニアをしている守屋と申します。 私の所属するチームでは AWS EC2 上でサーバーサイドの運用を行ってきました。サービスリリース当初からの様々な改善の積み重ねの結果、近頃はインフラ障害も少なくなり、安定した運用が実現しています。しかし安定した運用が実現すればサーバーエンジニアの仕事は終わりなのでしょうか?安定しているとはいえ既存インフラには様々な技術的な負債が蓄積されています。我々サーバーチームは既存インフラを設計から見直し、新環境へ全てを移行する一大決心を下したのです。

続きを読む

ElixirのサーバアプリケーションをDatadog APMでトレースする

この記事は Akatsuki Advent Calendar 2018 の23日目の記事です。 前回は id:yunon_phys さんの、エンジニア組織の責任範囲の透明性をRACI図で高めてみた でした。

はじめに

アカツキではElixirを使ってゲームのAPIサーバを開発・運用しています。 ゲームのAPIサーバは、大量のリクエストを低いレイテンシで捌くことが要求されるため、Erlang VMの高いスケーラビリティが利用できるのは効果的です。加えてRubyなどを書き慣れている人にとっつきやすいElixirの文法も魅力です。

とはいえ、その性能を引き出すためには、やはりアプリケーションのパフォーマンスチューニングは不可欠です。 その際、"Don't guess, measure" という言葉の通り、どこを改善すれば良いかを知るための良い計測ツールが必要になります。

これまでRuby on Railsのアプリケーションでは、計測・監視のためにNew RelicをAPM (Application Performance Monitoring) ツールとして使っていたのですが、現在New Relicでは公式にはElixirはサポートされていません。*1

そこで今回は、インフラの監視に利用していたDatadogにAPMも統合できることを期待して、Datadog APMを利用してみることにしました。

Datadog APMでは単なる集計値のみならず、タイミングの記録にも対応しており、下記のように個々のAPI内の処理内容をフレームグラフとして見ることも可能なため、性能改善には有効そうです。

f:id:NeoCat:20181215201740p:plain

*1:コミュニティによるライブラリは開発されていますが、Phoenixを前提としているものが多いようです。後述の通り、今回の対象アプリケーションはPhoenixを使用していないため、採用は見送りました。

続きを読む

エンジニア組織の責任範囲の透明性をRACI図で高めてみた

こんにちは、ゆのん(id:yunon_phys)です。この記事は Akatsuki Advent Calendar 2018 の22日目の記事です。 前日は@kackytwさんのDQNの学習速度を改善するでした。

Engineering Managerのjob descriptionを共有する流れ

近年、Engineering Manager(EM)業界が賑わってきています。特に2018年は非常に活発化した年でした。書籍としては、エンジニアリング組織論への招待エンジニアのためのマネジメントキャリアパスという名著が生まれました。また、Engineering Manager Meetupが3回開催され、Slack workspaceでは12/22時点で268人となっています。EMのためのPodcast EM.FMも誕生し、総再生回数が10,000回を突破するなど、多くの方から注目を集めています。*1

*1:このPodcastはエンジニアリング組織論への招待の著者である広木さんと、私がやっています

続きを読む

UnityでAPK拡張ファイルを利用するときのあれこれ

こんにちは。このブログでははじめまして。クライアントエンジニアの下村です。

この記事は Akatsuki Advent Calendar 2018 の19日目の記事です。 
前回は kidach1 さんの ニューラルネットワークでStyle Transferを行う論文の紹介 でした。

背景

この記事を開いた方にはご存知の事かと思いますが、Google Play Storeではアプリケーションパッケージのサイズに制限があり、100MBを超えるapkファイルをアップロードすることができません。 100MBを超える大きなアプリを提供したい場合は、APK拡張ファイルと呼ばれる別のパッケージにデータを分割する必要があります。

インターネット接続を前提とするソーシャルゲームでこの制限に引っかかることはそう多くありませんが、最近たまたまこれを利用する機会があったので備忘録を兼ねて知見を共有したいと思います。

UnityでAPK拡張ファイルを出力する

UnityでAPK拡張ファイルを出力する方法は非常に簡単です。 PlayerSettingsの Split Application Binary オプションにチェックを入れてビルドすると、apkファイルと一緒にobbファイルが生成されます。 Unityは分割されたファイルから自動的にアセットを読み分けるため、Unityの実装上で分割を意識する必要はほとんどありません。

分割したアプリを手動でインストールする

Build And Run を実行するとUnityは自動的にobbファイルをインストールしてくれます。 一方手動でアプリをインストールする場合は、 apkファイルに加えてobbファイルを特定の場所に配置する必要があります。

obbファイルの名前や置き場所は決まっていて、以下のようなパスに配置する必要があります。
外部ストレージのAndroidディレクトリは端末によってパスが異なる事がある点に注意してください。

/storage/emulated/0/Android/obb/<パッケージ名>/main.<ビルド番号>.<パッケージ名>.obb

ファイルの転送には adb push コマンドを使うのが便利です。

$ adb install App.apk
$ adb push App.main.obb /storage/emulated/0/Android/obb/jp.aktsk.testapp/main.1.jp.aktsk.testapp.obb

これで分割されたアプリをインストールすることができました。

APK拡張ファイルのバリデーション

obbファイルは通常apkファイルと一緒にインストールされますが、実態は外部ストレージに配置されるいちファイルに過ぎません。 アプリ起動時にファイルが存在しないという状況が起こりえることから、Googleはアプリ起動時にAPK拡張ファイルのチェックや再ダウンロードを行うよう喚起しています。

Googleからはobbファイルをバックグラウンドでダウンロードするライブラリが公開されていますが、今回は急ぎで対応が必要だったことからこの高機能なダウンローダーを実装して詳しく検証している時間がありませんでした。 幸い分割されたデータはそう大きくなく、最近ではダウンロード漏れもかなり稀な発生率であるらしいことから、起動時に簡単なファイルチェックのみを行うことにしました。

APK拡張ファイルをチェックするアクティビティを挟む

今回のアプリではUnityの起動直後にいくつものアセットを読み込んでいたため、UnityPlayerActivityが起動する前にobbファイルの存在チェックをする必要がありました。 そこでonStartでobbファイルの存在をチェックをするActivityを新しく作成します。

public class ExpansionFileCheckActivity extends Activity {
    @Override
    protected void onStart() {
        super.onStart();

        if (getMainObbFile().canRead()) {
            // UnityPlayerActivityを起動する
            return;
        }

        // エラーを表示してアプリを終了する
    }

    private File getMainObbFile() {
        String fileName = "main." + getVersionCode() + "." + getPackageName() + ".obb";
        return new File(getObbDir(), fileName);
    }

    private long getVersionCode() {
        try {
            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
            return PackageInfoCompat.getLongVersionCode(packageInfo);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            return 0;
        }
    }

    // (省略)
}

getObbDir() でobbファイルの配置されるディレクトリを取得できますが、ファイル名は自分で生成する必要があります。 Activityを追加したら、AndroidManifestを修正して最初に起動するActivityを置き換えます。

<activity android:name="jp.aktsk.testapp.ExpansionFileCheckActivity" android:label="@string/app_name" android:launchMode="singleTask" android:theme="@style/Theme.AppCompat">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
</activity>
<activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name" android:launchMode="singleTask">
  <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>

これで起動時に問題があれば適切なエラーを表示することができるようになりました。

必要なときだけアクセス権限を要求する

さらに一部の端末ではインストールされたobbファイルがユーザ所有にならず、アプリからアクセスできなくなることがあるようです。 Unityはこの問題を回避するため、 Split Application Binary オプションを有効にしてビルドすると READ_EXTERNAL_STORAGE 権限要求をAndroidManifestに自動で付与します。

しかしながら、ごく一部の端末のためにすべてのユーザーにこの強力な権限を要求するのはナンセンスです。ここはobbファイルが読み込めなかった場合にのみ権限を要求するようActivityを拡張しましょう。

public class ExpansionFileCheckActivity extends Activity {
    private static final int RC_PERMISSION_REQUEST = 1;

    @Override
    protected void onStart() {
        super.onStart();

        checkApkExtension();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode != RC_PERMISSION_REQUEST) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            return;
        }

        if (grantResults.length == 0 && !ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
            // アプリの設定画面を開く
            return;
        }

        checkApkExtension();
    }

    private void checkApkExtension()
    {
        if (getMainObbFile().canRead()) {
            // UnityPlayerActivityを起動する
            return;
        }

        if (PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE
            }, RC_PERMISSION_REQUEST);
            return;
        }

        // エラーを表示してアプリを終了する
    }

    // (省略)
}

ファイルチェックに失敗した場合は READ_EXTERNAL_STORAGE 権限をチェックし、なければリクエストしてから再度チェックを行います。 このときユーザーが「今後表示しない」をチェックしていると shouldShowRequestPermissionRationalefalse を返すので、一言添えてアプリ設定画面に誘導するとよいでしょう。

最後にUnity側の権限リクエストを無効化する設定をAndroidManifestに追加すれば完了です。

<meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="true" />

これでなんとかAPK拡張ファイルを利用したアプリをリリースする最低限の準備ができました。 あとはお好みでユーザーへの説明や操作指示などを肉付けしましょう。

その他の注意点

バイナリの配布方法

利用しているアプリケーション配信サービスによってはobbファイルを取り扱えないことがあります。ストアの内部テストだけでは大抵不足なので、ビルドの用途に応じてAPK拡張ファイルを利用するかを切り替えられるような仕組みを準備しておくべきでしょう。

StreamingAssetsへのアクセス

UnityでAPK拡張ファイルを利用するとStreamingAssetsはobbファイルの側に格納されます。AndroidにおいてStreamingAssets以下のファイルへのアクセスはどちらにあってもそう変わりません。
しかし、一部のサードパーティライブラリではobbファイル内のStreamingAssetsに正常にアクセスできないものがありました。

StreamingAssets以下にあるアセットが正しく読み込まれているか検証を忘れないようにしましょう。

ビルド番号

Google Play StoreではAPK拡張ファイルのビルド番号はアプリ本体のものと別に扱われるようですが、Unityは必ずアプリのビルド番号を用いてobbファイルを開こうとするようです。 Google Play Storeで新しいリリースを生成するとき、新しいobbファイルをアップロードせずに過去のファイルを選択すると、Unityからアクセスできなくなってしまいます。 

まとめ

UnityでAPK拡張ファイルを扱うのは簡単ですが、その仕組みには落とし穴がいくつもありました。 アプリサイズが逼迫したときはまずより簡単な手順(例えば、アーキテクチャ毎にapkをビルドするなど)を講じて、なお足りない場合に採用を検討すべきでしょう。

その上でどうしても必要になったときは、Unityによるサポートがあるからと軽視せず早めに実装と検証を行うことが大事ですね。