PHPUnitでユニットテスト⑤ データベースをテストする 後編
目次
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の正しい動作を担保していくことが出来るようになります。
“PHPUnitでユニットテスト⑤ データベースをテストする 後編” に対して1件のコメントがあります。
コメントは受け付けていません。