なぜPHP標準関数のdateではなくCarbonを使うのか?
表題の通りです。
なぜ PHP 標準関数の date ではなく Carbon を使うのか?を順を追って説明します。
答え
結論から先に述べておくと、Carbon を使う最も高いモチベーションは、UnitTest の際に副作用が生じる。モック化して副作用を消したいが、モック化 するのが難しいからです。
どういうことか説明していきます。
修正前コード
以下は実際にdate
を使っているクラスの例です。
<?php
namespace App\Services\Programs;
use App\Models\Notifyprogram;
use App\Models\Personality;
use App\Models\Program;
class ProgramService
{
//中略
/**
* 曜日を返す.
*
* @return array
*/
public function getWeekDays()
{
$weekDays = ['0' => 'MonDay', '1' => 'Tuesday', '2' => 'Wednesday', '3' => 'Thursday', '4' => 'FriDay', '5' => 'Saturday', '6' => 'SunDay'];
return $weekDays;
}
/**
* 今日の曜日を返す
* バッチは0が月曜日で6が日曜日なので、1を引く。負数は6にする.
*
* @return int
*/
public function getTargetDay()
{
$today = date('w');
$today = ($today - 1);
if ($today < 0) {
$today = 6;
}
return $today;
}
}
getTargetDay
はとある事情により、$today
を-1 した日付を返します。
この処理の必要性は、DB には Python で生成されたデータが保存されており、PHP は日曜日始まり、Python は月曜日始まりで始まることに起因しています。
PHP 側の日付を-1 することで Python の曜日に合わせているという感じです。
一方でテストコードは以下のようになっています。
test('getTargetDay is Valid', function () {
$sut = new ProgramService();
$exp = date('w');
$exp = ($exp - 1);
if ($exp < 0) {
$exp = 6;
}
$act = $sut->getTargetDay();
expect($act === $exp)->toBeTrue();
});
これまた良くないコードになってしまっています。
プロダクション側と同じようなコードを書くことで無理やりアサーションしようとしています。
そしてこのコードには重大な問題があります。
日曜日にテストが実行された場合にしか if の中を通らないという点です。
この処理の背景が Python との曜日の歪みを埋めるために生まれており、if の中が処理として重要な部分なはずです。
しかしここを検証できていないテストコードにあまり意味がありません。
というわけでdata('w')
をモックして、0 または 0 以外を返す状態を検証するのがよさそうです。
…が調べたところ困ったことに PHP 標準関数の date をモックするのは困難で、非常にめんどくさそうです。
一方でCarbon
であればテストコード実行を想定しているので、2 行で簡単にモックできます。
修正後コード
ではdate
をCarbon
に置き換えたコードがこちらです。
<?php
namespace App\Services\Programs;
use App\Models\Notifyprogram;
use App\Models\Personality;
use App\Models\Program;
use Carbon\Carbon;
class ProgramService
{
//中略
/**
* 曜日を返す.
*
* @return array
*/
public function getWeekDays()
{
$weekDays = ['0' => 'MonDay', '1' => 'Tuesday', '2' => 'Wednesday', '3' => 'Thursday', '4' => 'FriDay', '5' => 'Saturday', '6' => 'SunDay'];
return $weekDays;
}
/**
* 今日の曜日を返す
* バッチは0が月曜日で6が日曜日なので、1を引く。負数は6にする.
*
* @return int
*/
public function getTargetDay()
{
$Carbon::now()->dayOfWeek;
$today = ($today - 1);
if ($today < 0) {
$today = 6;
}
return $today;
}
}
そしてテストコード側はCarbon
をモックしつつ、C1
網羅したテストが書けていそうです。
test('getTargetDay returns Valid PythonDate When date func resutns other than 0', function () {
$sut = new ProgramService();
$knownDate = Carbon::create(2023, 11, 20, 0, 0, 0); //とある月曜日
Carbon::setTestNow($knownDate);
$act = $sut->getTargetDay();
$exp = 0;
expect($act === $exp)->toBeTrue();
});
test('getTargetDay returns Valid PythonDate When date func resutns 0', function () {
$sut = new ProgramService();
$knownDate = Carbon::create(2023, 11, 19, 0, 0, 0); //とある日曜日
Carbon::setTestNow($knownDate);
$act = $sut->getTargetDay();
$exp = 6;
expect($act === $exp)->toBeTrue();
});
Mutation Testing にて適切に修正されたことを確認
Mutation Testing とは
こちらの記事を参照
上記記事は JavaScript の Mutator であるStryker
を使っていますが、今回は PHP の Mutator であるInfection
を使っています。
Infection
については今度 PHP カンファレンス関西 2024 にてお話しする予定です。よければぜひ聴きにきてください。
適切に修正されたことを確認
修正前は以下のように、if の中の変異を検出できていません。
ですが、修正後は変異が Kill されたことが確認できました。
おわりに
実はこの記事を書くきっかけは、Mutation Testing を実行したところこの記事の例のコード部分が引っかかったからです。
その意味でやはり Mutation Testing は有効なテストコード作成に寄与していると言えそうです。
Discussion