CakePHP3とPHPUnitでControllerの単体テストを実施してみた

CakePHP3とPHPUnitでの単体テストコードの勉強をするために簡単なコードを書いて実施してみた。

環境
CakePHP 3.6.3
PHPUnit 5.7.27
PHP 7.0.27

UsersController.phpにsum()を追加する。
sum()はGETまたはPOSTから渡されるパラメータa、bの値を足し算する。

//UsersController.php
public function sum()
{
    $this->request->allowMethod(['post', 'get']);
    if($this->request->is('post')) {
        $a = $this->request->getData('a');
        $b = $this->request->getData('b');
    } elseif ($this->request->is('get')) {
        $a = $this->request->getQuery('a');
        $b = $this->request->getQuery('b');
    }
    $this->set('sum', $a + $b);
}

Controllerがsum()なのでsum.ctpテンプレートを追加する。

//Template/Users/sum.ctp
<?php
echo $sum;

UsersControllerTest.phpにはGETとPOST用のコードを作成する。
viewVariable()はControllerからset()した値を取得している。

//UsersControllerTest.php
public function testSumGet()
{
    $this->get('Users/sum?a=0&b=0');
    $sum = $this->viewVariable('sum');
    $this->assertEquals(0, $sum);

    $this->get('Users/sum?a=10&b=20');
    $sum = $this->viewVariable('sum');
    $this->assertEquals(30, $sum);

    $this->get('Users/sum?a=1000000&b=2000000');
    $sum = $this->viewVariable('sum');
    $this->assertEquals(3000000, $sum);
}

public function testSumPost()
{
    $this->post('Users/sum', ['a' => 0, 'b' => 0]);
    $sum =  $this->viewVariable('sum');
    $this->assertEquals(0, $sum);

    $this->post('Users/sum', ['a' => 10, 'b' => 20]);
    $sum =  $this->viewVariable('sum');
    $this->assertEquals(30, $sum);

    $this->post('Users/sum', ['a' => 1000000, 'b' => 2000000]);
    $sum =  $this->viewVariable('sum');
    $this->assertEquals(3000000, $sum);
}

PHPUnitを実行すると以下のような成功メッセージが出力される。

> php vendor\phpunit\phpunit\phpunit --coverage-html ./report.html tests\TestCase\Controller\UsersControllerTest.php

PHPUnit 5.7.27 by Sebastian Bergmann and contributors.
.. 2 / 2 (100%)

Time: 1.91 seconds, Memory: 12.00MB

OK (2 tests, 6 assertions)

Generating code coverage report in HTML format ... done

・途中で手が止まった点
testSumGet()とtestSumPost()の2つ目以降のテストがエラーになっていた。
メッセージを見るとControllerから受け取る$sumがnullになっていた。
原因はTemplate/Users/sum.ctpを作成していなかったため。
cli-error.logにテンプレートファイルが見つからないエラーが出ていた。

・今回解決できなかった点
UsersController.phpのsum()はGETとPOST以外は許容していない。
allowMethod()で指定したメソッド以外のアクセスは
500エラーが返るのでassertResponseFailure()を使ったテストコードを作った。
その結果、テストコードは成功した。
しかし、カバレッジレポートが生成されるがテストが通過したことにならなかった。

PHPの公式サポートの状況を調べてみた

いま、PHPは5系と7系があるのはなんとなく知っていた。
けれど、詳しいバージョンとサポート終了日を知らなかったので調べてみた。

参考にしたページ
PHP: Supported Versions

Version Latest Release Active Support Security Support
5.6 2018/04/26 (5.6.36) 2017/01/19 2018/12/31
7.0 2018/04/26 (7.0.30) 2017/12/03 2018/12/03
7.1 2018/06/22 (7.1.19) 2018/12/01 2019/12/01
7.2 2018/06/21 (7.2.7) 2019/11/30 2020/11/30

Active Supportの日付はバグやセキュリティーの問題があればその日まではサポートしてくれる締切日。
Security Supportの日付は重大なセキュリティーの問題のみその日まではサポートしてくれる締切日。

5.6と7.0は今年中で終了。7.1も来年で終了してしまう。
7.3はまだアルファ版なので、今からPHPのバージョンを変えるとすると7.2か。

PHPのisset()、empty()、is_null()の結果の違い

PHPのisset()、empty()、is_null()の結果の違い。
PHP 7.0.27で調べた結果。

