アラタナエンジニアブログ

aratana Engineer's Blog

CodeceptionとAspectMockでユニットテストしてみた

皆さん、テスト自動化してますか?
こんにちわ、QA担当の中島です。社内では、テスト自動化や品質改善のための仕組みづくりなどに取り組んでいます。

DBアクセスがあるようなメソッドユニットテストを書くときに、DB依存をしないようにDBアクセスクラスをモックと入れ替えてテストをしたいけど、メソッド内でDBクラスをnewしてて心が折れそうになることありますよね。はい、私のことです。

DI(依存性の注入)に実装を変えたいけど、すぐにできないことも多いので、そういう現実と戦うときの方法について今日は書きます。

スティングフレームワーク

Modern PHP testingなCodeceptionとnot an ordinary PHP mocking frameworkなAspectMockを使用します。

全体概要

今回はAspectMockで以下のようにDBアクセス処理を置き換えます。

f:id:ombran:20150202114301p:plain

前提

使用したツールとバージョンは以下となります。

ツール バージョン 説明
PHP 5.4以上 使用言語
Composer 1.0.0-alpha9 パッケージ管理ツール
Codeception 2.0.9 スティングフレームワーク
AspectMock 0.5.1 モッキングフレームワーク

インストール

CodeceptionとAspectMockのインストールはComposerでやります。
Composerがインストールされてない場合は、インストールしてから実行してください。

composer.jsonの作成

composer.jsonに以下を書きます。

CodeceptionとAspectMockのインストール

以下のコマンドを実行してください。

$ composer install

これでCodeceptionとAspectMockがインストールされます。

準備

CodeceptionとAspectMockを利用できる環境にするための準備方法について説明します。

1. Codeceptionの初期化

まずは、Codeceptionの初期化を行います。
以下のコマンドを実行してください。

$ ./vendor/bin/codecept bootstrap

上記コマンドを実行することでtestsディレクトリが新規作成され、その中にテスト用のファイルが作成されます。

2. AspectMockの初期化

tests/_bootstrap.phpにAspeceMockの初期化処理を追加します。

3. AspectMockのクリーンアップ処理追加

tests/_support/UnitHelper.phpにAspectMockのクリーンアップ処理を追加します。

4. ユニットテストコード作成

ユニットクラス用のテストクラスを作成します。
以下のコマンドの場合は、Sampleクラス用のテストクラスを作成します。

$ vendor/bin/codecept generate:test unit Sample

tests/unit/SampleTest.phpが作成されます。

ユニットテスト

Sampleクラスに対してAspectMockを使ったユニットテストについて説明します。

テスト対象クラス

テスト対象クラスは以下のSampleクラスになります。

getFullnameメソッド内でQueryクラス(DBアクセスクラスだと思ってください)がnewされているので、そのままテストしようとするとDBアクセスしてしまいます。

テストクラス

Sampleクラス用のテストクラスは以下のようになります。

AspectMockの利用

AspectMockを利用することで、テストダブルを簡単に利用できます。
以下のような記載を追加することで、Queryクラスのfindメソッドの戻り値を固定できます。

$user = ['lastname' => '苗字', 'firstname' => '名前'];
test::double('Query', ['find' => $user]);

QueryクラスがDBアクセスクラスの場合などは、DB依存を解消した上でテストを作成することができます。

テスト実行

以下のコマンド実行でユニットテストが実行できます。

$ ./vendor/bin/codecept run unit
Codeception PHP Testing Framework v2.0.9
Powered by PHPUnit 4.4.4 by Sebastian Bergmann.

Unit Tests (1) ------------------------------------------------------------------------------
Trying to test get fullname (SampleTest::testGetFullname)                               Ok
---------------------------------------------------------------------------------------------


Time: 608 ms, Memory: 13.50Mb

OK (1 test, 1 assertion)

レガシーコードと戦える

AspectMockを使用することで、メソッド内でnewしているようなオブジェクトに対しても簡単にモックを適用できるようになります。
テストしづらいレガシーコードと戦う方法としても使えるかと思います。

まあそもそもテストしづらいコードを書かないのがベストなんですけどね。。

今回説明用に使ったコードはGithubに上げてるので試してみたい方はご自由に。
それでは良いテスト自動化を。