CakePHPのschemaからtypescriptのinterfaceを吐きだすプラグインをかいた
小ネタです。
最近は相変わらずCakePHPでAPIを書いて、nextjsでフロントのアプリを書くサイトばかり作っているのですが、API側で定義したAPIレスポンスデータをフロント側用にinterfaceを書くのがだるいのでプラグインを書いた話です。
TsExport plugin for CakePHP
TsExport plugin for CakePHPは以下のようにインストールしてください。
composer require --dev kaz29/cakephp-ts-export-plugin
実行は以下のような感じ。
bin/cake export_entity --all または bin/cake export_entity モデル名
実際に実行すると、以下のようにinterface定義が標準出力に出力されます。
bin/cake export_entity Users /** * User entity interface */ export interface User { id: number name: string email: string password: string created?: string modified?: string }
フロント側では、src/types/exported_interfaces.ts のようなファイル名でこのプラグインの出力をそのまま使って、フロント用に変更する場合は、別のファイルでextend して項目を追加したり不要なものをOmitしたりしたものを使ってます。
Azure Web PubSubのnegotiateをPHPで実装してみる
最近書いているとあるサービスでリアルタイム更新をしたいと思い、Azure SignalR ServiceとAzure Web PubSubを試してます。
クイックスタートを参考にすれば、Azure Functionsで割と簡単に動作を試せます。
今回のサービスのバックエンドAPIはPHPで書かれているため、 negotiate
の処理をPHPのAPIで実施したいと考えていたのですが、残念ながらAzure PubSubのPHP SDKは現時点で提供されていません。(多分この先も提供はされなそう... (;_; )
ということで、Azure Web PubSub service client library for JavaScript を参考に、negotiate
が何をしているか調べてみました。
調べた結果、negotiate
のレスポンスは以下のような内容になっていました。
{ baseUrl: 'wss://[PubSubName].webpubsub.azure.com/client/hubs/[hubname]', token: 'JWT token', url: 'wss://[PubSubName].webpubsub.azure.com/client/hubs/[hubname]?access_token=[JWT Token]' }
ふむふむ、JWTで認証しているよう...。生成されるJWTの中身は以下の様な内容でした。
{ "header": { "typ": "JWT", "alg": "HS256" }, "claims": { "iat": 1623618349, "exp": 1623621949, "aud": "https://[PubSubName].webpubsub.azure.com/client/hubs/[hubname]" }, "signature": "sigunature...", "raw": "eyJ0eXAiOiJ..." }
要は、接続文字列からこのJWTを生成できれば良さそうです。ということで、gree/joseを使ってざくっと書いてみたのが以下。
<?php declare(strict_types=1); class PubSubToken { protected $endpoint; protected $wssEndpoint; protected $accesskey; protected $version; protected $alg = 'HS256'; public function __construct($connectionString) { $params = explode(';', $connectionString); foreach ($params as $param) { list($k, $v) = explode('=', $param, 2); $this->{strtolower($k)} = $v; } $this->wssEndpoint = preg_replace('/(http)(s?:\/\/)/i', 'ws$2', $this->endpoint); if ($this->endpoint === null || $this->accesskey === null || $this->version === null || $this->wssEndpoint === null) { throw new \Exception('Parameter error'); } } public function getAuthenticationToken(string $hub, string $userId = null, int $ttl = 3600): array { $now = time(); $payload = [ 'iat' => $now, 'exp' => $now + $ttl, 'aud' => "{$this->endpoint}/client/hubs/{$hub}", ]; if ($userId !== null) { $payload['sub'] = $userId; } $jwt = new \JOSE_JWT($payload); $jwt->header['alg'] = $this->alg; $jwt->header['typ'] = 'JWT'; $jwt->sign($this->accesskey, $this->alg); $jws = new \JOSE_JWS($jwt); $jws = $jws->sign($this->accesskey, $this->alg); $token = $jws->toString(); return [ 'baseUrl' => "{$this->wssEndpoint}/client/hubs/{$hub}", 'token' => $token, 'url' => "{$this->wssEndpoint}/client/hubs/{$hub}?access_token={$token}", ]; } } $pubsub = new PubSubToken('Azure WebPubSubの接続文字列'); $token = $pubsub->getAuthenticationToken('test');
このtoken
を使って無事subscribeできました。
ということで、このtokenをAPIで返してあげれば、クライアント側でsubscribeできそうです。
コードは、gistにも上げておきました。
CakePHP4用のOpenApi bake theme pluginを公開しました
最近は久々にガッツリPHPのコードを書いているわたなべです。
このところ、仕事でもプライベートでもPHPでAPIを書いて、Next.jsでフロントのWebアプリを書くことがほとんどです。
この場合API仕様は以前ブログにも書きましたが、swagger-phpのアノテーションで記述して、Swagger-UIで参照できる様にしています。
Swagger-UI と swagger-php
最近は使われている方も多いと思いますが、簡単に説明すると、EntityとControllerに以下の様なアノテーションを記述します。
Entity/Article.php
/** * Article Entity * * @OA\Schema( * schema="Article", * title="", * description="Article entity", * @OA\Property( * property="id", * type="integer", * format="int32", * description="", * ), * @OA\Property( * property="user_id", * type="integer", * format="int32", * description="", * ), * @OA\Property( * property="title", * type="string", * description="", * ), * @OA\Property( * property="slug", * type="string", * description="", * ), * @OA\Property( * property="body", * type="string", * description="", * ), * @OA\Property( * property="published", * type="boolean", * description="", * ), * @OA\Property( * property="created", * type="string", * format="datetime", * description="", * ), * @OA\Property( * property="modified", * type="string", * format="datetime", * description="", * ), * )
Controller/Api/ArticlesController.php
/**
* Index method
*
* @OA\Get(
* path="/api/articles.json",
* summary="Articles index",
* description="Articles index",
* @OA\Parameter(
* name="page",
* in="query",
* required=false,
* @OA\Schema(
* type="number",
* ),
* description=""
* ),
* @OA\Parameter(
* name="limit",
* in="query",
* required=false,
* @OA\Schema(
* type="number",
* ),
* description=""
* ),
* @OA\Response(
* response=200,
* description="successful operation",
* @OA\JsonContent(
* @OA\Property(
* property="success",
* type="boolean",
* default=true,
* ),
* @OA\Property(
* property="data",
* type="array",
* @OA\Items(
* allOf={
* @OA\Schema(ref="#/components/schemas/Article"),
* @OA\Schema(
* @OA\Property(
* property="user",
* ref="#/components/schemas/User",
* description="User Entity",
* ),
* ),
* },
* ),
* ),
* @OA\Property(
* property="pagination",
* ref="#/components/schemas/Pagination",
* ),
* ),
* ),
* )
* @return \Psr\Http\Message\ResponseInterface
*/
これらのコードを、以下の様なコマンドでswagger-phpを使用してビルドします。
swagger.jsonをビルドするコマンド
#!/usr/local/bin/php -q <?php include_once __DIR__.'/../autoload.php'; $app_path = '.'; $openapi = \OpenApi\scan( $app_path, [ 'exclude' => [ 'vendor', 'tmp', 'logs', 'tests', 'webroot', ] ] ); file_put_contents(dirname($app_path).'/docs/swagger.json', $openapi->toJson());
ビルドが成功すると、swagger.jsonが作成されるのでこれをSwagger-UIで読み込むと、以下の様にドキュメントを見ることはもちろん、Swagger-UI上からAPIを呼び出すこともできます。
これすごい便利なのでおすすめなのですが、記述するのが結構面倒なのと、記述方法にいろいろ癖があるので書くたびに毎回試行錯誤することになったりします。
前からなんとかしたいなぁと思っていたのですが、現在とあるリプレース案件で大量にAPIを作成する予定で、この作業を少しでも効率化したいと思いCakePHPのbakeテンプレートを書きました。
bakeテンプレートを自作すると、CakePHPを使っている方であればご存知のbakeコマンドで生成される雛形のソースコードをカスタマイズすることができます。
OpenApiTheme plugin
OpenApiTheme pluginでは、APIを作成する際には定番のfriends od cake CRUD Pluginを使うことを前提で作成しました。
今回は、以下の2つのbakeコマンドを追加しています。
- open_api_model - モデルのbake時にEntityにOpenApiのSchema定義を自動生成する
- open_api_controller - コントローラのbake時にCRUDのAPI定義を自動生成する
実際には以下のような感じでbakeすることができます。
// モデルのbake $ bin/cake bake open_api_model Articles // コントローラのbake $ bin/cake bake open_api_controller Articles --prefix Api
現在のバージョンでは、EntityのSchameにはアソシエーション先のプロパティはあえて含めないようになっています。 定義すると便利は便利なのですが、実際の利用シーンではどのアソシエーションをContainさせるかはAPIによって変わるケースが多いのでEntity側で定義してしまうと使いにくいことが多いです。 この為、OpenApiTheme pluginではEntityのSchameにはアソシエーションを含めずにControllerのAPI定義の方で複数のSchemaを合成(?)するようにしています。
公式のbakeでは、index actionではBelongsToのみcontainし、view acrionでは全てのアソシエーションをcontainするコードが生成されるので、それに倣って以下のようなレスポンスを定義しています。
index action のレスポンス定義サンプル
* @OA\Response( * response=200, * description="successful operation", * @OA\JsonContent( * @OA\Property( * property="success", * type="boolean", * default=true, * ), * @OA\Property( * property="data", * type="array", * @OA\Items( * allOf={ * @OA\Schema(ref="#/components/schemas/Article"), * @OA\Schema( * @OA\Property( * property="user", * ref="#/components/schemas/User", * description="User Entity", * ), * ), * }, * ), * ), * @OA\Property( * property="pagination", * ref="#/components/schemas/Pagination", * ), * ), * ),
view action のレスポンス定義サンプル
* @OA\Response( * response=200, * description="successful operation", * @OA\JsonContent( * @OA\Property( * property="success", * type="boolean", * default=true, * ), * @OA\Property( * property="data", * allOf={ * @OA\Schema(ref="#/components/schemas/Article"), * @OA\Schema( * @OA\Property( * property="user", * ref="#/components/schemas/User", * description="User Entity", * ), * @OA\Property( * property="tags", * type="array", * @OA\Items(ref="#/components/schemas/Tag"), * description="Tag Entities", * ), * ), * }, * ), * ), * ),
bakeしたままでは実際に作成したいAPIにマッチしないケースも多々あるとは思いますが、これを元に実際のAPI定義を作成することで、記述の手間をだいぶ軽減できると思います。
以下で実際にOpenApiTheme pluginで生成したAPI仕様を確認できますので、ぜひ一度見てみてください。
開発環境でのSwagger-UIの利用
普段利用している開発環境では、開発中のAPIをSwagger-UIから直接叩けるように、開発環境用のdocker-compose.ymlにSwagger-UIのコンテナも含めるようにしています。
docker hub に上がっている、公式のDockerコンテナを利用しています。
まとめ
現在進行中の実案件にもOpenApiTheme pluginを導入して使い始めていますが、仕様書作成がだいぶ捗ります。
随時フィードバックして改善していくつもりですが、ぜひ使っていただいて、要望などあればIssueなりPRなりいただければと思います。
ExportしたApp Service 証明書にパスフレーズをつける
管理を手伝っている、友人のサイトでApp Service 証明書を移行する必要があってちょっとハマったので備忘録。
App Service 証明書のExport
CloudShellから以下のコマンドで、Exportできます。
$ az keyvault secret download \ --file appservicecertificate.pfx \ --vault-name <key-valut-name> \ --name <保存先のシークレット名> \ --encoding base64
vault-name
には証明書作成時に設定した、キーコンテナ名を指定します。
name
には証明書が保存されている、シークレットの名前を指定します。
このコマンドでExportした証明書には空のパスフレーズで生成されます。
このあたりの詳細は公式ドキュメントにも解説があります。
証明書のImport
で、この証明書をアップロードしようとするとアップロード画面ではパスフレーズが必須になっています。
pfxファイルにパスワードをつける方法を探すのに少し手間取りましたが、以下で大丈夫でした。
# 一旦pem形式に変換 openssl pkcs12 -in appservicecertificate.pfx -out example.com.pem -nodes # 再度pfx形式に変換、この際にパスフレーズの入力プロンプトが表示されます。 openssl pkcs12 -export -out example.com.pfx -in example.com.pem
ということで無事移行できました。
CakePHP4でRoutingのテスト
最近React+TypeScriptばかりで、CakePHPのコードはあまり書いていないわたなべです。
Routingのテスト
CakePHP1の頃の新原さんのブログ(なんと2009-05-25の記事、11年前!?)でも書かれているように、routes.phpの設定変更は、思わぬバグを出す可能性があるので、UnitTestでの動作確認は必須だと思っています。
CakePHP3までは、以下のような感じでテストできていましたが、CakePHP4でRoutingがmiddleware化した影響などでそのままでは動作しません。
<?php declare(strict_types=1); ... use Cake\Network\Request; ... public function testRouting($request, $expected) { $request = new Request([ 'url' => '/api/articles.json', 'environment' => ['REQUEST_METHOD' => 'GET'] ]), $result = Router::parseRequest($request); $expected = [ 'pass' => [], 'plugin' => null, 'controller' => 'Articles', 'prefix' => 'api', '_ext' => 'json', 'action' => 'index', ]; $this->assertEquals($expected, $result); } ...
CakePHP4でのRoutingのテスト
最初書いたテストコードは以下のような感じ。
<?php declare(strict_types=1); ... use Cake\Http\ServerRequest; ... public function testRouting() { $request = new ServerRequest([ 'url' => '/api/users/login.json', 'environment' => ['REQUEST_METHOD' => 'POST'] ]); $result = Router::parseRequest($request); $expected = [ 'pass' => [], 'plugin' => null, 'controller' => 'Users', 'prefix' => 'Api', '_ext' => 'json', 'action' => 'login', '_matchedRoute' => '/api/users/login', '_method' => ['POST'], '_middleware' => ['bodies'] ]; $this->assertEquals($expected, $result); } ...
これを実行すると、以下のようなエラーになってしまいます。もちろんroutes.phpには正しく設定されていて、Web経由でのアクセスにも問題ありません。
Cake\Routing\Exception\MissingRouteException: A route matching "/api/users/login.json" could not be found.
色々調べてみたところ、Routerがmiddleware化されたため、Applicationにmiddlewareが追加された時点でrouterを初期化するようになったようです。
このため、routingテーブルが空の状態になっていました。
解決方法
ということで、Routingテーブルを初期化する処理をtraitにしました。
<?php declare(strict_types=1); namespace App\Test\Utility; use Cake\Routing\Router; trait RoutingTestTrait { protected function initializeRoute() { Router::reload(); $routes = Router::createRouteBuilder('/'); require CONFIG . 'routes.php'; } }
前出のコードをこのtraitを使うようにこんな感じに修正するとroutingのテストができます。
<?php declare(strict_types=1); ... use Cake\Http\ServerRequest; use App\Test\Utility\RoutingTestTrait; // 追加 ... class UsersControllerTest extends TestCase { use IntegrationTestTrait; use RoutingTestTrait; // 追加 ... public function testRouting() { $this->initializeRoute(); // 追加 $request = new ServerRequest([ 'url' => '/api/users/login.json', 'environment' => ['REQUEST_METHOD' => 'POST'] ]); $result = Router::parseRequest($request); $expected = [ 'pass' => [], 'plugin' => null, 'controller' => 'Users', 'prefix' => 'Api', '_ext' => 'json', 'action' => 'login', '_matchedRoute' => '/api/users/login', '_method' => ['POST'], '_middleware' => ['bodies'] ]; $this->assertEquals($expected, $result); } ...
まとめ
routingのテストを書いていたことで、routing変更時に何度も救われたことがあるので是非テストを書くことをお勧めします。 CakePHP4は、色々と改善されていい感じに進化しているので今後も使っていこうと思います。
以上、小ネタでした。
2020/6/25追記
RoutingのMiddleware化は、CakePHP3で実施されていました。具体的に何が理由でRoutingテーブルが初期化されていないかは不明。後日調べてみてわかれば追記します。
Github ActionsでMultiContainerなAzure WebApp for Containersをデプロイする
友達に頼まれて、@kunyamiさんのスライド 堅牢&運用楽々な WordPress を Azure App Service でを参考に、WordpressをMultiContainerなAzure WebApp for Containersで構築してたのですが、いくつかハマったのでメモとして残します。
基本的な設定は、@kunyamiさんの資料の通りに進めたのですが、今回は、一部Wordpress管理外の静的なページを含むため、独自コンテナをビルドする必要があったので、GitHub Actionsを使用して、コンテナイメージのビルド、Azure WebApp for Containersへのデプロイをすることにしました。
リポジトリは以下の様な構成になっています。
- Dockerfile - デプロイ用Dockerfile
- docker-compose.yml - デプロイ用 docker-composeファイル
- 静的ファイル用のディレクトリ
- その他設定ファイルなど
ベースイメージは、wordpress公式のものを使用して、静的なページは、ビルド時にコピーする様にしました
Dockerfileサンプル
FROM wordpress:latest COPY static_webpages /var/www/html/static_webpages
事前準備
Azure WebAppデプロイ用クレデンシャルの作成
いつものですね。以下のコマンドを実行すると表示されるJSONがクレデンシャルです。
$ az ad sp create-for-rbac --name "https://[WebAppの名前].azurewebsites.net" --role contributor \ --scopes /subscriptions/[サブスクリプションID]/resourceGroups/wetest \ --sdk-auth
秘匿情報の設定
Github Actions内で利用する秘匿情報は、Githubの対象リポジトリ内の Settings -> Secrets
で設定します。
今回は、ビルドしたイメージをDocker Hubに保存するので、Docker Hubアカウントパスワードと先ほど作成した、クレデンシャルをSecretsに追加します。
- AZURE_CREDENTIALS - Azureクレデンシャル
- DOCKER_PASSWORD - Docker Hubのパスワード
Github Actionsの設定
Github Actionsの設定は、リポジトリ内の .github/workfloes
ディレクトリ以下に設定ファイルを配置します。今回作成したのは以下の様なファイルです。
name: Build And Deploy env: DOCKER_USERNAME: [Docker Hubのユーザ名] DOCKER_IMAGENAME: [docker image名] AZURE_WEBAPP_NAME: [webappの名称] on: push: branches: - master jobs: build: runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v2 - name: Azure authentication uses: azure/login@v1 with: creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Build & push Docker image uses: docker/build-push-action@v1 with: image: ${{ env.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGENAME }} repository: ${{ env.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGENAME }} dockerfile: Dockerfile username: ${{ env.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} tags: ${{ github.sha }} - name: 'Deploy to Azure Web App for Container' uses: azure/webapps-deploy@v2 with: images: ${{ env.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGENAME }}:${{ github.sha }} app-name: ${{ env.AZURE_WEBAPP_NAME }} configuration-file: docker-compose-webapp.yml
yamlファイルを見れば一目瞭然ですが、今回作成したJobには以下の4つのStepが定義されています。
- Check out code - コードをチェックアウト
- Azure authentication - Azureの認証処理
- Build & push Docker image - docker imageのビルドとプッシュ
- Deploy to Azure Web App for Container - Azure WebApp for Containersへのデプロイ
docker imageのビルドとプッシュ
docker imageのビルドとプッシュには、Docker公式のGitHub Actionを使っています。 公式サイトに詳しい解説があるので、そちらを参照してください。
今回は、ビルト時のタグにgit hashを使用しているので以下の様に指定しています。
tags: ${{ github.sha }}
Azureの認証とAzure WebApp for Containersへのデプロイ
Azure WebApp for Containersへのデプロイは、Azure 公式のGitHub Actionを使っています。 こちらも、公式サイトに詳しい解説があるので、そちらを参照してください。
MultiContainerなAzure WebApp for Containersのデプロイ方法
今回は、MultiContainerなAzure WebApp for Containersを使用しているのですが、公式ドキュメントにはMultiContainerの場合のデプロイ方法は書かれていないため、GitHub Actionのソースコードを確認したところ、以下の様に設定情報を初期化しているのを発見しました。
... class ActionParameters { constructor(endpoint) { this._publishProfileContent = core.getInput('publish-profile'); this._appName = core.getInput('app-name'); this._slotName = core.getInput('slot-name'); this._packageInput = core.getInput('package'); this._images = core.getInput('images'); this._multiContainerConfigFile = core.getInput('configuration-file'); this._startupCommand = core.getInput('startup-command'); this._endpoint = endpoint; } ...
ということで、MultiContainer時のdocker-composeファイルは configuration-file
で指定できました。最終的なStepの設定が下記です。
- name: 'Deploy to Azure Web App for Container' uses: azure/webapps-deploy@v2 with: images: ${{ env.DOCKER_USERNAME }}/${{ env.DOCKER_IMAGENAME }}:${{ github.sha }} app-name: ${{ env.AZURE_WEBAPP_NAME }} configuration-file: docker-compose-webapp.yml
docker-compose.yml 内でイメージ名を指定していますが、 images
に今回ビルドしたタグを含むイメージ名を指定しています。
これもドキュメントには記載がないのですが、試行錯誤している最中に以下のエラーが発生してエラーメッセージの内容から発見したのですが、MultiContainer時にimage名を指定すると、docker-compose内のイメージ名を上書きしてくれますので、自前でdocker-composeファイルを書き換える様な処理は書かなくても大丈夫でした。
試行錯誤中に発生したエラーメッセージ
Deployment Failed with Error: Error: For single-container, just specify a valid image name. For multi-container specifying a Docker-Compose file is mandatory and specifying image names is optional. Provide image names if the tags in Docker-Compose file need to be substituted.
また、slot-name
でデプロイ対象のスロット名も指定できる様なので、B/Gデプロイもできそうですね。
まとめ
マーケットプレイスで公開されているGitHub Actionはソースコードも公開されているので、ドキュメントに記載のない内容はソースコードにあたるとすんなり解決できることも多そうです。 GitHub Actionsかなり便利なので、これからも積極的に使っていきたいです。
PCOVでコードカバレッジ取得を高速化
この記事はCakePHP Advent Calendar 2019の21日目の記事です
つい先日、ついにCakePHP 4.0がリリースされましたが、CakePHP 4.0で利用しているテスティングフレームワークはもちろんPHPUnitです。CakePHP3では、PHPUnit 6.0系を使っていましたが8.5.0に更新されています。
PHPUnitで、コードカバレッジを取得するにはXdebugを使うのが定番ですが、PHPUnit8系ではXdebug以外にPCOVを利用することができます。
PCOVは、今年(2019年)リリースされたばかりのコードカバレッジドライバーで、高速かつ省メモリで動作することが特徴です。
ということで、今回は実際にどれくらい高速化できるのかを簡単に調べてみました。
計測した環境
当初、CakePHP3で作ったサンプルアプリをCakePHP4化して試そうと思っていたのですが、いろいろな問題があり断念しCakePHP4本体のテストの一部(CakePHPのUnitTestすべてを実行するとかなり時間がかかるので、ORMのテストのみ)を利用して計測しました。
計測した環境は、macOS上のDocker Containerで、PHP公式で公開されているこちらをベースに若干拡張などを追加したものを利用しています。
それぞれのバージョンは以下のとおりです。
# php -v PHP 7.3.10 (cli) (built: Oct 10 2019 21:12:52) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.3.10, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.3.10, Copyright (c) 1999-2018, by Zend Technologies # pecl list Installed packages, channel pecl.php.net: ========================================= Package Version State apcu 5.1.17 stable pcov 1.0.6 stable redis 5.0.2 stable xdebug 2.9.0 stable
コードカバレッジなし
まずは、Xdebugを無効にしてコードカバレッジなしでの計測結果がこちら。
# ./vendor/bin/phpunit tests/TestCase/ORM/ PHPUnit 8.5.0 by Sebastian Bergmann and contributors. ............................................................. 61 / 1331 ( 4%) ............................................................. 122 / 1331 ( 9%) ... .................................................. 1331 / 1331 (100%) Time: 7.04 seconds, Memory: 36.00 MB OK, but incomplete, skipped, or risky tests! Tests: 1331, Assertions: 3917, Skipped: 3, Incomplete: 2.
Xdebug
# ./vendor/bin/phpunit tests/TestCase/ORM/ PHPUnit 8.5.0 by Sebastian Bergmann and contributors. ............................................................. 61 / 1331 ( 4%) ............................................................. 122 / 1331 ( 9%) ... .................................................. 1331 / 1331 (100%) Time: 3.68 minutes, Memory: 126.00 MB OK, but incomplete, skipped, or risky tests! Tests: 1331, Assertions: 3917, Skipped: 3, Incomplete: 2. Generating code coverage report in Clover XML format ... done [1.68 minutes] Generating code coverage report in HTML format ... done [1.8 minutes]
やはりかなり遅いですね。約31倍です。
PCOV
PCOVでの計測結果がこちら。
# php -d pcov.enabled=1 ./vendor/bin/phpunit tests/TestCase/ORM/ PHPUnit 8.5.0 by Sebastian Bergmann and contributors. ............................................................. 61 / 1331 ( 4%) ............................................................. 122 / 1331 ( 9%) ... .................................................. 1331 / 1331 (100%) Time: 24.55 seconds, Memory: 126.00 MB OK, but incomplete, skipped, or risky tests! Tests: 1331, Assertions: 3917, Skipped: 3, Incomplete: 2. Generating code coverage report in Clover XML format ... done [10.48 seconds] Generating code coverage report in HTML format ... done [46.87 seconds]
なんと、Xdebugを使った場合と比べると約10分の1の時間で完了しています!カバレッジを計測していない場合と比べると、約3.5倍。Xdebugと比べると許容範囲かなと思います。しかも、メモリ使用量もXdebugと同じという結果でした
まとめ
まとめたものがこちら
実行時間 | 使用メモリ | |
---|---|---|
カバレッジなし | 7.04 s | 36.00 MB |
Xdebug | 220.80 s | 126.00 MB |
PCOV | 24.55 s | 126.00 MB |
実際のコードで実行時間に差は出るとは思いますが、かなりの高速化が期待できそうです。
CakePHP4へのアップデートはプラグインやライブラリの対応など、まだ色々とハードルはありますが、PHPUnitのバージョンアップはかなり魅力的だと思っています。PHPUnit自体の変更については全く追えていないので今後キャッチアップしていきたいと思っています。