PHPUnitでユニットテスト⑤ データベースをテストする 後編

PHPUnitでデータベースのテスト


前回の記事でテスト対象DBとプログラムの作成を行いましたので、今回はテストケースを作成して実際にユニットテストを実行していきます。

PHPUnitでユニットテスト⑤ データベースをテストする 前編

環境: PHPUnit 6.5.14, PHP 7.4.8, mysql Ver 8.0.19

(SAMPLE)

テストケースクラス、及びテストケースクラスから利用する設定ファイルを用意します。

├── app
│   ├── composer.json
│   ├── composer.lock
│   ├── data
│   │   └── phpunit_dataset
│   │       ├── BooksTest_after_insert.yml
│   │       ├── BooksTest_after_update.yml
│   │       └── BooksTest_fixture.yml
│   ├── phpunit.xml
│   ├── src
│   │   └── Book.php
│   ├── test
│   │   ├── BookTest.php
│   │   └── Generic_Tests_DatabaseTestCase.php
│   └── vendor
└── database.sql

テストデータの用意

テスト実行前の状態と実行後の期待値の整合性を保つため、ひとつのテストを実行する度に、DB内のデータを決まった値で初期化する必要があります。

この初期化する動作、または初期化データのことをフィクスチャーと呼びます。

フィクスチャー用のデータは様々な形式で用意することが出来ますが、今回はYAMLというデータ形式で作成する方法を紹介します。

BooksTest_fixture.yml

books:
  -
    id: 1
    title: 絵で見てわかるITインフラの仕組み
    author: 山崎 泰史
  -
    id: 2
    title: シリコンバレー式超ライフハック
    author: デイヴ・アスプリー
  -
    id: 3
    title: 試験によく出る 基本情報技術者試験問題集(午後)
    author: 角谷 一成
  -
    id: 4
    title: なぜ?がわかるデータベース
    author: 小笠原 種高

上記のように、.ymlという拡張子形式のファイルをプロジェクト内に作成します。

最初のbooks:がDB内のデータを保存するテーブル名を指し、次の行以降からはテーブルに挿入する1レコード毎のデータを-(ハイフン)区切りで記述し、

id:
title:
author:

と1レコード毎にそれぞれカラム(列)の値を指定していきます。

フィクスチャーのデータが、テスト実行毎にbooksテーブルのデータとして上書きされるようになります。

同じ階層にBooksTest_after_insert.ymlという異なるデータも作成しておきます。(後述)

DBの接続設定

DBの接続情報は設定ファイルphpunit.xmlに記述でき、
パラメータをconstで設定することでテストケースクラス内から定数として利用することが出来ます。

phpunit.xml

<phpunit colors="true"
         verbose="true"
         bootstrap="vendor/autoload.php">
    <php>
        <const name="DSN" value="mysql:host=127.0.0.1;port=3306;dbname=sample_test;charset=utf8mb4" />
        <const name="DB_USERNAME" value="username" />
        <const name="DB_PASSWORD" value="password" />
    </php>
    <testsuites>
        <testsuite name="dbtest">
            <directory>test</directory>
        </testsuite>
    </testsuites>
</phpunit>

テストケースクラスの作成

公式ドキュメント推奨のテストケース作成方法

テストをより汎用的にするため、DB接続機能であるgetConnectionメソッドを下記のように抽象クラスに定義し、テストケースクラスから継承しています。
これにより子クラスとして作成するテストケースは毎回同じDBに接続することが出来、操作対象のテーブルやフィクスチャーはテストケース毎に変えられます。

(抽象クラス)Generic_Tests_DatabaseTestCase.php

<?php

namespace app\test;

abstract class Generic_Tests_DatabaseTestCase extends \PHPUnit\Framework\TestCase
{
    use \PHPUnit\DbUnit\TestCaseTrait;

    // PDO のインスタンス生成は、クリーンアップおよびフィクスチャ読み込みのときに一度だけ
    static protected $pdo = null;

    // PHPUnit\DbUnit\Database\Connection のインスタンス生成は、テストごとに一度だけ
    private $conn = null;

    final public function getConnection()
    {
        if ($this->conn === null) {
            if (self::$pdo == null) {
                self::$pdo = new \PDO(DSN, DB_USERNAME, DB_PASSWORD);
            }
            // $this->conn = $this->createDefaultDBConnection(self::$pdo, $GLOBALS['DB_DBNAME']);
            $this->conn = $this->createDefaultDBConnection(self::$pdo);
        }

        return $this->conn;
    }
}
テストケースクラス BookTest

テスト対象プログラム、BookのテストケースクラスとしてBookTestを作成します。

BookTest.php

<?php
use PHPUnit\Framework\TestCase;
use PHPUnit\DbUnit\TestCaseTrait;
use PHPUnit\DbUnit\DataSet\YamlDataSet;
use app\src\Book;
use app\test\Generic_Tests_DatabaseTestCase;

class BookTest extends Generic_Tests_DatabaseTestCase
{
    public $book;
    
    protected function getDataSet()
    {
        $dataset = new YamlDataSet(dirname(__FILE__)."/../data/phpunit_dataset/BooksTest_fixture.yml");
        return $dataset;
    }

