API仕様の出力プラグインをtraitにしてみた話

みなさんこんにちは
先月よりジョインしました、 niisan-tokyo です。
今後とも宜しくお願いします。

で、今回のテーマですが、以前にkotamatが公開していた、テストを通したAPIスペックの自動生成プラグインをちょいと改造したという話です。

APIスペック自動生成ツールの改造

TL;DR

  • API仕様の自動吐き出しがクラス継承式だったので、使いにくかった
  • traitでも使えるようにした
  • 一応後方互換性を確保した

対象のリポジトリ

https://github.com/kotamat/laravel-apispec-generator

ちょっと使いにくかった

このプラグインは、使い方は容易だったのですが、一つ欠点がありました。
それは、このプラグインをベースクラスとして継承しなければならないということです。

<?php

use ApiSpec\ApiSpecTestCase;

class TestCase extends ApiSpecTestCase
{

}

APIスペックを出力する必要のないところでは、このクラスを継承したくないため、別のベースクラスを使いたくなりますが、一方で、ベースクラスでアプリで特有の設定やら便利なテスト機能を実装することがあるでしょう。
結局、普通のベーステストクラスとともに、APIスペック出力用のクラスを継承したベーステストクラスを用意するという、少々厄介なことになります。

traitにする

そこで、ベースクラスの代わりにtraitを使うという技が考えられます。
traitは使用することで、そのクラスに単純にtraitの中のコードをコピーしたのと同じ状態を作ることができます。
ApiSpecTestCaseは、実際にはいくつかのメソッドにAPI仕様を吐き出すコードを入れているだけですので、traitで十分ということになります。

traitを使う場合のAPI仕様の出力は以下のようにすると可能になります。

<?php

use ApiSpec\ApiSpecOutput;

class SomeTestCase extends TestCase
{
    use ApiSpecOutput;

    //...
    
    /**
     * @test
     */
    public function API仕様を吐き出すテスト()
    {
        $this->isExportSpec = true;
        $this->getJson('/someone/status');
    }
}

こうして、必要なときにだけ、use ApiSpecOutputすることで、ベースクラスをいじることなく、API仕様を吐き出すことができます。

後方互換性の維持

こうして、traitにしようとしたときに、すでにベースクラスで使っているところはどうなるんだという話になります。
これは、プロダクト開発において、仕様を追加する場合にも発生する問題で、後方互換性をどうするのかということです。 今回は後方互換性は残すことにします。

後方互換性の確保の方法は簡単で、要するにテストが通っていればいいということです。
API仕様出力ベースクラスはテストクラスのベースクラスなので、テストの方法が厄介でした。

https://github.com/kotamat/laravel-apispec-generator/blob/0ade044a5a8f41d54a4b872978a1c3cbb2647bc9/test/ApiSpecTestCaseTest.php

テスト自体はかなり無理矢理なコードでとにかくテストできればいいやって感じになっています。

<?php

// ... 中略

    public function createApplication()
    {
        $app = new class extends Application {
            private $acceptor;
            public function __construct($basePath = null)
            {
                //
            }
            public function setAcceptor($acceptor)
            {
                $this->acceptor = $acceptor;
            }
            public function make($class, array $param = []) {
                $mock = m::mock(FilesystemAdapter::class);
                $mock->shouldReceive('drive')->andReturn($this->acceptor);
                return $mock;
            }
        };
        $app->setAcceptor($this->acceptor);
        $this->app = $app;
    }
    protected function setUp()
    {
        $this->acceptor = new class {
            public $filename;
            public $str;
            public function put($filename, $str) {
                $this->filename = $filename;
                $this->str = $str;
            }
        };
        $this->createApplication();
    }

これがテストの前提部分ですね。
無名クラスを連発しておきながら、思い出したようにMockeryを利用したりしています。
テスト部分は上のリンクで見てください。単純にpostJsonとかを投げたら、API仕様の出力ができていることを確認しているだけです。

このテストを作った上で、ApiSpecTestCaseにあったロジックの大半をApiSpecOutputに移し、ApiSpecTestCaseApiSpecOutputを使っているだけという状態にすることができました。

まあ、ベースクラスに手を入れずに、traitだけ作ればこんな苦労はないんですが、同じコードがあるって気持ち悪いので、やってしまいしました。

まとめ

というわけで、API仕様の出力をtrait化することに成功しました。
個人的にはもうちょい改造したいところですが、ひとまずはtraitで使えるようになったというところで満足しておきましょう。
今回はこんなところです。

最後に

現在、株式会社SCOUTERでは、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記からご応募お願い致します!

www.wantedly.com

www.wantedly.com

www.wantedly.com

www.wantedly.com