目次
PHPUnitでデータベースのテスト
前回の記事でテスト対象DBとプログラムの作成を行いましたので、今回はテストケースを作成して実際にユニットテストを実行していきます。
環境: 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の正しい動作を担保していくことが出来るようになります。