はじめに
こんばんは。今回も積本消化月間 vol.1で手を進みながら勉強した、PHPUnit + Phakeのログ的なものをきれいに整形してブログにしました。
WEB+DB PRESS Vol.108
- 作者:中野 暁人,山本 浩平,大和田 純,曽根 壮大,ZOZOTOWNリプレースチーム,権守 健嗣,茨木 暢仁,松井 菜穂子,新多 真琴,laiso,豊田 啓介,藤原 俊一郎,牧 大輔,向井 咲人,大島 一将,上川 慶,末永 恭正,久保田 祐史,星 北斗,池田 拓司,竹馬 光太郎,粕谷 大輔
- 出版社/メーカー: 技術評論社
- 発売日: 2018/12/22
- メディア: 単行本
phpは普段業務で使っているので、ハマるところはなくスムーズに進みました。
今回も前回同様やってみたログなので、プログラムコードに関しては、本で紹介されている内容をほぼほぼ再現しています。
本題
assert()を使用したテスト
phpunitなどを使わず、assert()を使用した場合のテスト方法を紹介されています。
# sum.php
<?php
function sum(int $a, int $b): int
{
return $a + $b;
}
<?php
require_once 'sum.php';
$result = sum(1, 2);
assert($result === 4, '1 + 2は3を返す');
自分の環境では、普通に実行しただけでは何も表示されなかったのですが、本書で紹介されている、 assert.exception = 1
と zend.assertions = 1
を指定することで、狙い通り例外が発生しました。
php -d assert.exception=1 -d zend.assertions=1 test_sum.php
PHP Fatal error: Uncaught AssertionError: 1 + 2は3を返す in /path/to/phpunit_phake/test_sum.php:6
Stack trace:
thrown in /path/to/phpunit_phake/test_sum.php on line 6
phpカンファレンスで @t_wada さんが話していた、 表明プログラミングの部分で登場していた assert()関数でした。
https://www.youtube.com/watch?v=17i1EL9pBwA
youtubeでも紹介されているのですが、 zend.assertions
を -1
にしてあげれば、assert()
のコードは生成されない動作になります。
なので、開発時に assert()
をテストとして使用して、本番では -1
にしてあげてassert()が生成されない動作にしてあげることが出来ます。
自分が考えるこれの旨味は、testファイルとして分けなくても、実際に使用されるクラスなり関数なりに直接書くことができるというところですが、いくら運用時に生成されないとはいえ、assert()
のコード自体は存在しているので、ちょっとみにくくなるなと思いました。
assert()を「テストツール」として使用するのではなく、youtubeでも紹介されている通り、「暗黙の前提を表明するツール」と割り切って、別でテストツールを使うのが現実的だと思いました。
phptを使ったテスト
これは自分知らなかったのですが、phpの標準テストツールの phpt
を使ったテストが紹介されていました。
ただ、こちらはpearのインストールが必要とのことでした。
おそらくですが、今後pearを使うことはないと思うので、これに関しては読み飛ばしました。
自分毎回お世話になっている PHPUnitを使用したテストです。
インストール等はググれば死ぬほど乗ってるので、省略します。
# Sum.php
<?php
namespace Kojirock;
class Sum
{
public static function sum(int $a, int $b): int
{
return $a + $b;
}
}
<?php
use Kojirock\Sum;
class SumTest extends \PHPUnit\Framework\TestCase
{
@test
public function 2つの値を足し算できること()
{
$this->assertSame(4, Sum::sum(1, 2));
}
}
$ php vendor/bin/phpunit tests
PHPUnit 7.4.3 by Sebastian Bergmann and contributors.
F 1 / 1 (100%)
Time: 54 ms, Memory: 4.00 MB
There was 1 failure:
1) SumTest::2つの値を足し算できること
Failed asserting that 3 is identical to 4.
/path/to/tests/SumTest.php:12
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
よく見る結果が出てきました。
本書では、よく使われる assertメソッドが紹介されていました。
知らないものが多かったというか、自分は正直、assertSame()
と assertTrue()|assertFalse()
と expectException()
expectExceptionMessage
または、 expectExceptionObject
を使うくらいなので、その他はほぼ使いません。。。
フィクスチャを使用する
# TaxCalculator.php
<?php
namespace Kojirock;
use DateTimeInterface;
use DateTime;
class TaxCalculator
{
private $tax3perDate;
private $tax5perDate;
private $tax8perDate;
private $tax10perDate;
public function __construct()
{
$this->tax3perDate = new DateTime('1989-04-01');
$this->tax5perDate = new DateTime('1997-04-01');
$this->tax8perDate = new DateTime('2014-04-01');
$this->tax10perDate = new DateTime('2019-04-01');
}
public function calc(int $amount, DateTimeInterface $current):int
{
if ($this->tax3perDate <= $current && $current < $this->tax5perDate) {
return $this->multiple($amount, 103);
} elseif ($this->tax5perDate <= $current && $current < $this->tax8perDate) {
return $this->multiple($amount, 105);
} elseif ($this->tax8perDate <= $current && $current < $this->tax10perDate) {
return $this->multiple($amount, 108);
} elseif ($this->tax10perDate <= $current) {
return $this->multiple($amount, 110);
}
return $amount;
}
private function multiple(int $amount, int $hundredTimesTaxRate): int
{
$multi = $amount * $hundredTimesTaxRate;
return (int)floor($multi / 100);
}
}
<?php
use Kojirock\TaxCalculator;
class TaxCalculatorTest extends \PHPUnit\Framework\TestCase
{
private $taxCalculator;
public function setUp()
{
$this->taxCalculator = new TaxCalculator();
}
@test
public function 税率5パーセントの金額を計算できる()
{
$date = new DateTime('2014-03-31');
$this->assertSame(105, $this->taxCalculator->calc(100, $date));
}
@test
public function 税率8パーセントの金額を計算できる()
{
$date = new DateTime('2018-10-31');
$this->assertSame(108, $this->taxCalculator->calc(100, $date));
}
@test
public function 税率10パーセントの金額を計算できる()
{
$date = new DateTime('2019-10-01');
$this->assertSame(110, $this->taxCalculator->calc(100, $date));
}
}
$ php vendor/bin/phpunit tests
PHPUnit 7.4.3 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 39 ms, Memory: 4.00 MB
OK (3 tests, 3 assertions)
こちらもよく使用する setUp
の紹介でした。
Phakeを使ったテスト
Mockeryとかは Laravelプロジェクトで少々触ったことがあったのですが、Phakeは使ったことありませんでした。
PHPUnit標準で搭載されているモックツールよりも使い勝手が良いとのことで、試してみます。
インストール
$ composer require --dev phake/phake:3.1.3
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Installing phake/phake (v3.1.3): Downloading (100%)
phake/phake suggests installing doctrine/common (Allows mock annotations to use import statements for classes.)
phake/phake suggests installing hamcrest/hamcrest-php (Use Hamcrest matchers.)
Writing lock file
Generating autoload files
——
実装コード
# CostReport.php
<?php
namespace Kojirock;
class CostReport
{
private $repository;
private $taxCalculator;
public function __construct(CostRepository $repository, TaxCalculator $taxCalculator)
{
$this->repository = $repository;
$this->taxCalculator = $taxCalculator;
}
public function report(): array
{
$rows = $this->repository->findAll();
return array_map(function (array $row) {
return $this->taxCalculator->calc($row['value'], new \DateTime($row['date']));
}, $rows);
}
}
<?php
namespace Kojirock;
class CostRepository
{
private $pdo;
public function __construct(\PDO $pdo)
{
$this->pdo = $pdo;
}
public function findAll()
{
$stmt = $this->pdo->query('SELECT * FROM report_table');
$stmt->execute();
return $stmt->findAll();
}
}
<?php
use Kojirock\TaxCalculator;
use Kojirock\CostReport;
use Kojirock\CostRepository;
class CostReportTest extends \PHPUnit\Framework\TestCase
{
private $costReport;
private $mockRepository;
public function setUp()
{
$this->mockRepository = Phake::mock(CostRepository::class);
$this->costReport = new CostReport($this->mockRepository, new TaxCalculator());
}
@test
public function 税率8パーセントのコストをレポートできる()
{
Phake::when($this->mockRepository)
->findAll()
->thenReturn([[
'date' => '2018-10-31',
'value' => 100
]]);
$this->assertSame([108], $this->costReport->report());
}
}
php vendor/bin/phpunit tests
PHPUnit 7.4.3 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 59 ms, Memory: 4.00 MB
OK (1 test, 1 assertion)
PDOを使用してDBに接続するCostRepositoryというクラスをmockしてテストできるようにしました。
自分が使用したことのある Mockery
と直感的にあまり変わりませんでした。
だいたいmockを使用するのって外部API連携などの部分かと思います。
終わりに
最近仕事がマジで忙しくて、ブログはもちろん、1月1回チャレンジ、毎週運動が全然出来てない状況です。
とりあえずGWすぎれば落ち着くはずなので、それまでは簡単なブログのみになっちゃいそうです。。
現場からは以上です。