[ 使用用途 ] PHPでログをテストする少しおかしな方法 [ 超限定 ]

こんにちはみなさん

ログはアプリケーションの処理を追跡するためにコードの中に仕込むものであって、基本的にテストするようなものではありません。
それとは別に、ユーザーの行動に従ってKPI用のログを取る場合もありますが、これもユーザーに直接影響のあるものではありませんので、他の部分と一緒にテストしたとき、とりあえずシンタックスエラーが出なければそれでいい、程度の確認で済ませ、実際にログの中身をする必要性はあまりないといえるでしょう。

そんな中、どうしてもログに吐き出された内容をテストしなければならないという状況に陥ったため、ちょっと変な小細工を弄して解決した経緯と内容を書いていきます。

# Laravelでメールのテスト
私はメールに対して非常に苦手意識を持っています。
メールサーバのたて方とか、未だによくわかりませんし。

Laravelでは色々なやり方でメールを出すことができるわけですが、ローカルでの開発用に`log`というドライバーを用意してくれています。
これを使うと、本来メールで投げられる内容をログとして吐き出してくれます。
これで、ひとまずローカルでの実装はなんとかなりました。

## リマインド用のメール
ところが、パスワードリマインダ用のメールを出す機能を作ったときに、そのメールにちゃんとリマインド用のURLが記載されているかをテストしたくなりました。

```remind.html
これはパスワード再設定用のメールです
以下のURLからパスワードの再設定を行ってください。

https://niisan.example.com/auth/resetpassword/gzXDZXE8QPz6avcdADsievTFGH5D5hSuk6m59qWZdjHzMKnL2rqcv64luHM

※本メールは送信専用のメールです.返信しても再度返信されることはありません
```
こんな感じのやつです。
このURLがあるとき間違ってしまったら、パスワードのリマインドができなくなり、システムが大きく混乱する可能性がありますので、正しいURLが記載されていることの確証を得たいわけです。

とまあ、こういうかなり限定された状況下にて、ログの中身をテストする必要が出てきたのです。

# ログの内容のテストをする
最も単純にログのテストをするのなら、ログファイル事態を取得して、その中身を見ればいいわけです。

```php
$content = file_get_contents(storage('logs/laravel.log');
```
いやしかし、これはやりたくないですね。
ログファイルは毎回毎回溜まっていきますし、それこそテストごとにどかどか増えていきます。
テストのたびにログファイルを消去するってのも、なんだか面倒くさいし、ドライバーを`single`から`daily`に変えただけでも大きな改修が必要そう。

そこで、ログに吐き出される内容を一時的に別の変数に入れて、その変数の中身をテストするという技を考えます。
Laravelだと、LoggerはMonologを使っているので、次のようにログを抜き取ることができます。

```php
        $cont = [];
        Log::getMonolog()->pushProcessor(function ($record) use (&$cont) {
            $cont[] = $record['message'];

            return $record;
        });// ここからログの採取開始

        // テスト用のアクション

        Log::getMonolog()->popProcessor();// ここでログの採取終了
```
`pushProcessor`を使って、各ログからメッセージを抜き出す処理を追加しています。
ちなみに、`pushProcessor`は本来は`$record`に情報を追加するためにあるらしいので、言ってみれば邪道な手段になっています。

ただ、このままだと際限なく変数にログがたまるので、アクションが完了した時点でログの採取を終了するために、追加した処理を`popProcessor`を使って取り除いています。

こんな感じで、ログを抜き取ることができるので、

```php
$cont = var_export($cont, true);
$this->assertTrue(strpos($cont, $correct_url) !== false);
```
こんな感じで、メールの中に正しくリマインド用のメールアドレスがあることを確認できています。

# ちょっと改良する
このままでもいいんですが、`popProcessor`を忘れて、変数に変なログが投入され続けるのも気持ち悪いので、そのような状況が起きないようにちょっと細工しましょう。
まず、次のような`trait`を作ります。

```tests/util/MailLog.php
<?php
namespace util;

use Log;

trait MailLog
{

    protected function getLogHistory(callable $func)
    {
        $cont = [];
        Log::getMonolog()->pushProcessor(function ($record) use (&$cont) {
            if (strpos($record['message'], 'swift.generated')) {
                $cont[] = $record['message'];
            }
            return $record;
        });

        $func();

        Log::getMonolog()->popProcessor();

        return $cont;
    }
}
```
メールに特化させた形となりますが、これをテストで使うと、

```MailTest.php
<?php

use Illuminate\Foundation\Testing\DatabaseTransactions;
use util\MailLog;

class OperatorTest extends TestCase
{

    use DatabaseTransactions, MailLog;

    public function testSend()
    {
        $to = 'niisan@example.com';
        $valid_url = '正しいURL'
        $content = $this->getLogHistory(function () use ($to) {
            // 必要なアクション
        });
        $this->assertTrue(strpos(current($content), $valid_url) !== false);
    }
}
```
こんな感じで安全にログのテストができます。
ただし、実はこれをテストしようとすると、MailLogが見つからないエラーが出ます。

そこで、`composer.json`に次の項目を足します。

```composer.json
    "autoload-dev": {
        "classmap": [
            "tests/TestCase.php",
+           "tests/utils"
        ]
    },
```
この状態でオートロードを吐き出します。

```
$ composer dump-autoload
```
これで、テストが実施できます。

# まとめ
というわけで、ログをテストするための、より具体的にはLaravelのメールをlogドライバを使って出している場合にその内容をテストするための、少々マニアックなやり方でした。
こういった、ともすれば小細工にしか見えないやり方は、個人的にはとても好きなのですが、小細工だけにあまり出てこないのですよね。

今回はこんなところです。