概要
ci-phpunit-testで、DBアクセスを伴うModelクラスのUnit Testを実装します。
CodeIgniter3でci-phpunit-test 2.xの環境を用意する方法については、CodeIgniter3 + PHPUnit 9.4.0 + ci-phpunit-test dev-2.x を利用した Unit Test環境の整備に載っています。
このModelクラスのUnit Testの実装では、事前にCodeIgniter3でci-phpunit-test 2.xを利用したUnitTestいろいろ②(Migrationの利用)の実装をしておく必要があります。
ソースの構成
.application
├── models
│ └── Gmo_result_notification_logs_model.php
├── tests
│ ├── models
│ │ └── Gmo_result_notification_logs_model_test.php
各ソースの内容とポイント
テスト対象となるDBアクセスを含むモデルクラス
特に変わったことはしていない、通常のモデルクラスです。
ログを削除する処理は基本的に必要ないですが、UnitTestがしやすいように削除処理も入れています。
<?php
/**
* GMOから実行される結果通知プログラムのログ情報モデルクラス
*/
class Gmo_result_notification_logs_model extends CI_Model {
/** ID(自動採番) */
public $id;
/** 決済方法(credit:クレジット,cvs:コンビニ ...etc) */
public $pay_method;
/** 結果通知プログラムに送られてきたパラメータをJSON変換したもの */
public $parameter;
/** 登録日時 */
public $create_date;
/** モデルで操作するテーブル名 */
private $_table = 'gmo_result_notification_logs';
public function __construct() {
parent::__construct();
}
/**
* ログを登録する
*/
public function insert_log() {
$this->db->insert($this->_table, $this);
}
/**
* ログを削除する
*/
public function delete_log() {
$this->db->delete($this->_table, array('id' => $this->id));
}
/**
* 最新10件のログを取得する
*/
public function get_last_ten_logs() {
$query = $this->db->limit(10)
->order_by('id', 'desc')
->get($this->_table, 10);
return $query->result();
}
}
DBアクセスを含むModelクラスのテストクラス
ポイント
- setUpBeforeClass() でMigrationを実行し、テスト用のDBの初期構築を行っている。
- ci-phpunit-testで実行されるとき、ENVIRONMENT=testingで実行されるため、ここでloadされるデータベースはapplication/config/testing/database.phpの定義を参照している。
- tearDownAfterClass() でMigrationのrollbackを実行し、version=0の状態に戻している。
- 各CI_Migrationを継承するクラスの
down()
が実行されて最初の状態に戻るイメージ
- 各CI_Migrationを継承するクラスの
<?php
/**
* Gmo_result_notification_logs_modelクラスのテスト
*/
class Gmo_result_notification_logs_model_test extends TestCase
{
/**
* テストクラス内の初回(1回)のみ実行する初期化処理
*/
public static function setUpBeforeClass():void
{
// テスト用DBを初期化(migration)して利用
$CI =& get_instance();
$CI->load->database();
$CI->load->library('migration');
$CI->migration->current();
}
/**
* テスト初期処理(各テストメソッドの実行前処理)
*/
public function setUp():void
{
$this->resetInstance();
$this->CI->load->model('Gmo_result_notification_logs_model');
$this->obj = $this->CI->Gmo_result_notification_logs_model;
}
/**
* テストクラス内の最後(1回)のみ実行する終了処理
*/
public static function tearDownAfterClass():void
{
$CI =& get_instance();
$CI->load->library('migration');
$CI->migration->version(0);
}
/**
* @test
*/
public function ログの登録が正常に完了すること():void
{
$this->obj->id = 999;
$this->obj->pay_method = 'credit';
$this->obj->parameter = '{"aaa": "bbb", "ccc": "ddd"}';
$this->obj->create_date = date('Y-m-d h:m:s');
$this->obj->insert_log();
$this->assertTrue(true); // ここまできたらOK
}
/**
* @test
* @depends ログの登録が正常に完了すること
*/
public function 登録済みのログが削除できること():void
{
$this->obj->id = 999;
$this->obj->delete_log();
$this->assertTrue(true); // ここまできたらOK
}
/**
* @test
* @depends 登録済みのログが削除できること
*/
public function 登録した順序の降順で取得できること():void
{
$insert_values = array(
array(
'id'=> 1,
'pay_method'=> 'credit',
'parameter'=> '{"aaa": "bbb", "ccc": "ddd"}',
'create_date'=> date('Y-m-d h:m:s')
),
array(
'id'=> 2,
'pay_method'=> 'cvs',
'parameter'=> '{"eee": "ffff", "gggg": "hhh"}',
'create_date'=> date('Y-m-d h:m:s')
),
array(
'id'=> 3,
'pay_method'=> 'paypay',
'parameter'=> '{"iii": "jjj", "kkk": "lll"}',
'create_date'=> date('Y-m-d h:m:s')
)
);
foreach ($insert_values as $value) {
$this->obj->id = $value['id'];
$this->obj->pay_method = $value['pay_method'];
$this->obj->parameter = $value['parameter'];
$this->obj->create_date = $value['create_date'];
$this->obj->insert_log();
}
// ログを取得
$logs = $this->obj->get_last_ten_logs();
// 降順に並び替え
$reverse_values = array_reverse($insert_values);
for ($i = 0; $i < count($reverse_values); $i++) {
$this->assertEquals($reverse_values[$i]['id'], $logs[$i]->id);
$this->assertEquals($reverse_values[$i]['pay_method'], $logs[$i]->pay_method);
$this->assertEquals($reverse_values[$i]['parameter'], $logs[$i]->parameter);
$this->assertEquals($reverse_values[$i]['create_date'], $logs[$i]->create_date);
}
}
}
実際のテスト実行
まだ何もしていない状態では、ENVIRONMENT=testingのDB接続先となっているphpunitdb
のデータベース内には何もテーブルがない状態。
# mysql -u root -p
MariaDB [(none)]> connect phpunitdb;
MariaDB [phpunitdb]> show tables;
Empty set (0.001 sec)
phpunitを実行してテストを動かす。
# cd /var/www/html/codeigniter3/application/tests
# ../../vendor/bin/phpunit --testdox
PHPUnit 9.4.0 by Sebastian Bergmann and contributors.
Warning: No code coverage driver available
Warning: Your XML configuration validates against a deprecated schema.
Suggestion: Migrate your XML configuration using "--migrate-configuration"!
Gmo_api_model_test
✔ G m o決済リンク urlが正常に取得できること
✔ G m o決済リンク url取得失敗の詳細が exceptionで伝播されること
✔ G m oカード会員編集 urlが正常に取得できること
Gmo_result_notification_logs_model_test
✔ ログの登録が正常に完了すること
✔ 登録済みのログが削除できること
✔ 登録した順序の降順で取得できること
Time: 00:00.047, Memory: 8.00 MB
OK (6 tests, 29 assertions)
テストを実行したので、データベースの状態を再確認してみる。
# mysql -u root -p
MariaDB [(none)]> connect phpunitdb;
MariaDB [phpunitdb]> show tables;
+---------------------+
| Tables_in_phpunitdb |
+---------------------+
| migrations |
+---------------------+
1 row in set (0.000 sec)
MariaDB [phpunitdb]> select * from migrations;
+---------+
| version |
+---------+
| 0 |
+---------+
1 row in set (0.002 sec)
このように、UnitTest自体は正常に完了し、phpunitdbにはmigrationsテーブルのみが残った状態となっています。(本当は作られているはずのgmo_result_notification_logs
テーブルが残っていない。)
version=0となっており、tearDownAfterClass()
で指定したバージョンまでrollbackされていることが確認できる。
最後に
今回の対応では、特にテーブルを作った後、初期データのロードなどは行っていないため、毎回初期化しても耐えられるレベルのものでした。
初期データのロードが大量にあったりする場合は、テストクラス内で依存するデータのみをロードするようにしたり、データに依存しないテスト(Mockを利用したテスト)にすることで、規模が大きくなったときに対応する必要がありそうです。
とはいえ、小さいサービスのうちは、こういったテストを作っておくことで、Migrationの検証にもなるし、いいかなぁとは思っています。