Photo by Chris Ried on Unsplash
この記事はユアマイスター Advent Calendar 2020 5日目の記事です。
こんにちは!
都内でWebエンジニアをしている南です!
今回は、最近仕事で知ったPHPUnitを使って内部時間を固定する方法について紹介します。
PHPUnitでライブラリを使った内部時間を固定する方法は見かけますが、意外とライブラリを使わずに実現する方法がなかったりするのだなあと感じました。
なので、この記事ではライブラリを使わずに素のPHPとPHPUnitを使って、テストを行う内部時間を固定する方法について解説します。
内部時間を固定するテストについて
Photo by Markus Winkler on Unsplash
処理を書いている中で、下記のような処理を書くことがあると思います。
・毎朝決まった時間にデータベースからデータを取得する処理。
ただ、実際上記のようなテストを書く際、テスト用のデータを固定してもテストする時間帯によっては処理の結果が変わってしまうことがあります。
実際最近仕事でやっていた処理も同じような処理で、時間帯によって処理の結果が変わってしまうのは全然良いのですが、テストコード上だとちゃんと正しく処理が動いているかを把握したい場合はそうはいきません。
PHPUnitの処理をお昼の13時までに回せば、テストは通りますが、それ以降だとテストが通らないとかなると嫌ですよね。。。
そこで、処理内で使用している日付を固定する方法について探しました。
内部時間を固定する方法
Photo by Campaign Creators on Unsplash
結論を先にいってしまうと、下記のようなコードを書くことで実現することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?php namespace App; class Timer { public $dateTime; public function __construct(int $dateTime) { $this->dateTime = $dateTime; } public function isSameTime() { if ($this->getNow() === $this->dateTime) { return true; } return false; } public function getNow(): int { return time(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<?php namespace Test; use App\Timer; use PHPUnit\Framework\TestCase; class TimerTest extends TestCase { private $TimerMock; private $fixedDayTime; public function setUp(): void{ parent::setUp(); $this->fixedDayTime = strtotime(date('2020-11-30 09:00:00')); $this->TimerMock = $this->getMockBuilder("App\Timer") // Constructorに引数が必要な場合は、引数を指定する。 ->setConstructorArgs([strtotime(date('2020-11-30 09:00:00'))]) ->setMethods(['getNow']) ->getMock(); } public function tearDown(): void{ unset($this->fixedDayTime); unset($this->Timer); parent::tearDown(); } public function testTimer() { // 日付を固定しない場合 $this->assertSame(false, $this->Timer->isSameTime()); // 値を固定したいメソッドと返り値を指定する。 $this->TimerMock ->method('getNow') ->will($this->returnValue($this->fixedDayTime)); // 日付を固定する場合 $this->assertSame(true, $this->Timer->isSameTime()); } } |
コード解説
Photo by Priscilla Du Preez on Unsplash
isSameTimeメソッドについて
isSameTimeメソッドは、単純に今の時間と指定した時間帯が同じかどうかを判断する処理です。
指定した時間と同じ場合は、True。
違う場合は、Falseを返します。
getNowメソッドについて
getNowメソッドは、time関数を使って今現在の日付をタイムスタンプ形式で返しています。
また、ここはあえて独立したメソッドとして切り出しています。
理由としては、こういう風にメソッドに切り出さないとテストを行うことができないためです。
Mockメソッドについて
Mockメソッドは今回の肝になるメソッドです。
具体的には、下記の部分です。
1 2 3 4 5 6 7 8 9 10 |
public function setUp(): void{ parent::setUp(); $this->fixedDayTime = strtotime(date('2020-11-30 09:00:00')); $this->TimerMock = $this->getMockBuilder("App\Timer") // Constructorに引数が必要な場合は、引数を指定する。 ->setConstructorArgs([strtotime(date('2020-11-30 09:00:00'))]) ->setMethods(['getNow']) ->getMock(); } |
まずは、getMockBuilderメソッドでMockしたいクラスを指定します。
Mockとは、模倣という意味です。
getMockBuilderメソッドでMockしたいクラスを指定したら、次はsetConstructorArgsでコンストラクタに渡す引数を配列形式で指定します。
もし、コンストラクタがない場合は、setConstructorArgsの部分の記述はスキップしても大丈夫です。
コンストラクタに引数を渡したら、setMethodsメソッドで返り値などを指定したいメソッドを指定します。
最後にgetMockメソッドで、設定を行ったMockオブジェクトを取得します。
returnValueメソッドについて
Mockオブジェクトが作れたら、あとは実際の処理の中で使用していきます。
今回だと、下記の箇所で使用しています。
1 2 3 4 5 6 7 8 |
public function testTimer() { $this->assertSame(false, $this->Timer->diffTimes()); // 値を固定したいメソッドと返り値を指定する。 $this->TimerMock->method('getNow') ->will($this->returnValue($this->fixedDayTime)); $this->assertSame(true, $this->Timer->diffTimes()); } |
先程のsetMethodsメソッドで指定したメソッドで、Mockしたいメソッドをmethodメソッドで指定します。
指定ができたら、willメソッドで挙動の制御を行います。
今回だと$this->returnValue($this->fixedDayTime)というように制御しているので、strtotime(date(‘2020-11-30 09:00:00’))が返ってくる($this->returnValue())というように指定しています。
そのため、1番目のassertSameでは、時間を固定していないため、結果は「False」。
2番目のassertSameでは時間を固定しているため、結果は「True」が返ってきます。
もう少し詳しく知られたい方は、下記のPHPUnitの公式ドキュメントを参考にしてみてください。
8. テストダブル
まとめ
今回は、ライブラリを使わずに素のPHPとPHPUnitを使ってテスト中の内部時間を固定する方法を紹介しました。
ライブラリだと「Carbon」や「Chronos」などのライブラリがあり、それぞれで内部時間を固定する方法があります。
ただ、素のPHPを使った方法があまり詳しくまとまっていないので、自分用のメモとしてまとめました。
今回まとめた内容があなたのお役に立てれば幸いです。