Help us understand the problem. What is going on with this article?

CodeIgniter3でci-phpunit-test 2.xを利用したUnitTestいろいろ③(DBアクセスを伴うModelクラスのUnit Test)

概要

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がしやすいように削除処理も入れています。

application/models/Gmo_result_notification_logs_model.php
<?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()が実行されて最初の状態に戻るイメージ
application/tests/models/Gmo_result_notification_logs_model_test.php
<?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の検証にもなるし、いいかなぁとは思っています。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away