単体テストガイドライン
원문출처 | http://svn.ec-cube.net/open_trac/wiki/EC...9%E3%82%8B |
---|
本ガイドラインはEC-CUBEの単体テストをPHPUnitを使って行う上でのガイドラインを
株式会社SHIFT様(http://www.shiftinc.jp/)のご協力によりまとめたものとなります。
各クラス共通のガイドライン
1. テストを含めたフォルダ構成
テストコードを含んだフォルダ構成は以下のようになります。
tests以下には、テストコード本体の他にテスト用のユーティリティや設定ファイル等が含まれます。
build.xml | テストやインスペクションを行うための設定ファイルです。 |
tests | |
├phpunit.xml | PHPUnitで使う各種設定を記載したファイルです。 SVN上にはphpunit.xml.baseというファイルがありますが、ローカルではこれをコピーしてphpunit.xmlを作成してください。 |
├ruleset.xml | PHP_CodeSniffer(インスペクションツール)用の設定ファイルです。 |
├require.php | テストに必要なファイルをインクルードするためのクラスです。 SVN上にはrequire.php.baseというファイルがありますが、ローカルではこれをコピーしてrequire.phpを作成してください。 |
└class | テスト用のクラスを格納するディレクトリです。 |
├Common_TestCase.php | 他のテストクラスの基底となるクラスです。 |
├replace | テスト用に実装を入れ替えているクラスを格納するディレクトリです。 |
└test/util | テスト用のユーティリティを格納するディレクトリです。 |
テストコードはそれぞれ対応するソースコードと同じ階層に保存します。
2. テストの実行方法
2.1. 実行の準備(初回のみ)
単体テストを実行するためには、ローカルの環境にPHPUnitをインストールしておく必要があります。
また、インクルードパス等をローカルの環境に合わせて書き換えるため、SVNに含まれているファイルをコピーしてローカル用の設定ファイルを作成する必要があります。
手順は下記の通りです。
- tests/phpunit.xml.baseをコピーしてtests/phpunit.xmlを作成します。
- <filter><blacklist>タグ以下の「/usr/local/lib」の部分を、ローカルで使われる各種ライブラリが含まれているパスと置換します。
- この設定はどのファイルをテストのカバレッジ測定の対象にするかを設定するためのものなので、設定をし直さなくても単体テスト自体は問題なく動作します。
- tests/require.php.baseをコピーしてtests/require.phpを作成します。
- PHPUnitのモジュールが使用できるように、インクルードパスを設定します。
tests/phpunit.xml、tests/require.phpはsvn:ignoreに設定されているため、自由に書き換えてもコミットはされません。
2.2. 実行
全体のテストを行う場合には、phingのtestターゲットを実行します。
テストの内容はbuild.xmlの中に定義されているため、実際にはphpunitコマンドが発行されます。
% phing test
テストが完了すると、結果がreportsディレクトリ以下に出力されます。
- reports/tap.log TAP形式のテスト結果
- reports/unitreport.xml xUnit形式のテスト結果
- reports/coverage/coverage.xml カバレッジ測定結果のXML(主にJenkinsで処理するためのものなので気にしなくて良いです)
- reports/coverage/.html カバレッジ測定結果のHTML
個々のテストを行う場合には、テスト対象のディレクトリを指定してphpunitコマンドを実行します。
標準出力ですぐにテスト結果を確認したい場合にはこちらのやり方のほうが良いでしょう。
% phpunit –c tests/phpunit.xml tests/class/pages/LC_Page/LC_Page_InitTest.php % phpunit –c tests/phpunit.xml tests/class/pages
下の例のようにディレクトリを指定した場合には、ディレクトリ以下にあるテストケースが実行されます。
また、--colorsオプションを付けると、結果が色付きで表示され見やすくなります。
% phpunit --colors –c tests/phpunit.xml tests/class/pages/LC_Page/LC_Page_InitTest.php
カバレッジを測定したい場合には、専用のオプションを指定します。
% phpunit -c tests/phpunit.xml --coverage-html reports/coverage tests/class/pages/LC_Page/LC_Page_InitTest.php
3. テストクラスの構成
テストクラスは、基本的にtests/class/Common_TestCase.phpを継承して作成します。
Common_TestCaseの中には、次節で述べるAssertionを一度に行うverify()関数や
テストの開始時と終了時にDBの準備・後片付けを行うsetUp()/tearDown()関数が含まれています。
個々のテストクラスでは、Common_TestCaseのsetUp()/tearDown()の処理に必要な処理を追加して使います。
また、テストに使用するユーティリティクラスもCommon_TestCaseでまとめてrequireします。
<?php SampleTest extends Common_TestCase { protected function setUp() { parent::setUp(); // 個々のテストケースで必要な処理 } protected function tearDown() { // 個々のテストケースで必要な処理 parent::tearDown(); } public function testFunctionName_❍❍の場合_△△になる() { $this->expected = array('hoge', 'fuga'); $this->actual = array(); $this->actual[0] = functionName('a'); $this->actual[1] = functionName('b'); $this->verify(); } }
4. Assertion(期待値の確認)の方法
PHPUnitにはassertEquals()、assertTrue()など様々な期待値の確認用funcitonが存在します。
これらを細かく使用してテストの期待値を確認することもできますが、複数のasseritionを並べると
最初の方で失敗した場合に後のassertionが実行されず、全体の修正までに時間がかかってしまう場合があります。
これを防ぐため、基本的に期待値と実際の結果はarrayに格納して一度でassertできるようにします。
もちろん、それぞれの値が1つずつの場合はarrayに入れなくても構いません。
<?php protected function verify($msg = null) { $this->assertEquals($this->expected, $this->actual, $msg); } public function testHoge() { $this->expected = array(1, "山田", "太郎"); // テスト対象を実行して$actualに結果を格納 $this->verify(); }
5. テストfunctionの分け方
原則として、1つのテストfunctionで1つの条件をテストします。
- 良い例
<?php function testAbs_正の値の場合() { $expected = 1; // テスト対象functionの呼び出し $actual = abs(1); $this->verify(); } function testAbs_負の値の場合() { $expected = 2; // テスト対象funcitonの呼び出し $actual = abs(-2); $this->verify(); }
- 悪い例
<?php function testAbs() { $expected[0] = 1; $actual[0] = abs(1); $expected[1] = 2; $actual[1] = -2; $this->verify(); }
「悪い例」の書き方の場合、ケースの中身を確認しないと
・何種類のテストを行っているのかが把握できない
・どんな観点でテストを行っているのかが把握できない
といった問題点があります。
1funciton1条件の前提を守った上で、なるべく条件分岐を網羅するようにテストを作成していきます。
6. テストfunctionの命名規則
テストfunctionの名称は、テストの内容を分かりやすくするため
test【function名】_【条件】_【期待する結果】()
という形式で統一します。命名規則を統一することで可読性が上がり、
テストコードを書いた本人でなくてもJenkinsのテストレポートを見るとどのようなテストが行われているかが一目でわかります。
また、条件・期待する結果は日本語で記載することでさらに分かりやすくすることができます。
- 例
testAction_必須項目が入力されていない場合_エラー画面に遷移する()
7. テストクラスの分け方
テストクラスはテスト対象のfunction毎に1つずつ分けて作成します。
ただし、後述する「定数による条件分岐」をテストする場合には条件毎にクラスを分ける必要があるためさらに細分化されます。
8.テストクラスの命名規則
上で述べたとおり基本的にテストクラスはテスト対象のfunctionに対応するため、
【対象クラス】_【対象function】Test.php
という名称にします。さらに条件毎にファイルを分ける場合には、
【対象クラス】_【対象function】_【条件】Test.php
とします。
- 例
LC_Page_Products_Detail_ActionTest.php LC_Page_Products_Detail_Action_HasErrorTest.php
条件によってファイル名を分ける場合には、ファイル名は日本語を避けて定義するようにしてください。
9. より網羅的にテストを書く方法
1. 定数による条件分岐
defineを使って定義されている定数は、テスト中に自由に上書きすることができません。
そこで、定数の値によって条件が分岐する場合は定数の値ごとにテストコードのファイルを分割します。
- ソースコード
<?php function sfGetHashString($str, $salt) { $res = ''; if ($salt == '') { $salt = AUTH_MAGIC; } if (AUTH_TYPE == 'PLAIN') { $res = $str; } else { $res = hash_hmac(PASSWORD_HASH_ALGOS, $str . ':' . AUTH_MAGIC, $salt); } return $res; }
- テストコード
<?php $HOME = realpath(dirname(__FILE__)) . "/../../../.."; // このテスト専用の定数の設定。必ずCommon_TestCase.phpより先に定義する define('AUTH_TYPE', 'PLAIN'); require_once($HOME, "/tests/class/Common_TestCase.php"); // クラス名に条件(authTypePlain)も含める class SC_Utils_sfGetHasString_authTypePlainTest extends Common_TestCase { // AUTH_TYPE=PLAINであることを想定した実際のテストコード }
このようにCommon_TestCaseより先に指定したい定数を定義することで、このテストクラスに限定した値を定義することができます。
2. exitする箇所のテスト
PHPUnitでテストを行う際には実際にphpのプログラムを走らせることになりますが、
プログラム中にexitする箇所があるとそこでPHPUnit自体も終了してしまうため、有効なテスト結果を得ることができません。
EC-CUBEの場合は、pages以下のクラスでSC_Response_Ex::sendRedirect()やSC_Response_Ex::actionExit()を呼んでいる箇所がそれにあたります。
このような部分をきちんとテストするために、テスト実施時はSC_Response_Exの実装を切り替えてexitしないようにします。
実装を切り替えた後のクラスはtests/class/replace以下に存在します。
このクラスをCommon_TestCaseから呼び出すことにより、テスト時の実装を切り替えます。
<?php /** * actionExit()呼び出しを書き換えてexit()させない例です。 */ public function testExit() { $resp = new SC_Response_Ex(); $resp->actionExit(); $this->expected = TRUE; $this->actual = $resp->isExited(); // exit()したかどうかをチェックします。 $this->verify('exitしたかどうか'); }
10. テストを書きやすくする対策
1. functionを「単体」でテストする
functionの中でさらにfunctionが呼ばれている場合、いちばん外側のfunctionをそのまま実行すると中の分岐が多すぎてテストしきれない場合があります。
そのような場合は、内側のfunctionの実装をモックに切り替えて欲しい値を自由に返すようにし、外側のfunctionだけをテストするようにします。
- ソースコード
<?php class Sample { function hoge() { if (fuga()) { return 1; } else { return 2; } } function fuga() { return rand() % 2 == 0; } }
- テストコード
<?php class SampleTest extends PHPUnit_Framcework_TestCase { function testHoge_fugaがtrueの場合() { $sample = new Sample_Mock(); $sample->fuga_val = TRUE; $this->assertEquals(1, $sample->hoge()); } } class Sample_Mock extends Sample { $fuga_val; function fuga() { return $fuga_val; } }
この例はそれほど分岐が複雑ではありませんが、内側の関数fuga()の返り値がランダムなのでテストで要求する値を返却させるためにオーバーライドしています。
2. ユーティリティクラスを使ってデータ準備を効率化する
テストで常に同じ結果を得られるようにするには、テスト実行のたびにDBの内容を期待値に合わせてリセットする必要があります。
そのため、EC-CUBEのユーティリティであるSC_Queryクラスを使ってsetUp()の中でデータの準備を行い、tearDown()でロールバックを行います。
<?php class SampleTest extends PHPUnit_Framework_TestCase { // データ準備 protected function setUp() { $this->objQuery = SC_Query_Ex::getSingletonInstance(); $this->objQuery->begin(); $this->setUpCustomer(); // 実際にデータを投入する箇所 } // ロールバック protected function tearDown() { $this->objQuery->rollback(); } // データの定義 protected function setUpCustomer() { $arrValue['customer_id'] = $this->customer_id; $arrValue['name01'] = $this->name01; $arrValue['name02'] = $this->name02; $arrValue['kana01'] = $this->name01; $arrValue['email'] = 'test@example.com'; $arrValue['secret_key'] = 'aaaaaa'; $arrValue['status'] = 2; // 会員 $arrValue['create_date'] = 'CURRENT_TIMESTAMP'; $arrValue['update_date'] = 'CURRENT_TIMESTAMP'; $this->objQuery->insert('dtb_customer', $arrValue); } }
よく使うデータ定義はtests/class/util以下のユーティリティクラスから取得できるようにしておき、データの再利用性を高めます。
3. ユーティリティクラスを使って端末の種類を設定する
端末の種類がPC・モバイル・スマートフォンのいずれになっているかによって条件が分岐する場合は、
テスト専用のユーティリティを使用して擬似的に端末の種別を設定します。ユーティリティはtests/class/test/util/User_Utils.phpに定義されています。
<?php /** * 端末種別をテストケースから自由に設定する例です。 */ public function testDeviceType() { $this->expected = array(DEVICE_TYPE_MOBILE, DEVICE_TYPE_SMARTPHONE); $this->actual = array(); // 端末種別を設定 User_Utils::setDeviceType(DEVICE_TYPE_MOBILE); $this->actual[0] = SC_Display_Ex::detectDevice(); User_Utils::setDeviceType(DEVICE_TYPE_SMARTPHONE); $this->actual[1] = SC_Display_Ex::detectDevice(); $this->verify('端末種別'); }
4. ユーティリティクラスを使ってログイン状態を設定する
ユーザがログインしているかどうかによって処理が分岐する場合は、セッションの情報とDBの値を書き換えることによりテストで要求する分岐を実現します。
情報を書き換えるfunctionは、端末種別設定と同じくtests/class/test/util/User_Utils.php内で定義されています。
<?php /** * ログイン状態をテストケースから自由に切り替える例です。 */ public function testLoginState() { $this->expected = array(FALSE, TRUE); $this->actual = array(); $objCustomer = new SC_Customer_Ex(); // ログインしていない状態に設定 User_Utils::setLoginState(FALSE); $this->actual[0] = $objCustomer->isLoginSuccess(); // ログインしている状態に設定 User_Utils::setLoginState(TRUE, null, $this->objQuery); $this->actual[1] = $objCustomer->isLoginSuccess(); $this->verify('ログイン状態'); }
-
클래스의 사용
클래스의 사용
인스턴스의 생성 클래스가 선언되고 나면, 선언된 클래스로부터 인스턴스를 생성할 수 있습니다. PHP에서는 new 키워드를 사용하여 인스턴스를 생성할 수 있습니다. 이때 클래스 이름을 통해 생성자로 필요한 인수를 전달할 수 있습니다. 문법 $객체이름 = new...Date2019.10.03 Category클래스 Views93 -
클래스의 구조
클래스의 구조
클래스의 구조 PHP에서 클래스는 class 키워드를 사용하여 다음과 같이 선언합니다. 문법 class 클래스이름 { 클래스의 프로퍼티과 메소드의 정의; } PHP에서 클래스의 이름을 생성할 때는 반드시 다음 규칙을 지켜야만 합니다. 1. 클래스의 이름은 숫자와의 ...Date2019.10.03 Category클래스 Views35 -
클래스와 객체의 기초
クラス(class)とオブジェクト(object) オブジェクト(object)とは、実生活では、我々が認識することができるもので理解することができます。 これらのオブジェクトの状態(state)と行動(behavior)は、それぞれのプロパティ(property)とメソッド(met...Date2019.10.03 Category클래스 Views56 -
EC-CUBE:SC_FormParamクラスによるパラメーターチェック方法
EC-CUBE:SC_FormParamクラスによるパラメーターチェック方法
EC-CUBE에 대한 인터넷상에서 아직 정보가 많이 부족합니다. 예를 들어 플러그인 작성에 원래 폼을 만들어도 SC_FormParam 클래스 를 사용하여 어떻게 확인하면 좋은 것인지 조사해도 좀처럼 나오지 않습니다. 그래서 내가 독자적으로 조사한 속에서 조금씩 정...Date2019.10.03 CategoryEC-CUBE 2.x Views119 -
EC-CUBE2.12 運用マニュアル
EC-CUBE ver2120 2121 2122 運用マニュアル1 Page EC-CUBE Ver2120 Ver2121 Ver2122 運用マニュアル 2012年11月28日 Thi s document is licensed under a Creative Commons 表示 - 継承 2 1 日本 License THEVOS ECO LAB Inc EC-CUBE ver2120 2121 212...Date2019.09.29 CategoryEC-CUBE 운용 설명서 Views62 -
単体テストガイドライン
単体テストガイドライン
本ガイドラインはEC-CUBEの単体テストをPHPUnitを使って行う上でのガイドラインを株式会社SHIFT様(http://www.shiftinc.jp/)のご協力によりまとめたものとなります。 各クラス共通のガイドライン1. テストを含めたフォルダ構成テストコードを含んだフォルダ構...Date2019.09.28 CategoryEC-CUBE 2系 Views86 -
リファクタリングガイドライン
リファクタリングガイドライン
init 関数init 関数は, クラスの初期化を目的する. ビジネスロジックの記述はしないこと <?php function init() { /** * NG ビジネスロジックの記述はしない */ if (count($this->arrPayment) > 0) { $i = 0; foreach ($this->arrPayment as $val) { $this->pa...Date2019.09.28 CategoryEC-CUBE 2系 Views153 -
EC-CUBE標準規約
EC-CUBE標準規約
基本的に Zend Framework PHP 標準コーディング規約 に順ずる.以下, 要点及び相違点を規定する. また, コーディングに際して, 以下のガイドラインに沿うことが望ましい EC-CUBE標準規約 > リファクタリングガイドライン PHPUnitを利用した単体テストを行う際は...Date2019.09.28 CategoryEC-CUBE 2系 Views154 -
デフォルトのtitleを任意のものに変更する方法
EC-CUBE 3.Xでデフォルトでtitle部分に表示されるものを任意で削除・変更したり、あらかじめ用意されているページのページ名(テンプレート名)を任意のものに変更する方法です。 ※紹介している内容はEC-CUBEのVersion 3.0.10で動作確認したもので、デフォルト...Date2019.04.10 CategoryEC-CUBE 3.x Views228 -
titleの並びや区切り記号を変更する方法
EC-CUBE 3.Xは、デフォルトのテンプレートだとtitle表示が「ショップ名 / ページ名」という形になっていますが、それを任意の並びにしたり区切り記号を変更したりする方法です。 ※紹介している内容はEC-CUBEのVersion 3.0.10で動作確認したもので、デフォルト...Date2019.04.10 CategoryEC-CUBE 3.x Views97 -
商品数や階層に関係なく全カテゴリーを表示させる方法
EC-CUBEの備忘録。デフォルトのテンプレートだとサイドに表示されている商品カテゴリーですが、商品登録がない場合はカテゴリー名が表示されず、商品が登録されていたとしても親カテゴリーのページでなければ子カテゴリー名は表示されないようになっています。...Date2019.04.10 CategoryEC-CUBE 2.x Views260 -
商品名などで長くなったテキストを省略する方法
引き続きEC-CUBEの備忘録です。商品のタイトルや紹介文が指定した文字数以上になったときに「...」などを表示して、見栄えを揃える方法です。jQueryを使うとかCSSのtext-overflowなんかでも同じ事はできますが、Smartyを使って実装するものになります。 ※EC-CU...Date2019.04.10 CategoryEC-CUBE 2.x Views45 -
サイトデザインをPCで統一させる方法
EC-CUBEはもともとスマートフォンやモバイル用のテンプレートが用意されており、それぞれのデバイスで閲覧すると各テンプレートに振り分けらるようになっているのですが、それを無効にする方法です。この機能自体は良いと思うのですが、例えばモバイルまで手が...Date2019.04.10 CategoryEC-CUBE 2.x Views53 -
商品ステータスを変更・追加する方法
EC-CUBE自体をまだあまり触ったこともなければ、そんなに頻繁に使わないのもあって、しばらくすると簡単なことでもいろいろと忘れてしまうので備忘録。デフォルトだと「NEW」とか「残りわずか」などで登録されている商品ステータスを任意のものに変更・追加す...Date2019.04.10 CategoryEC-CUBE 2.x Views62 -
新規作成したページURLから「user_data」を消す方法とURLを出力するテンプレートタグ
EC-CUBE 3.Xではもともと用意されているページを利用するだけでなくオリジナルで新規ページを追加することもできるのですが、その場合作成したページのURLに「user_data」が付与されています。この「user_data」という部分をURLから消す方法とそれに関連して新...Date2019.04.10 CategoryEC-CUBE 3.x Views868 -
메일 사용자 설명서
메일 사용자 설명서
원 포인트 가이드메일 소프트에 다음 정보를 설정하십시오. 메일 서버 (POP)sv11.star.ne.jp메일 서버 (SMTP)sv11.star.ne.jp계정 (사용자 이름)만든 계정 이름 (@이후도 포함)비밀번호설정 한 패스워드Date2019.03.29 Category메일 서비스 Views65 -
페이스북위젯 삽입하기
페이스북 위젯을 삽입하는 방법을 알아보겠습니다. 1. 페이스북 위젯을 설정하는 곳으로 갑니다. ( https://developers.facebook.com/docs/plugins/page-plugin ) 2. 원하는 위젯모양을 만듭니다. 밑의 이미지를 보시면 쉽게 이해가 되실꺼예요. 3. 설정이 끝...Date2018.12.15 Category팁 & 노하우 나눔 Views49 -
구글 캘린더 합치기
구글 캘린더로 일정관리를 하는 사람이라면 한번쯤 여러개로 나눠진 캘린더를 하나로 합쳐야 할 때가 생깁니다. 저같은 경우는 처음에 일정관리에 심취해서 캘린더 수를 아주 자세하게 여러개로 나눠 놨었죠. 그러나 최근에서야 단순함의 미학과 효율성에 눈을...Date2018.12.12 Category팁 & 노하우 나눔 Views427 -
THEVOS Youtube BOARD SKIN 사용 설명
THEVOS Youtube BOARD SKIN 사용 설명
[ 스킨 기능 ] - 사용자 정의 자동추가 게시판 스킨을 "INSP 유튜브"으로 변경하고 사용자정의 eid(dex_embed_srl)가 없을겨우 자동으로 추가됨 - 기존게시판에서도 사용가능 ㄴ 게시판에 필요한 사용자 정의는 "thevos_extv_youtube_code" 이다. ㄴ 하지만 sk...Date2018.11.11 Category메뉴얼 Views116
Facebook [ko]댓글