ひでメモ

プログラムについて勉強したことを書きます。たぶん。

Rails チュートリアルでモデル生成時に併せて erb ファイルが生成されない

Rails チュートリアルの下記あたりです。
第2章 Toyアプリケーション - Railsチュートリアル

以下のコマンドでモデルを生成しましょうというくだりがあるのですが、私の環境ではapp\views以下に erb ファイルが生成されませんでした。

rails generate scaffold User name:string email:string

調べるとrails generate scaffoldコマンドではconfig\application.rbを参照して生成をスキップするファイルを指定できるらしいので見てみました。

    # Only loads a smaller set of middleware suitable for API only apps.
    # Middleware like session, flash, cookies can be added back manually.
    # Skip views, helpers and assets when generating a new resource.
    config.api_only = true

上記のような記述があったため、コメントアウトして再度rails generate scaffoldコマンドを実行したところ正常に生成されました。

今は SPA が普通になっているので標準で erb ファイルは作らないようになっているのでしょうか。
厳密に Rails チュートリアルと同じ Cloud9 環境ではなくて、コンテナ環境なのでインストールしてるライブラリなどに差異があるせいで初期設定が違うとか…?

Laravel は使っているので Laravel で言うとあれだな、というのはわかるのですが具体的な設定方法などはまだまだわからず手探りで進めています。

追記

config\application.rbconfig.api_only = trueが指定されるのは API モードでアプリケーションを新規作成した場合だそうです。

Rails による API 専用アプリケーション - Railsガイド

rails new my_api --api

上記のようにオプションを付けたときに API モードで作成されるそうです(つけた記憶はなかったんですが…)

【PHPUnit】php artisan test --parallel するときに設定ファイルを指定する

現在のプロジェクトでは CI でテストのジョブを分散させるために PHPUnit の設定ファイル(デフォルトだと phpunit.xml )が複数あります。
そのため、以下のようなことを同時にやりたい場合が発生します。

以下のようなコマンドになるかなと思って実行すると (--configurationは設定ファイルを指定するオプション)

$ php artisan test --parallel --configuration ./phpunit-2.xml 

怒られてしまいました…

...
In CoverageMerger.php line 59:
                                                                                                                                                                           
  Coverage file /private/var/folders/tf/v66b2v0n7lv8gt8plbf7dnzc0000gn/T/CV_BOSyno is empty. No coverage driver found! Enable one of Xdebug, PHPDBG or PCOV for coverage.  

エラーとしてカバレッジファイルがどうのこうのと怒られているのですが、今はテストさえ流せればいいのでオプションで除外します。

--parallelオプションを付けると、実態はParatestが実行されるようですので--no-coverageカバレッジを取得しないように指定してやると無事に実行できました。

php artisan test --parallel --configuration ./phpunit-3.xml --no-coverage 

追記

設定ファイルphpunit.xmlに以下のような記述があるとカバレッジを出力しようとしてくれるようです。
これをコメントアウトすることでも--no-coverageオプションは不要になりました。

    <logging>
        <log type="coverage-html" target="./report"
            lowUpperBound="50" highLowerBound="80" />
    </logging>

余談

設定ファイルを指定するオプションを調べていて気づいてしまったのですが、XML 設定ファイルで対象とするディレクトリをグループ分けができそうでした。

phpunit.readthedocs.io

つまり、テスト実行時のオプションでそれぞれのグループを指定して実行という形にすれば CI でジョブも分けられて、ファイルはひとつにまとめるということができそう…!

xor 演算子の優先順位は = (代入)より下位

自戒を兼ねて記事にします。内容はタイトルの通りなんですが知らずハマりました……

結論

xor より代入のほうが優先順位が上なので $a に代入される値は xor が処理される前の値になります。

$a = true xor false; // true 
$a = false xor true; // false

そのため、意図した値を変数に格納するには括弧をつけて、代入より先に処理を実行させる必要があります。

$c = (false xor true); // true

&&||と同じレベルだと思っていたのでまさか優先順位の問題だと思わずハマりました…

演算子の優先順位

ふと気づいて括弧つけたら意図した挙動になったのでドキュメントを確認したらその通りでした。

PHP: 演算子の優先順位 - Manual

とりあえず以下のようにざっくり覚えておくことにしました。

  1. && || などの記号系
  2. = 代入
  3. and or xorなどの英字系

PHPUnit の setMethods の代わりに使うメソッド

概要

PHPUnit でモックを作成する際に、、モック対象となるメソッドを指定する際にsetMethodsを使っていると、これは現在非推奨とのことで代わりのメソッドを調べました。PHPStorm が教えてくれました… ありがとうございます🙏🏼

代わりのメソッド

setMethodsは今後削除されるので代わりには以下のどちらかを使うそうです。

  • onlyMethods:すでに存在するメソッドをモックする場合
  • addMethods:まだ存在しないメソッドをモックする場合

どちらがよいのか

基本的にはonlyMethodsにしておくとモック対象のメソッドがなくなった場合は以下のようなエラー文と共にテストを失敗させてくれるのでテストは通ってるけどそのメソッド実は消されてた!みたいなことがなくて良さそうです。

Trying to set mock method "execute" with onlyMethods, but it does not exist in class "<モック対象のクラス名>". 
Use addMethods() for methods that do not exist in the class <テストのクラス名>

逆に存在するメソッドに対してaddMethodsを指定すると、それすでにあるからonlyMethodsを使ってや!とテストを失敗させてくれました。
テスト作成時に存在してなかったメソッドが後で作成された場合も気づけるようになっていて親切ですね。