評価する値 isset() empty() is_null()
0 true true false
‘0’ true true false
1 true false false
‘1’ true false false
true true false
‘aiueo’ true false false
null false true true
true true false false
false true true false
[] true true false
[‘aiueo’] true false false
‘name’ => ‘tanaka’ true false false
$var(初期化していない変数) false true true

調べたときに使ったコード。

#!/usr/bin/php
<?php

$values = [
  0,         // 数値の0
  '0',       // 文字の0
  1,         // 数値の1
  '1',       // 文字の1
  '',        // 空文字
  'aiueo',   // 文字列
  null,      
  true,      
  false,     
  [],        // 空配列
  ['aiueo'], // 配列
  'name' => 'tanaka',
  $val       // 初期化していない変数
];

echo '|isset|empty|is_null|';

foreach($values as $value) {
  var_export($value);
  echo PHP_EOL;
  echo '|';
  var_export(isset($value));
  echo '|';
  var_export(empty($value));
  echo '|';
  var_export(is_null($value));
  echo '|';
  echo PHP_EOL . PHP_EOL;
}

?>

CakePHP3でPHPUnitのテストをしようとしていたときに出たエラーメッセージ

CakePHP3でPHPUnitのテストをしようとしていたときに出たエラーメッセージ。

・テスト用のデータベースが見つからなかっときのエラーメッセージ
Exception: Unable to insert fixtures for “test class path” test case.
SQLSTATE[HY000] [1049] Unknown database ‘database name’ in [cake php class file path]

原因
‘database name’データベースが存在しないとき

対処
app.phpファイルの’Datasources’ => ‘test’ => ‘database’にテスト用のデータベースを指定する。

・テスト用のデータベースのusernameかpasswordが間違っているときのエラーメッセージ
Exception: Unable to insert fixtures for “test class path” test case.
SQLSTATE[HY000] [1045] Access denied for user ‘user name’@’localhost’ (using password: YES) in [cake php class file path]

原因
ユーザ名かパスワード誤り

対処
app.phpファイルの’Datasources’ => ‘test’ => ‘username’、または’password’にテスト用のデータベースのユーザ名、パスワードを指定する。

CakePHP3とPHPUnitを使って単体を実施したらClass could not be foundメッセージが表示された

CakePHP3のドキュメントを元にPHPUnitを使って単体を実施したら以下のメッセージが表示された。
Class ‘tests\TestCase\View\Helper\ProgressHelperTest’ could not be found in ‘tests\TestCase\View\Helper\ProgressHelperTest.php’.

原因
ドキュメントを参考にして書いていたので「<?php」を付け忘れていた。

付けたあとに実行すると正常にPHPUnitが終了した。

vendor\bin\phpunit tests\TestCase\View\Helper\ProgressHelperTest
PHPUnit 5.7.27 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 256 ms, Memory: 6.00MB

OK (1 test, 3 assertions)

バージョン
CakePHP 3.x
PHPUnit 5.7.27

PHPで前月・翌月の末日を取得する。

PHPで前月・翌月の末日を取得するときに気を付けないといけない点。
dateとstrtotimeを用いて翌月の末日を取得したいときに以下のように当月末日(2017-01-31)を指定しても期待した結果と異なる。

echo date('Y-m-d', strtotime('+1 month' . '2017-01-31'));
// 期待した結果:2017-02-28
// 実際の結果:2017-03-03

これはphp.netにも注意書きがされている。
http://php.net/manual/ja/datetime.formats.relative.php

月数を相対指定すると、その途中に経過する月の日数を使って結果を算出します。 たとえば “+2 month 2011-11-30” の結果は “2012-01-30” となります。 11 月の日数は 30 日、12 月の日数は 31 日なので、 その合計である 61 日後となるわけです。

前月・翌月の末日を取得したいときは以下のようにする。

echo date('Y-m-d', strtotime('last day of previous month' . '2017-01-31'));
// 結果:2016-12-31

echo date('Y-m-d', strtotime('last day of next month' . '2017-01-31'));
// 結果:2017-02-28

// 月の途中でも計算してくれる。
echo date('Y-m-d', strtotime('last day of previous month' . '2017-01-15'));
// 結果:2016-12-31

おまけ
末日ではなく前月・翌月の初日を取得したいとき。

echo date('Y-m-d', strtotime('first day of previous month' . '2017-01-31'));
// 結果:2016-12-01

echo date('Y-m-d', strtotime('first day of next month' . '2017-01-31'));
// 結果:2017-02-01

// 月の途中でも計算してくれる。
echo date('Y-m-d', strtotime('first day of previous month' . '2017-01-15'));
// 結果:2016-12-01

PHP 5.6.32で確認した。