May 13, 2019

テストしにくいコード

対象

LaravelとPHP

new

よく言われている分かりやすい目印。new使ってるだけでやばいなと分かる。

$foo = new Foo();

LaravelならDIかapp()/resolve()でテスト時にモックしやすい実装にできる。

public function __invoke(Foo $foo)
{
    $foo->bar();
}
$foo = resolve(Foo::class);

テストではこうやってモック。

$this->mock(Foo::class, function ($mock) {
    $mock->shouldReceive('bar')->once();
});

Laravelでの例外。Mail、Event、Notificationなどのnewは簡単にモックできるので使ってOK。

https://readouble.com/laravel/5.8/ja/mocking.html

staticメソッド

あまりにも有名すぎて自分では使わないから知らなかったけど世の中には本当にstaticメソッド使いまくる人が存在する。
staticメソッド使っていいのはそのメソッド単体で完結していて他への影響がない場合に限る。
staticメソッド内でDB操作なんかしていたらとんでもなくテストしにくいコード。
他人が書いたstaticメソッドをテストしようとするとstaticメソッドやめろと言われてる理由が理解できる。

$bar = Foo::bar();

こう使えばいいだけのことなのでstaticにするメリットは全く分からない。(一般的ではないけど実はstaticメソッドでもこう書ける。)

$bar = (new Foo)->bar();

staticじゃないだけでモックできる実装に。(実装のほうを変えればstaticでもモックできるかもしれないけどそれができるならstaticじゃなくすこともできる)

$bar = resolve(Foo::class)->bar();

LaravelでstaticメソッドはStrとかArr。
Facadeはstaticに見えてstaticじゃないしテストできるので問題ない。

それでもテストしにくい

Guzzleの非同期通信とかReactPHPとかのPromiseはどうテストすればいいのか未だに分かってない。
http://docs.guzzlephp.org/en/stable/quickstart.html

$pool = resolve(Pool::class, [
    'client'   => $client,
    'requests' => $requests($urls),
    'config'   => $config,
]);

$pool->promise()->wait();

これなら?resolve()の第二引数はLaravel5.8の途中で追加された。app()では5.4で追加されてた。

$pool = Mockery::mock(Pool::class);
$pool->shouldReceive('promise->wait');

参考

Laravel関連で唯一役に立った本。

Laravel Testing Decoded
https://leanpub.com/laravel-testing-decoded
日本語版
https://leanpub.com/laravel-testing-decoded-japanese

Laravel4時代なので今から読んでどうなのかは分からないけど。

Laravelの使い方はドキュメント読めば分かる。
良いコードや設計はLaravelの本読んでも意味がない。フレームワークや言語には依存しない話。

© kawax