CakePHPのPHPUnitドキュメント
PHPUnitの公式サイト

目次

ページ内の目次
  1. PHPUnitとは
  2. CakePHPに同梱されているPHPUnitを動かしてみる
  3. テストクラスの作り方と実行方法
  4. PHP Unit でできるテストについて
  5. コントローラのテスト
    1. コントローラテストでPOSTデータを渡す方法
    2. コントローラテストでリダイレクトをテスト
    3. コントローラテストでセッションを渡す方法
  6. DBと結びついたモデルをテストする
    1. フィクスチャとは
    2. フィクスチャを使う前の準備
    3. フィクスチャの作成方法とテストクラスの書き方
    4. DB登録系のモデルのメソッドをテストする方法
    5. saveとsaveAllの違い、およびトランザクション有無によるテスト難易度
  7. テストスイートで まとめてテストを実行する
  8. テスト計測の指標 | コードカバレッジ
  9. 補足
    1. 通常画面にテスト項目画面を表示させる
    2. 「App::import('vendor', 'vendor/autoload');」について
    3. よく使うアサーション
    4. テストクラスの基本クラスの種類
  10. テストの原則
    1. テストで入力する値のパターン
    2. 境界値テスト
    3. コードカバレッジ | パターンの網羅
    4. 同値分割

PHPUnitとは

PHPUnitとはPHPのユニットテスト用のPHPライブラリであり、 PHPのクラス単位でテストを行う。

PHP Unitはどんなソースコードでもテストできるわけではない。
ある程度テストしやすい設計が求められている。
なるべくクラスは他のクラスと疎結合であるとテストしやすくなる。

PHP Unitを利用するからといって、テスト作業がすべて自動化されるわけではない。
PHP Unitも「テストの原則」則っている。
そのため、テストコードを書かねばならず、時間もかかる。

PHPUnitと手動テストは互いに得意、不得意があるので、 PHPUnitだけに頼るのでなく、場所によっては手動テストを行い、使い分けるのが良い。

PHP Unitのテストコードは複雑にならないようにしなければならない。
何をしているかすぐに分からないテストコードは、開発コストを高くする。

メリット

数値を少し変えて繰り返し行うようなテストに効果的である。
修正をしたあと、PHPUnitを実行して他の個所に影響がないか確かめることができる。
品質をある程度数値化するのに役に立つ。
プログラマーとっては手動テストよりも、やりがいがあるテスト手法である。

デメリット

PHPUnitではテストできない箇所がある。
テストコードを書かねばならない。
ソースコードの設計によっては、不十分なテスト結果しか出せない。

テストできないケース

どうしてもPHPUnitではテストできない箇所もある。
テストできない箇所

CakePHPに同梱されているPHPUnitを動かしてみる

PHPUnitはCakePHPに最初から同梱されている場合がある。
この場合、ConmoserやPEARを使用してインストールする作業は不要になる。

テストの実行方法は以下のURLにアクセスするだけである。
http://localhost/hello_okinawa/test.php

以下の画面が表示された場合、PHPUnitは動作している。

「Missing Controller」などのエラーが表示された場合は、ComposerでPHPUnitをインストールする必要がある。

通常画面にテスト項目画面を表示させる

通常画面であるViewのctpファイルにテスト項目画面を出力することができる。

test.ctp
<? php
App::import('vendor', 'vendor/autoload'); // ※bootstrap.phpに記述している場合は不要。
require_once CAKE . 'TestSuite' . DS . 'CakeTestSuiteDispatcher.php';
CakeTestSuiteDispatcher::run();
?>
		

「App::import('vendor', 'vendor/autoload');」について


App::import('vendor', 'vendor/autoload');
このコードは、Composerでインストールしたすべてのライブラリを使用可能にする。
通常は「app/Config/bootstrap.php」に記述する。
このコードは1回だけ呼び出せばよく、bootstrap.phpに記述しておけば各所で記述する必要がなくなる。

よく使うアサーション