    public function testInsert()
    {
        $book = new Book(self::$pdo);
        $book->insert('title_test_example', 'author_test_example');

        $result = $this->getConnection()->getRowCount('books');
        $this->assertEquals(5, $result);
        
        // データ挿入後のデータセットを読み込み
        $after_dataset = new YamlDataSet(dirname(__FILE__)."/../data/phpunit_dataset/BooksTest_after_insert.yml");

        // // ファイルから読み込んだデータセットから、データテーブルを取得
        $expected = $after_dataset->getTable('books');

        $test = $this->getConnection()->createQueryTable(
            'books', 'SELECT * FROM books'
        );

        $this->assertTablesEqual($expected, $test);
    }

    public function testFindTitleById()
    {
        $book = new Book(self::$pdo);
        $result = $book->findTitleById(4);
        
        $this->assertEquals('なぜ?がわかるデータベース', $result);
    }

    public function testUpdate()
    {
        $book = new Book(self::$pdo);
        $result = $book->update('updated title', 'updated author', 1);
        
        // データ更新後のデータセットを読み込み
        $after_dataset = new YamlDataSet(dirname(__FILE__)."/../data/phpunit_dataset/BooksTest_after_update.yml");

        // // ファイルから読み込んだデータセットから、データテーブルを取得
        $expected = $after_dataset->getTable('books');

        $test = $this->getConnection()->createQueryTable(
            'books', 'SELECT * FROM books'
        );

        $this->assertTablesEqual($expected, $test);
    }
}

用意したテストの中からtestInsertメソッドについて解説します。

まず、bookクラスのinsertメソッドで1件のレコードを挿入後、getRowCount機能でテーブルに存在するデータ(レコード)の件数を取得しています。

フィクスチャーとして用意したBooksTest_fixture.yml内のデータは4件のため、insert実行後の全件数の期待値を5件として、assertEqualsで検証しています。

次に、insert実行後のデータの状態を記述したBooksTest_after_insert.ymlから、getTable機能を使って期待される値を保持したDBテーブルを作成します。
(YAMLファイルからDBテーブルを組み立てるイメージ)

最後はcreateQueryTable機能を使って実際のDBからinsert実行後のbooksテーブルを取得し、そちらと先ほどgetTableで作成した期待値のテーブルデータをassertTablesEqualで比較しています。

テストを実行する

テストケースの作成、準備が出来たらユニットテストを実行します。

vendor/bin/phpunit test/BookTest.php

実行結果

F..                                                                 3 / 3 (100%)

Time: 132 ms, Memory: 4.00MB

There was 1 failure:

1) BookTest::testInsert
Failed asserting that 
+----------------------+----------------------+----------------------+
| books                                                              |
+----------------------+----------------------+----------------------+
|          id          |        title         |        author        |
+----------------------+----------------------+----------------------+
|          1           |  絵で見てわかるITインフラの仕組み   |        山崎 泰史         |
+----------------------+----------------------+----------------------+
|          2           |   シリコンバレー式超ライフハック    |      デイヴ・アスプリー       |
+----------------------+----------------------+----------------------+
|          3           | 試験によく出る 基本情報技術者試験問題集 |        角谷 一成         |
+----------------------+----------------------+----------------------+
|          4           |    なぜ?がわかるデータベース     |        小笠原 種高        |
+----------------------+----------------------+----------------------+
|          5           |      title_test      | author_test_example  |
+----------------------+----------------------+----------------------+

 is equal to expected (table diff enabled)
+----------------------+----------------------+----------------------+
| books                                                              |
+----------------------+----------------------+----------------------+
|          id          |        title         |        author        |
+----------------------+----------------------+----------------------+
|          1           |  絵で見てわかるITインフラの仕組み   |        山崎 泰史         |
+----------------------+----------------------+----------------------+
|          2           |   シリコンバレー式超ライフハック    |      デイヴ・アスプリー       |
+----------------------+----------------------+----------------------+
|          3           | 試験によく出る 基本情報技術者試験問題集 |        角谷 一成         |
+----------------------+----------------------+----------------------+
|          4           |    なぜ?がわかるデータベース     |        小笠原 種高        |
+----------------------+----------------------+----------------------+
|          5           | 'title_test_example' | author_test_example  |
+----------------------+----------------------+----------------------+

.

/Users/ken/Sites/phpunit-dbunit-lesson/app/test/BookTest.php:36

FAILURES!
Tests: 3, Assertions: 4, Failures: 1.

上記結果より、insertによるデータ挿入機能自体は正常に動作していますが、assertTablesEqualのアサーションが失敗していることがわかります。

結果に表示されている2つのテーブルの値を参照すると、insert実行時にパラメータで指定しているデータと、期待値として用意しているデータが異なっていますので、どちらかを修正してテストを成功させます。

今回は解説を省きますが、同テストケース内、testUpdateによるデータ更新のテストも専用の結果ファイルを用意して同様にアサーションします。


このようにDBUnitの機能を利用してユニットテストを行うことで、アプリケーションで利用するDBやSQLの正しい動作を担保していくことが出来るようになります。

Follow me!

PHPUnitでユニットテスト⑤ データベースをテストする 後編” に対して1件のコメントがあります。

コメントは受け付けていません。