参考

PHPUnit 9時代のTest Doubleの作り方 / deep dive into mockbuilder of phpunit 9 - Speaker Deck

【Laravel】assertDatabaseHas のテーブル名の取得をモデルから行ったほうが可読性が上がる気がする

テストで下記のようにデータベースの中身を確認するパターンよくあると思います。

$this->assertDatabaseHas(
         'm_user',   // テーブル名を文字列で指定
        [
            'id'       => $user->id,
            'user_name'  => $user->user_name,
        ]);

ただ、テーブル名を文字列で指定するのってなんか冗長だなぁと思っていました。
たまにモデル名と一致してなかったりするし、わざわざ Model のクラスまで見に行くのめんどくさいし…

そこで、以下のようにテーブル名をモデルから取得するように書いたほうがわかりやすいのではと。

$this->assertDatabaseHas(
        User::getModel()->getTable(),   // テーブル名をモデルから取得
        [
            'id'       => $user->id,
            'user_name'  => $user->user_name,
        ]);

メリットとしては以下のようなものがあるかと思います。

  • どのモデルがテスト対象になっているか明らか
  • IDE でのサジェストが効く ※テーブル名もサジェストしてくれる IDE もあるとは思いますがモデルよりは少ないと思います

デメリットとしては以下くらいかなと思います。

  • テーブル名を取得する処理を挟んでるので処理負荷は(テーブル名直指定よりは)高い
  • 記述が長くなる?

ただ、処理負荷としてはテスト用ファクトリでモデルつくるのと比べたら誤差なのでほぼこじつけみたいなものかなと思います。

【Laravel】CI での自動テストを並列化したらジョブの時間が短縮した

概要

CI での PHPUnit で行う自動テストで並列化オプションを有効にしたらテストが早く終わるようになりました。
ローカルではもちろん使っていたんですが CI でも有効したらいいじゃんと気づいたので有効しました。

並列化オプション

parallelオプションでテストを並列化できます。内部的には Paratest を使っているようです。

php artisan test --parallel

ドキュメントにも書いてますね。
readouble.com

注意点というほどでもないのですが、PHPUnit のオプションを使う場合はparallelオプションの後に付けないと怒られてしまいました。
artisan testとしてのオプションを先に受け取って、その後に PHPUnit のオプションという仕様のようです。

php artisan test --parallel --no-coverage -c ./phpunit.xml

並列数

並列数はprocessesオプションで指定できますが、今回は特に指定しませんでした。

php artisan test --parallel --processes=4

デフォルトは使用できる論理コア数とのことでした。

GitHub - paratestphp/paratest: Parallel testing for PHPUnit

The default is auto, which means the number of logical CPU cores is set as number of processes.

他の処理はしておらず使用できるコアはできるだけ使ってほしいのでデフォルトです。
多すぎてもあんまり意味がないと思うのでとりあえず論理コア数で十分かと考えました。
あれ? CI サーバは他のジョブも並列で走るから全部使うのはあんまりよくないのかな…?
ここらへんは運用しながら調整かと思っています。

結果

CIサーバの混み具合にもよるのですがざっくり 5分 => 3分 くらいまでテストのジョブの時間が短縮できました。
ヤッター!

【Laravel】php artisan schema:dump でマイグレーションファイルを圧縮したらテストが早くなるか試してみた

結論

マイグレーションの圧縮をしても速度はほぼ変わりませんでした。
というかプロジェクトの CI 環境では遅くなりました…
もちろん、うちの環境では、という話なのでマイグレーションファイルの数や内容によっては早くなることも十分あると思います。
一例としてみていただければ…

やったこと

行ったのはphp artisan schema:dumpを実行して、マイグレーションファイルをひとつにまとめました。

readouble.com

このコマンドを実行するとdatabase以下にschemaというディレクトリが作成されそこにダンプファイルが保存されます。

マイグレーションファイルがまとまるので、追加したけど後で消したカラムなどはそもそも作られなくなる(そのカラムに関する記述がなくなる)ようです。
(なので、そういう無駄な処理がなくなった結果早くなるかな~と思ったのでしたが誤差でした😂)

pg_restore コマンドが必要

テストを流す際に躓いた点でほぼメインです。

皆さんご存知の通りテスト前にはマイグレーションが走り、テーブルを新たに作り直します。
ただ、マイグレーションの圧縮を行った場合、ダンプファイルがまず読み込まれることになります。

このダンプファイルを読み込むために Laravel 内部ではpg_restoreコマンドが呼ばれます ※ PostgreSQL の場合
当然、テストに使用している PHP のイメージにはそんなコマンドは含まれていませんでした。

今回は速度の検証だけだったので、暫定でテストを実行するのと同じコンテナに無理やりインストールさせました。
どのみち追加の処理が発生してしまうというわけです…

気になったのがpg_restoreartisanコマンドから呼ばれてしまうので、PHP(というかLaravel)が動くコンテナにpg_restoreがインストールされている必要があるということです。
(ダンプを読み込むだけであれば別のコンテナを立ち上げてとかできると思うのですが)
なにか良い方法があるんでしょうか。

感想

CI はちょっとでも早くしたいです…!
テストは件数もあってかなり時間を食っているので特に早くしたい…!
とりあえず並列テストを設定しなかったことに今更気づいてしまったので来週は試してみたいと思います。