よく使うと思われるアサーション関数を列挙する。
アサーション関数判定方法
assertNull($variable)$variable が null である
assertEquals($expected,$actual)$expected と $actual が等しい。
assertSame(($expected,$actual)$expected と $actual は型も含めて等しい。
assertTrue($condition)$condition は true である
assertFalse($condition)$condition は false である

参考リンク

公式ドキュメント:付録Aアサーション
Qiita PHPUnitの主なAssertメソッド一覧

テストクラスの基本クラスの種類

基本クラス説明
PHPUnit_Framework_TestCase PHPUnitの基本テストクラス。通常、一般的なテストクラスは、このクラスを継承する。
CakeTestCase CakePHPに特化した基本テストクラス。
PHPUnit_Framework_TestCaseを継承して拡張している。
PHPUnitではなくCakePHPが用意している基本テストクラスである。
ControllerTestCase CakePHPのコントローラに特化した基本テストクラス。
CakePHPコントローラのテストクラスを作るとき、この基本クラスを継承する。
PHPUnit_Extensions_PerformanceTestCaseパフォーマンスが関わってくるテスト。指定時間に処理が終わっているかを確認できる。
PHPUnit_Extensions_OutputTestCase出力を確認できる。
PHPUnit_Extensions_Database_TestCaseデータベースまわり用。

テストクラスの作り方と実行方法

PHP Unitではクラス1つに対してテストクラスを1つ作成し、テストクラスにテストコードを記述する。
テストコードはテスト対象クラスのpublicメソッドごとに記述する。
CakePHPのモデルクラスであるFish.phpを例に、テストクラスの作成、テストコードの書き方、テスト実行方法の手順を示する。

Fish.php
	<?php
	App::uses('Model', 'Model');
	
	class Fish extends Model {
	
		public $name='Fish';
		public $useTable = false;
		public $validate = null;
		
		public function getName1(){
			return 'グルクン';
		}
		
		public function getValue1(){
			return 999;
		}
	}
		

手順:Fish.phpのテストクラス作成、テストコード記述、テスト実行

  1. テストクラス「FishTest.php」を作成する。
    「hello_okinawa/app/Test/Case/Model」にFishTest.phpファイルを作成する。
    テストファイル名はテスト対象クラス名にTestの文字を付け加える必要がある。
    「クラス名 + Test.php」
  2. FishTest.phpに以下のテストコードを記述する。
    	<?php
    	App::uses('Fish', 'Model');
    	
    	class FishTest extends CakeTestCase  {
    	
    	    // テストの準備
    	    public function setUp() {
    	        $this->Fish = new Fish;
    	        parent::setUp();
    	    }
    	    
    	    // テスト終了後の片づけ
    	    public function tearDown() {
    	        unset($this->Fish);
    	        parent::tearDown();
    	    }
    	
    	    public function testGetName1() {
    	    	
    	        $res = $this->Fish->getName1();
    	        $this->assertEquals('グルクン', $res);
    	    }
    	
    	    public function testGetValue1() {
    	    	
    	        $res = $this->Fish->getValue1();
    	        $this->assertEquals(999, $res);
    	    }
    	}				
    				
  3. これでテスト準備は終わりましたので、テスト実行画面を開く。
    テスト実行画面には以下のURLからアクセスできる。
    http://localhost/hello_okinawa/test.php
  4. 「Model/Fish」をクリックしてテストを実行する。
    エラーがなければ緑色のバーが表示され、 エラーであれば赤色のバーが表示される。

PHP Unit でできるテストについて

CakePHPのコントローラをテスト

PHP Unitはコントローラをクラス単位でテストできる。
コントローラはHTTPリクエストやセッションと多くの関わりを持つ。
そのためテストコードはコントローラならではの書き方が存在する。

CakePHPのモデルをテスト

PHP Unitはモデルをクラス単位でテストできる。
モデルのメソッドはたいていの場合、データベースとの結びついている。
そのためテストコードは、データベースまわりならではの書き方がある。

CakePHPのビューをテスト

レイアウト崩れはPHP Unitでテストできない。
指定の文字が表示されているかはテスト可能である。
また、オリジナルヘルパーのテストも可能である。

ブラウザのリロードによる二重登録テスト

入力画面や編集画面からの登録結果画面では二重登録のバグが出てくるかもしれません。
登録結果画面※を表示したときにF5キーを押してブラウザをリロードを行い、二重登録がされてないかテストする。
PHPUnitでテストする場合、少し工夫が必要である。

セッション切れのテスト

セッションに入っているデータにより、振る舞いの異なる画面では、セッションに関するテストが必要である。
PHP Unitでセッションまわりのテストを行う場合、少し工夫が必要である。

様々なブラウザでテスト

Chrome,Firefox,Opera,Internet Explorer,SafariなどでレイアウトやJavascriptまわりを中心にテストする。
ブラウザ間の違いを厳密になくそうとするのは困難であるので、ある程度の妥協は必要である。
PHPUnitはサーバーサイドであるPHPのテストなので、このテストは対象外である。

セキュリティのテスト

クレジット情報など個人情報が漏れる可能性がないか、よく調査する。
漏らされる可能がある問題に、XSS(クロスサイトスクリプティング)、DBインジェクションなどがある。
非常に重要な部分ということもあり、PHPUnitだけでなく手動テストでの確認も必要である。

認証機能のテスト

認証を必要とするページに、認証せずにページにアクセスできるかテストする。
PHP Unitでもできないことはないだが、準備が大変である。
なので、手動でテストした方がよいと思われる。

レイアウトまわりのテスト

レイアウトの崩れがないかテストをする。
PHP Unitでは全く対応できないテストであるので、手動テストで確認する。

負荷テスト

大量アクセスにどこまで耐えられるかテストする。
PHP Unitでは非対応である。
WEBツールに専用のテストツールが存在するので利用できる。

マルチスレッド、並列処理、排他制御のテスト

複数のスレッドによる同時アクセステストである。
データベースの同時更新や、ファイル同時書き出しなどを中心にテストする。
PHP Unitでは非対応である。
WEBツールに専用のテストツールが存在するので利用できる。

コントローラのテスト

PHPUnitでCakePHPのコントローラをテストすることができる。
基本的なテストの方法は、testAction関数でページを呼出し、そのページのテキストに指定の文字列が含まれているかテストする。

注意

コントローラでは、末尾に「$this->render();」付け加える必要がある。
これがなくても最低限な動作確認はできますが、ページ内のテキストは取得できないため、詳細な検証はできなくなる。

テストコントローラの配置場所

「app/Test/Case/Controller」にテストコントローラを配置する。


テスト対象のコントローラ

	class FishController extends AppController {
		public $name = 'fish';
	
		function test1(){
	
			$this->render();
		}
	
	}
	

テストコントローラ

	class FishControllerTest extends ControllerTestCase {

		public function testTest() {
			
	 		$result=$this->testAction('/fish/test1');
			debug($result);//出力のみ
			$this->assertTextContains('Fish', $result);
	
		}
	
	}
	

testAction関数でテスト対象コントローラを動かす。
debug関数はコンソールとtest.phpの結果ページにページ内のテキストを出力する。出力のみで検証はしない。
assertTextContains関数はテキスト内に指定の文字列が含まれているかテストする。

test.php

「http://localhost/hello_okinawa/app/webroot/test.php」でテスト実行する。

参考リンク

コントローラテストでPOSTデータを渡す方法

コントローラをテストする際、テスト用のPOSTデータをセットすることができる。

テスト対象のコントローラ

	class FishController extends AppController {
		public $name = 'fish';
	
		function test2(){
			
			$key1='none';
			if(!empty($this->request->data['Fish'])){
				$data = $this->request->data['Fish'];
				$key1 = $data['key1'];
			}
			
			$this->set(array('key1'=>$key1));
			$this->render();
		}
	
	}
	

テストコントローラ

	class FishControllerTest extends ControllerTestCase {

		public function testTest2() {
			$data = array(
					'Fish' => array(
							'key1' => 'hello world',
					)
			);
			$result = $this->testAction(
					'/fish/test2',
					array('data' => $data, 'method' => 'post')
					);
			debug($result);
	
			$this->assertTextContains('hello world', $result);
		
		}
	
	}
	

postをgetと書き換えるだけで、getパラメータをセットすることができる。
array('data' => $data, 'method' => 'get')

公式ドキュメント

コントローラテストでリダイレクトをテスト

PHPUnitでリダイレクトが含まれるコードをテストすることは可能だが、コーディングの際にルールがある。
「$this->redirect」にはreturnを付けなければならない。returnがない場合、「$this->redirect」以降の処理まで実行してしまう。

return $this->redirect(array('action' => 'index'));

また、リダイレクト先のページ内容は取得できないが、リダイレクト先のURLは取得できる。
リダイレクト先が予想値と一致するかアサーションコードを記述する。

テスト対象のコントローラ

	class FishController extends AppController {
		function test3(){
			$this->redirect(array('action' => 'test3_r'));
		}
		
		function test3_r(){
		
		}
	}
	

テストコントローラ

	class FishControllerTest extends ControllerTestCase {
		$this->testAction('/fish/test3');
		$this->assertContains('/test3_r', $this->headers['Location']);// リダイレクト先ページが「test3_r」であるかテストする。
	}
	

公式ドキュメント

コントローラテストでセッションを渡す方法

PHPUnitはブラウザ主体でなくコンソール主体のテストである。 そのためセッションが使えない。
だが、ControllerTestCaseクラスにはモックという機能がある。 モックにセッションデータを仮セットすることによりテスト実行できる。

テスト対象のコントローラ

	class FishController extends AppController {
		function test4(){
			$msg = $this->Session->read('test4_fish');
			$this->set(array('msg'=>$msg));
			$this->render();
		}
	}
	

テストコントローラ

	class FishControllerTest extends ControllerTestCase {
		public function testTest4() {
	
			// モックをセット
			$this->controller = $this->generate('Fish', array(
					'components' => array(
							'Session',
					)
			));
	
			// モックにセッションデータを仮セット
			$this->controller->Session->expects($this->any())
				->method('read')
				->will($this->returnValueMap([['test4_fish', '勝連城']]));
			
			$result = $this->testAction('/fish/test4');
			debug($result);
			$this->assertTextContains('勝連城', $result);
		
		}
	}
	

公式ドキュメント
CakePHPのコントローラテストで注意すること

DBと結びついたモデルをテストする

CakePHPのモデルはほとんどの場合、データベースと結びついている。
データベースと結びついたモデルのメソッドは、SELECT文の参照系と、 INSERT,UPDATE,DELETEの更新系に分けられる。

参照系に関しては、特に難しいことはなく、テスト実測値とデータベースから取得したデータをアサーションで比較すればよい。

テストコードの例(参照系)

モデル
	App::uses('Model', 'Model');

	class Fish extends Model {
	
		public $name='Fish';
	
		public function getRec($id){
			
			$conditions=array(
					"id = {$id}",
			);
			
			$data=$this->find('all',array(
					'conditions'=>$conditions,
			));
			
			return $data;
		}
	}
	

テストコード
	App::uses('Fish', 'Model');
	
	class FishTest extends CakeTestCase  {
	
		public function testUpdateRec(){
			$this->Fish->begin();//トランザクション開始
			
			$res=$this->Fish->updateRec(2,"トビウオ");//テスト実施
			
			debug($res);
			$this->Fish->rollback();// ロールバック
	
			$this->assertEquals(2, $res['Fish']['id']);
			$this->assertEquals("トビウオ", $res['Fish']['fish_name']);
			
		}
	}
	

更新系のテストについて

更新系は、データベースを更新するため1回しかテストできないという事態に陥りやすい。
PHPUnitは何度もテスト実行するものなので、テスト後データを戻すという必要がある。(DB登録系のモデルのメソッドをテストする方法を参照)
しかしフィクスチャという仮データ機能を利用すれば、テスト後、データを戻す作業は不要になる。

フィクスチャとは

フィクスチャとは、DBの代わりに仮データを提供する機能である。
テストデータはDBにではなく、フィクスチャに記述する。
そのフィクスチャをテストクラスに組み込むと、DBから取得および更新する処理をテストする際、フィクスチャに記述されているデータを参照する。

フィクスチャのメリット

仮のテストデータでテストができる。
DB更新系処理をテストする場合、データを元に戻す必要がない。
DB側にテストデータを投入する手間が省ける。

フィクスチャのデメリット

MakeGoodプラグインではフィクスチャが使えない。
app/Config/database.php にある $test に別のデータベースを用意する必要がある。
フィクスチャを作成して仮データを用意するのは手間である。

フィクスチャを使うには

フィクスチャを使う前に、app/Config/database.php にある $test に別のデータベースを定義する必要がある。
$testに設定するデータベースは実際に利用しているデータベースとは別にしなければならない。
このデータベースはフィクスチャが内部的に利用しているようである。
詳しくは「フィクスチャを使う前の準備」を参考

準備ができたらフィクスチャを作成し、仮データを記述する。
そしてテストクラスは、作成したフィクスチャを組み込み設定する。
以上でテスト実行すると、DBデータを参照せず、フィクスチャのデータを参照するようになる。
詳しくは「フィクスチャの作成方法とテストクラスの書き方」を参考。

参考リンク

公式ドキュメント:フィクスチャ

フィクスチャを使う前の準備

フィクスチャを使う前に、テスト用のデータベースを登録する必要がある。
テスト用のデータベースはフィクスチャが内部で利用しているようなので、通常設定のデータベースとは別にデータベースを用意すること。
また、CakePHPはデバッグモードにする。

手順

  1. 「app/Config/core.php」を開き、デバッグモードにする。
    Configure::write('debug', 2);
    1または2にする。0はデバッグモードOFF。
  2. 実際のデータベースとは別にテスト用のデータベースを用意する。
  3. 「app/Config/database.php」を開く
  4. 「public $test ~」にテスト用データベースの設定を記述する。

    database.php
    class DATABASE_CONFIG {
    	public $default = array(
    			'datasource' => 'Database/Mysql',
    			'persistent' => false,
    			'host' => 'localhost',
    			'login' => 'root',
    			'password' => 'xxxxxx',
    			'database' => 'hello_okinawa',
    			'prefix' => '',
    			'encoding' => 'utf8',
    	);
    	
    	public $test = array(
    			'datasource' => 'Database/Mysql',
    			'persistent' => false,
    			'host' => 'localhost',
    			'login' => 'root',
    			'password' => 'xxxxxx',
    			'database' => 'hello_okinawa_test',
    			'prefix' => '',
    			'encoding' => 'utf8',
    	);
    	~ 省略 ~
    			
  5. 以上の準備でフィクスチャを作成できるようになる。
    フィクスチャの作成方法については 「フィクスチャの作成方法とテストクラスの書き方」を参考。

フィクスチャの作成方法とテストクラスの書き方

基本的な流れは、フィクスチャを作成してから、各テストファイルに使用するフィクスチャを設定するという流れである。

手順

  1. 「app/Test/Fixture」フォルダにフィクスチャをPHPファイルとして作成する。
    fishテーブルのフィクスチャを作成する場合、フィクスチャは「FishFixture.php」となる。
  2. フィクスチャに仮データ等を記述する。
    	class FishFixture extends CakeTestFixture {
    	
    		// 異なるテスト用データソースにフィクスチャを読み込む時にこのプロパティを指定してください。
    		public $useDbConfig = 'test';
    	
    		public $import = 'Fish';// DBのfishテーブルからフィールド情報をインポートする。
    	
    		// 仮データをセットする
    		public $records = array(
    				array(
    						'id' => 1,
    						'fish_name' => 'First Fish',
    						'fish_date' => '2007-03-01',
    						'value1' => '10',
    				),
    				array(
    						'id' => 2,
    						'fish_name' => 'kani',
    						'fish_date' => '2007-03-02',
    						'value1' => '20',
    				),
    				array(
    						'id' => 3,
    						'fish_name' => 'サメ',
    						'fish_date' => '2007-03-03',
    						'value1' => '30',
    				),
    				array(
    						'id' => 4,
    						'fish_name' => 'グルクン',
    						'fish_date' => '2007-03-04',
    						'value1' => '40',
    				),
    		);
    		
    		// 動的な仮データをセットしたい場合は、こちらのメソッドでセットする
    		public function init() {
    			$this->records[3]['fish_date'] = date('Y-m-d');
    	
    			parent::init();
    		}
    	}
    			
  3. 以上でフィクスチャーの作成は終了。続いてテストコードにフィクスチャを適用させる方法へ。
  4. テストコードの「$fixtures」メンバに、適用するフィクスチャをセットするだけである。
    	App::uses('Fish', 'Model');
    	class FishTest extends CakeTestCase  {
    	
    		public $fixtures = array('app.fish');// フィクスチャをセットする。
    	
    		public function setUp() {
    			//$this->Fish = new Fish;
    			parent::setUp();
    			$this->Fish = ClassRegistry::init('Fish');
    		}
    	
    		public function tearDown() {
    			unset($this->Fish);
    			parent::tearDown();
    		}
    		
    		// 更新系メソッドのテストコード
    		public function testUpdateRec(){
    			
    			$this->Fish->begin();//トランザクション開始
    			$res=$this->Fish->updateRec(2,"トビウオ");//テスト実施
    			debug($res);
    			$this->Fish->rollback();// ロールバック
    			$this->assertEquals(2, $res['Fish']['id']);
    			$this->assertEquals("トビウオ", $res['Fish']['fish_name']);
    	
    		}
    	}
    			
  5. 以上でテスト実行したとき、フィクスチャでセットした仮データからテストデータを取得するようになる。

公式ドキュメント:フィクスチャ

DB登録系のモデルのメソッドをテストする方法

PHPUnitは繰り返し何度もテストする仕様である。
そのため、1回しかテストできないというテストコードは避けるべきである。
DBまわりをテストする場合、1回の実施でDBにデータが登録されてしまい、2回目のテスト実施ができなくなるという事態になるかもしれない。
そのため、繰り返しテストができるよう、テストで変更したデータを元に戻す処理がテストコードに必要になる。
元に戻す処理はトランザクションのロールバックを使うのが一番簡単である。

モデルがトランザクションを使わず、即コミットするような仕様である場合、テストするのは難しくなる。
この場合、SQLのDELETEやUPDATEを駆使し、テストで変更したデータを元に戻さねばならない。
トランザクションも考慮しておらず、複雑な登録処理をしているモデルは、データを元に戻すだけでも非常に困難であり、危険も伴う。
なので、こういったモデルはPHPUnitでテストすべきでない。

更新系メソッドにはsave()とsaveAll()があるが、それぞれ動きは違うためテスト方法も異なる。
詳しくは「saveとsaveAllの違い、およびトランザクション有無によるテスト難易度」を参考。

以下にモデルのINSERT系処理とUPDATE系処理のテストコード記述例を示す。

モデルのINSERT系メソッドをテストする

モデル

	App::uses('Model', 'Model');
	class Fish extends Model {
		public $name='Fish';
		public function insertRec($fish_name){
			
			$rec=array(
					'fish_name' => $fish_name,
					'fish_date' => date('Y-m-d'),
					'value1' => 6,
			);
			
			$res=$this->save($rec, array('atomic' => false));
		
			return $res;
			
		}
	}
	

テストコード

	App::uses('Fish', 'Model');
	class FishTest extends CakeTestCase  {
	
	    // テストの準備
	    public function setUp() {
	        //$this->Fish = new Fish;
	        parent::setUp();
	        $this->Fish = ClassRegistry::init('Fish');
	    }
	    
	    // テスト終了後の片づけ
	    public function tearDown() {
	        unset($this->Fish);
	        parent::tearDown();
	    }
	    
	    public function testInsertRec(){
	    	$this->Fish->begin();//トランザクション開始
	    	$res=$this->Fish->insertRec("カツオ");//テスト実施
	    	debug($res);
	    	$this->Fish->rollback();// ロールバック
	    	$this->assertEquals("カツオ", $res['Fish']['fish_name']);
	    }
	}
	
DB更新系のテストはトランザクション内で行い、ロールバックで戻すのが基本である。

モデルのUPDATE系メソッドをテストする

モデル

	App::uses('Model', 'Model');
	class Fish extends Model {
	
		public $name='Fish';
	
		public function updateRec($id,$fish_name){
			$rec=array(
					'id' => $id,
					'fish_name' => $fish_name,
					'fish_date' => date('Y-m-d'),
					'value1' => 6,
			);
			$res=$this->save($rec, array('atomic' => false));
			return $res;
		}
	}
	

テストコード

	App::uses('Fish', 'Model');
	class FishTest extends CakeTestCase  {
		// テストの準備
		public function setUp() {
			//$this->Fish = new Fish;
			parent::setUp();
			$this->Fish = ClassRegistry::init('Fish');
		}
		
		// テスト終了後の片づけ
		public function tearDown() {
			unset($this->Fish);
			parent::tearDown();
		}
		
		public function testUpdateRec(){
			$this->Fish->begin();//トランザクション開始
			$res=$this->Fish->updateRec(2,"トビウオ");//テスト実施
			debug($res);
			$this->Fish->rollback();// ロールバック
			$this->assertEquals(2, $res['Fish']['id']);
			$this->assertEquals("トビウオ", $res['Fish']['fish_name']);
		}
	}
	
DB更新系のテストはトランザクション内で行い、ロールバックで戻すのが基本である。

saveとsaveAllの違い、およびトランザクション有無によるテスト難易度

テスト難易度save,saveAll トランザクションレスポンステストについて
save 有り
$res=$this->save($rec, array('atomic' => false));
$res=更新データの配列 1行更新用。更新したデータがレスポンスとして取得できる上、ロールバックもできるのでテストがとてもしやすい。
save 無し
$res=$this->save($rec);
$res=更新データの配列 1行更新用。更新したデータがレスポンスとして取得できるが、ロールバックはできない。テストコードでデータを戻すときはSQLのdeleteやupdateを使わなければならない。
saveAll 有り
$res=$this->saveAll($data, array('atomic' => false));
$res=true 複数行の更新用。レスポンスはtrueかfalseなので、更新したデータはSQLのselect文で取らねばならない。ロールバックはできるのでテストはしやすい。
× saveAll 無し
$res=$this->saveAll($data);
$res=true 複数行の更新の上、ロールバックが効かない。複数行のデータを元に戻すのは危険が伴う上、たいへんである。PHPUnitではテストしないほうがよい。

トランザクションが有効か無効かで、テストコードの書きやすさは全く違ってくる。
なのでsave,saveAllをソースコードに書くときは、「array('atomic' => false)」を指定し、トランザクションに対応させること。

ただし、フィクスチャを利用している場合、トランザクションに対応させてなくても、テスト後のデータを元に戻すのは容易である。
というよりデータを元に戻すことを考える必要はない。

テストスイートで まとめてテストを実行する

まとめてテストを実行するには、テストスイートを作成する。
テストスイートはtest.phpの画面から実行できる。

テストスイートの作成手順

  1. app/Test/Caseに「AllTestsTest.php」を作成する。
  2. AllTestsTest.phpに下記のソースコードを記述する。(そのままコピペで良い)
    	<?php
    	class AllTestsTest extends CakeTestSuite {
    	    public static function suite() {
    	        $suite = new CakeTestSuite('All tests');
    	        $suite->addTestDirectoryRecursive(TESTS . 'Case');
    	        return $suite;
    	    }
    	}
    			
  3. 以上でテストスイートは完成。つづいてテストスイートでまとめてテストを実行する方法へ。
  4. test.phpにアクセスする。

    http://localhost/hello_okinawa/app/webroot/test.php
  5. 「AllTests」リンクをクリックすれば、まとめてテストが実行される。

モデルのみ実行するテストスイート

AllModelTest.php
	<?php
	class AllModelTest extends CakeTestSuite {
		public static function suite() {
			$suite = new CakeTestSuite('All model tests');
			$suite->addTestDirectory(TESTS . 'Case/Model');
			return $suite;
		}
	}
	

参考リンク

公式ドキュメント:テストスイート

テスト計測の指標 | コードカバレッジ

PHPUnitにはコードカバレッジという、テストがどのくらいなされているか計測する指標がある。

コードカバレッジは、テストコードがテスト対象のソースコードをどのくらい網羅しているかを表す網羅率である。
つまり、コードカバレッジが高いほど、十分にテストがなされているということである。

コードカバレッジは0%~100%での数値であるため、品質を図る指標の一つとすることができる。

注意

コードカバレッジが100%でも、テストは不十分であるケースがある。
また、100%でなくとも十分テストができている場合がある。
なのでコードカバレッジは目安として認識する必要がある。
コードカバレッジは、だいたい80%くらいあればよいと言われている。

コードカバレッジを確認する手順

  1. テスト実行画面のtest.phpにアクセスする。
    http://localhost/hello_okinawa/test.php
  2. テスト対象を選びテストを実行する。
  3. 「Analyze Code Coverage」をクリックするとコードカバレッジを確認できる。


    FishController.php Code coverage: 48.72%


    例の場合、コードカバレッジは48.72%なのでテストは不十分といえる。


テストの原則

真のテストとは、テスト担当者の頭の中で、あらゆる予測を立てながら行うものであり、ツールの中だけで行うものではない。
また、完全なテストというものは存在せず、完全無欠な品質を作り出すことは不可能である。
「何をして、どうなったのか」の積み重ねにより、品質を高めることはできる。

PHPUnitは、テスト担当者の知識をもとに行うテストフレームワークである。
PHPUnitはテストに役立つ関数とフレームワークを提供しているだけである。
どのようにテストを行うかはテスト担当者に任されている。
とはいえ、テストにはある程度の共通するパターンがある。
詳しくは下記のテストパターンのヒントを参照。

テストパターンのヒント

テストで入力する値のパターン

文字列系のテスト入力パターン

  • NULLの時
  • 空文字 "" の時
  • 半角英数字の時
  • 日本語の時
  • 前後に半角スペースが付いている時
  • 半角スペースのみである場合
  • 改行コードが含まれる時
  • 長い文字列である時
  • XSSを引き起こす文字列の時
  • SQLインジェクションを引き起こす文字列の時
  • 記号の時
  • アスキーアートの時

数値系のテスト入力パターン

  • 数値でない時
  • NULLの時
  • 0の時
  • 境界値の時(2から5まで受け付ける処理なら、境界値である1,2,5,6でテスト)
  • 負値の時
  • 小数値の時
  • 範囲外の時(int型の場合、-2,147,483,648以下、2,147,483,647以上)
  • 浮動小数で8桁を超える時(float型の7桁制限)
  • bool型の時(true,false)
  • 文字列型数値の時

日付型のテスト入力パターン

  • 日付でない時
  • NULLの時
  • 2月29日の時(閏年)
  • 日時フォーマット(Y-m-d h:i:s)である時
  • 日付フォーマット(Y-m-d)である時
  • 時刻フォーマット(h:i:s)である時
  • 日付のセパレータが「/」である時
  • 存在しない日付である時(例:2016-4-31)
  • 1970/1/1 00:00:00 より以前の時(UNIXタイムスタンプの開始値まわり)
  • 2038-01-19 12:14:07を超える時(2038年問題)

bool系のテスト入力パターン

  • falseまたはtrueの時
  • 0または1の時
  • 0と1以外の数値の時
  • NULLの時

計算系のテストパターン

  • ゼロ除算が発生する入力値の時
  • 浮動小数で桁落ちする時(float型の場合、7桁を超える値が計算過程や結果に出現する場合)
  • int型の最小および最大を超える時(計算過程や結果に最大、最小を超える状態になる場合)

電話番号のテスト入力パターン

  • 固定電話の時
  • 携帯電話の時
  • PHSの時
  • IP電話の時
  • 国際電話の時

境界値テスト

境界値テストとは、入力条件として「20未満」のような範囲指定があるとき、20の境界値である19,20,21を入力するテストである。
境界値付近は、バグが集中しやすいため行われる。

数値や日付の範囲、上下、前後を判定する箇所でよく行われるテストである。
また同値分割法とセットで使われる。

境界値テストは境界値分析、限界値分析とも呼ばれる。

PHPUnitで利用できる関数

アサーション関数判定方法
assertGreaterThan($expected,$actual)$expected < $actual である
assertLessThan($expected,$actual)$expected > $actual
assertGreaterThanOrEqual($expected,$actual)$expected <= $actual である
assertLessThanOrEqual($expected,$actual)$expected >= $actual である

コードカバレッジ | パターンの網羅

コードカバレッジはパターンを分岐処理を網羅するテストである。
命令網羅、分岐網羅、条件網羅の3種類が存在しており、条件の複雑さによって使い分ける。

テストの精度は以下の比較の通りである。
命令網羅 < 分岐網羅 < 条件網羅
ただし、精度が増すほどテストケースは多くなり、テスト量も増える。

コードカバレッジには網羅率という概念が存在する。
全パターンでテストすれば、100%である。 あまりにも条件が多い処理をテストする場合、網羅率を下げることも必要になる。

命令網羅
別名:ステートメントカバレッジ
「平日、午前中は3割引き」という条件処理の場合、以下のパターンでのみテストする。
これだけで命令網羅率は100%になる。

定義

プログラム中の主な命令を必ず一度は実行すること。
if(...){
	~ 主な命令処理 ~
} else  {
	~ どうでもいい処理 ~
}
		
「主な命令処理」だけ一回、テスト実施すればよく、それ以外の分岐は省略できる。

どのようなときに行うテストか?

命令網羅は分岐網羅と似ているが、こちらは命令文に重きを置いている。
主な命令処理以外の分岐が重要でないのなら、命令網羅を採用できる。
例外処理(try ~ catch)に向いている。
テストケースは最も少なくて楽だが、精度は低い。

分岐網羅
別名:ブランチカバレッジ
「平日、午前中は3割引き」という条件処理の場合、以下のパターンを網羅したテストをする。
上記の2パターンを実施した場合、分岐網羅率100%となる。

定義

プログラム中の条件分岐について、分岐を必ず一度は実行すること
if(...){
	~ 分岐処理1 ~
} else if(...){
	~ 分岐処理2 ~
}
		
「分岐処理1」と「分岐処理2」を最低1回はテスト実施する。

どのようなときに行うテストか?

条件網羅と命令網羅の中間くらいのテストケース量と精度である。
分岐条件が比較的シンプルであるときに採用できる。
swich文のテストに向いている。

条件網羅
別名:コンディションカバレッジ
なるべく全パターンを網羅する。
「平日、午前中は3割引き」という条件処理の場合、以下の全パターンを網羅したテストをする。
平日/土日午前/午後割引/通常
平日である午前中である3割引価格
平日である午後以降である通常価格
土日である午前中である通常価格
土日である午後以降である通常価格
上記の4パターンをすべて実施した場合、条件網羅率100%となる。

定義

プログラムの条件分岐について、組み合わせをすべて実施する。
if( ~ 複雑な条件 ~){
	...
} else if ( ~ 複雑な条件 ~ )  {
	...
}
		
複雑な条件を分解し、一つ一つ、すべて網羅するようにテストする。

どのようなときに行うテストか?

AND条件、OR条件、大なり(>)、小なり(<)などの複雑な条件組み合わせなら、こちらを採用する。
テストの精度は高いが、テストケースの量は多くなりたいへんである。

同値分割

同値分割はテストケースを減らすための手法である。

出力が同値になる入力値をグループごとに分割する。
それぞれのグループから任意の入力値を代表として1つ選び、テスト実施する手法である。
通常、同値分割は境界値テストとセットで実施する。

「日付からキャンプ場料金を取得する」処理を例にした場合、全ての日付を網羅してテストする必要はない。
4パターンの代表日付だけテストすれば済む。

キャンプ場
月間料金代表の日付の例
7月~8月割増料金7月15日
9月~11月通常料金10月3日
12月~3月休園1月1日
4月~6月通常料金5月1日
「7月~8月」の料金情報を取得するテストを行う場合、7月~8月から任意の日付を一つ選び、 その日付で1回テストすればよい。