對查詢構造器進行調試並不難,從其構造 SQL -> 數據綁定 -> SQL 執行的過程當中就能發現,要方便調試,只要能夠觀察如下信息:php
PDO 提供了一個方便的 debug 方法 PDOStatement::debugDumpParams() 來打印 SQL 和綁定的數據。咱們就使用它來作 debug 的工做。html
在基類添加 _debug 屬性和 withDebug() 方法:git
protected $_debug = FALSE; ... public function withDebug() { $this->_debug = TRUE; // 方便鏈式調用,返回當前實例 return $this; }
修改 _execute() 方法:github
protected function _execute() { try { $this->_wrapPrepareSql(); $this->_pdoSt = $this->_pdo->prepare($this->_prepare_sql); $this->_bindParams(); $this->_pdoSt->execute(); $this->_reset(); // 若是是 debug 模式,則打印相關信息 if($this->_debug) { $this->_pdoSt->debugDumpParams(); // 打印 debug 信息 $this->_debug = FALSE; // debug 只在當此訪問有效,打印完就關閉 } } catch (PDOException $e) { // when time out, reconnect if($this->_isTimeout($e)) { $this->_closeConnection(); $this->_connect(); // retry try { $this->_wrapPrepareSql(); $this->_pdoSt = $this->_pdo->prepare($this->_prepare_sql); $this->_bindParams(); $this->_pdoSt->execute(); $this->_reset(); // 若是是 debug 模式,則打印相關信息 if($this->_debug) { $this->_pdoSt->debugDumpParams(); // 打印 debug 信息 $this->_debug = FALSE; // debug 只在當此訪問有效,打印完就關閉 } } catch (PDOException $e) { throw $e; } } else { throw $e; } } }
這樣,在任何一個語句構造過程當中使用 withDebug() 方法 (在 get()、row() 等取結果的方法調用以前),就能打印出 debug 的信息。web
注:由於我在常駐內存模式下使用,因此選擇直接打印到 stdout 中,這樣能夠直接在終端界面上調試。傳統 web 模式中可使用 Output Control 系列函數 來獲取 debug 信息。
從項目的角度看:sql
當項目的規模很小的時候,單元測試沒什麼用。可是若是是寫底層框架或者項目發展到必定的規模時,單元測試對於提升生產力有很明顯的貢獻。shell
從程序設計的角度上看:數據庫
單元測試可讓你更好的拆分程序爲最小單元,幫助你更好的解耦。服務器
單元測試的好處是給開發人員的,並非給機器的。composer
拿咱們編寫的查詢構造器爲例,where()、get() 等方法依賴了不少底層的方法,底層方法之間也有互相的調用。
狀況一:你要爲一個底層方法添加功能,改完後如何判斷是否會影響上層調用呢?把全部調用它的方法都調用一遍看結果嗎?不用,只需使用單元測試,肯定這個方法的輸入輸出、可能的運行狀況和邊界狀態,即保證最小單元可用。只要經過單元測試,則這個方法就沒有問題 (固然這裏的程序結構必須設計合理、測試必須準確有效)。狀況二:有一天,你想爲你的查詢構造器再支持一個新的數據庫,這個數據庫的驅動類繼承自基類。可是你不清楚基類的這些方法是否對新的數據庫還有效 (好比 postgresql 中 lastInsertId 的不一樣),要把全部方法跑一遍嗎?不用,你只需事先把這些通用方法寫好單元測試,把驅動類換做新數據庫的驅動類執行單元測試便可,跑一遍你就會發現有哪些方法是有問題的。
狀況三:和狀況一相似,當一個方法出 bug 時,你並不能立刻定位此 bug 出在此方法仍是此方法依賴的方法上。並且當你定位了 bug 並進行修復時,發現其它方法由於修復出現了新的 bug,又是一輪 bug 查找。使用單元測試後,每次有修改後,都跑一遍單元測試,能夠很快的發現這次修改對整個程序的影響,爲咱們節省不少時間。
固然,單元測試中還有 stub、mock 之類的模式能夠很好的解決依賴不肯定、難重現的問題,這裏不作重點,咱們就很少說了。
到如今以前,咱們都是使用 test/test.php 這個文件寫一些測試,這種簡單的方式雖然作一些簡單測試沒有問題,可是完成單元測試就要大費周章了。而 PHP 有著名的單元測試框架 PHPUnit,能很好的完成咱們進行測試的需求,因此單元測試這塊兒,咱們使用 PHPUnit。
安裝 PHPUnit
PHPUnit 的安裝很簡單,在項目中執行:
composer require "phpunit/phpunit" "~4.0" composer require "phpunit/dbunit" "~2.0"
注:咱們的測試須要鏈接數據庫,因此要安裝 dbunit
如今在項目目錄下的 test 文件夾中新建以 Test.php 結尾的測試文件,命令行運行 phpunit 便可運行測試。【1】
單元測試的代碼簡單、代碼量大,我就不在這裏展現了,全部的測試代碼見 WorkerF - tests - DB。
固然,對於這個單元測試,仍是要作一些說明的。
單元測試的結構:
項目目錄/ test/ PDODML.php PDODQL.php MysqlPDODMLTest.php MysqlPDODQLTest.php PgsqlPDODMLTest.php PgsqlPDODQLTest.php SqlitePDODMLTest.php SqlitePDODQLTest.php PDODriverTest.php test.xml testMysql.sql testPgsql.sql testSqlite.sql
PDODML.php 和 PDODQL.php 文件:
首先看 PDODML 和 PDODQL 類,包含了通用的 DQL 和 DML 方法的測試,經過原生 PDO 執行 SQL 得出的結果和查詢構造器構造執行得出的結果相比較。
MysqlPDODMLTest.php、MysqlPDODQLTest.php 等數據庫開頭測試文件:
MysqlPDODMLTest 繼承自 PDODML,MysqlPDODQLTest 繼承自 PDODQL,Pgsql 和 Sqlite 一樣道理。
MysqlPDODMLTest、MysqlPDODQLTest 這些測試類中使用 phpunit 的 setUpBeforeClass() 方法和 dbunit 的 getConnection() 方法等建立了一個全局可用的數據庫鏈接,方便測試時對數據庫的訪問。
test.xml:
test.xml 中寫好了 dbunit 要求的固定格式的模擬數據,用來測試時自動填充、恢復數據表 (由於 insert、update 等會更改數據表,這也是要用 dbunit 的緣由)。
PDODriverTest.php:
裏面包含了對基類全部方法的測試。這裏要說明一下,基類中有不少 protected 方法,個人測試方案是寫一個新的類,繼承自基類,而後新建 public 方法包裹要測試的 protected 方法,對新建的 public 方法進行測試,即達到了測試 protected 方法的目的。
這個文件中的測試更可能是測試各個方法構造的 SQL 字符串是否符合預期,使用的正則匹配斷言比較多。
sql 文件:
幾個數據庫測試表的建表 sql。
本地測試
想要在你的本地跑這些測試的話,打開 MysqlPDODMLTest.php、MysqlPDODQLTest.php 等數據庫開頭的文件,把數據庫配置中的 username、password、dbname 等改爲你本身的便可。
Travis CI 提供的是持續集成服務(Continuous Integration,簡稱 CI)。它綁定 Github 上面的項目,只要有新的代碼,就會自動抓取。而後,提供一個運行環境,執行測試,完成構建,還能部署到服務器。【2】
持續集成指的是隻要代碼有變動,就自動運行構建和測試,反饋運行結果。確保符合預期之後,再將新代碼"集成"到主幹。【2】
持續集成的好處在於,每次代碼的小幅變動,就能看到運行結果,從而不斷累積小的變動,而不是在開發週期結束時,一會兒合併一大塊代碼。【2】
若是你要將項目推到 Github 上,能夠接入 Travis CI。經過編寫 .travis.yml 配置文件,能夠實現遠程運行環境的語言多版本切換、軟件安裝、腳本執行等操做。
對於查詢構造器這個項目,咱們可讓其在遠程運行環境安裝相關數據庫軟件,執行數據表創建,數據導入,執行單元測試等操做。
個人框架項目 WorkerA 就集成了 Travis CI ,相關配置見 WorkerF - .travis.yml,感興趣的能夠了解下。
PHP 中對方法的註釋一是爲了提示,二是爲了生成文檔。我這裏的註釋寫法是標明功能、參數、返回值和拋出的異常。一個清晰好懂的註釋對於項目來講仍是很必要的。
例如:
/** * get paginate data * * @param int $step * @param int $page * @return array * @throws \PDOException */ public function paginate($step, $page = NULL) { ... }
一個查詢構造器的建立到此結束,但願對你們有用。若是發現文中的書寫和思路有錯誤,或者對此項目有什麼好的建議的話,歡迎提出。對文中的解釋有不解的地方,也歡迎提問。
查詢構造器的完整代碼:WorkerF - DB
查詢構造器的單元測試完整代碼:WorkerF - tests - DB。
【1】PHPUnit Doc