摘要:單元測試的好處是給開發(fā)人員的,并不是給機(jī)器的。對于查詢構(gòu)造器這個(gè)項(xiàng)目,我們可以讓其在遠(yuǎn)程運(yùn)行環(huán)境安裝相關(guān)數(shù)據(jù)庫軟件,執(zhí)行數(shù)據(jù)表建立,數(shù)據(jù)導(dǎo)入,執(zhí)行單元測試等操作。查詢構(gòu)造器的完整代碼查詢構(gòu)造器的單元測試完整代碼。
debug 模式
對查詢構(gòu)造器進(jìn)行調(diào)試并不難,從其構(gòu)造 SQL -> 數(shù)據(jù)綁定 -> SQL 執(zhí)行的過程中就能發(fā)現(xiàn),要方便調(diào)試,只要可以觀察以下信息:
構(gòu)造的 SQL
綁定的數(shù)據(jù)
PDO 提供了一個(gè)方便的 debug 方法 PDOStatement::debugDumpParams() 來打印 SQL 和綁定的數(shù)據(jù)。我們就使用它來做 debug 的工作。
在基類添加 _debug 屬性和 withDebug() 方法:
protected $_debug = FALSE; ... public function withDebug() { $this->_debug = TRUE; // 方便鏈?zhǔn)秸{(diào)用,返回當(dāng)前實(shí)例 return $this; }
修改 _execute() 方法:
protected function _execute() { try { $this->_wrapPrepareSql(); $this->_pdoSt = $this->_pdo->prepare($this->_prepare_sql); $this->_bindParams(); $this->_pdoSt->execute(); $this->_reset(); // 如果是 debug 模式,則打印相關(guān)信息 if($this->_debug) { $this->_pdoSt->debugDumpParams(); // 打印 debug 信息 $this->_debug = FALSE; // debug 只在當(dāng)此訪問有效,打印完就關(guān)閉 } } 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 模式,則打印相關(guān)信息 if($this->_debug) { $this->_pdoSt->debugDumpParams(); // 打印 debug 信息 $this->_debug = FALSE; // debug 只在當(dāng)此訪問有效,打印完就關(guān)閉 } } catch (PDOException $e) { throw $e; } } else { throw $e; } } }
這樣,在任何一個(gè)語句構(gòu)造過程中使用 withDebug() 方法 (在 get()、row() 等取結(jié)果的方法調(diào)用之前),就能打印出 debug 的信息。
注:因?yàn)槲以诔qv內(nèi)存模式下使用,所以選擇直接打印到 stdout 中,這樣可以直接在終端界面上調(diào)試。傳統(tǒng) web 模式中可以使用 Output Control 系列函數(shù) 來獲取 debug 信息。單元測試 單元測試的必要性
從項(xiàng)目的角度看:
當(dāng)項(xiàng)目的規(guī)模很小的時(shí)候,單元測試沒什么用。但是如果是寫底層框架或者項(xiàng)目發(fā)展到一定的規(guī)模時(shí),單元測試對于提高生產(chǎn)力有很明顯的貢獻(xiàn)。
從程序設(shè)計(jì)的角度上看:
單元測試可以讓你更好的拆分程序?yàn)樽钚卧瑤椭愀玫慕怦睢?/p>
單元測試的好處是給開發(fā)人員的,并不是給機(jī)器的。
拿我們編寫的查詢構(gòu)造器為例,where()、get() 等方法依賴了很多底層的方法,底層方法之間也有互相的調(diào)用。
情況一:你要為一個(gè)底層方法添加功能,改完后如何判斷是否會影響上層調(diào)用呢?把所有調(diào)用它的方法都調(diào)用一遍看結(jié)果嗎?不用,只需使用單元測試,確定這個(gè)方法的輸入輸出、可能的運(yùn)行情況和邊界狀態(tài),即保證最小單元可用。只要通過單元測試,則這個(gè)方法就沒有問題 (當(dāng)然這里的程序結(jié)構(gòu)必須設(shè)計(jì)合理、測試必須準(zhǔn)確有效)。情況二:有一天,你想為你的查詢構(gòu)造器再支持一個(gè)新的數(shù)據(jù)庫,這個(gè)數(shù)據(jù)庫的驅(qū)動類繼承自基類。但是你不清楚基類的這些方法是否對新的數(shù)據(jù)庫還有效 (比如 postgresql 中 lastInsertId 的不同),要把所有方法跑一遍嗎?不用,你只需事先把這些通用方法寫好單元測試,把驅(qū)動類換作新數(shù)據(jù)庫的驅(qū)動類執(zhí)行單元測試即可,跑一遍你就會發(fā)現(xiàn)有哪些方法是有問題的。
情況三:和情況一類似,當(dāng)一個(gè)方法出 bug 時(shí),你并不能馬上定位此 bug 出在此方法還是此方法依賴的方法上。而且當(dāng)你定位了 bug 并進(jìn)行修復(fù)時(shí),發(fā)現(xiàn)其它方法因?yàn)樾迯?fù)出現(xiàn)了新的 bug,又是一輪 bug 查找。使用單元測試后,每次有修改后,都跑一遍單元測試,可以很快的發(fā)現(xiàn)此次修改對整個(gè)程序的影響,為我們節(jié)省很多時(shí)間。
當(dāng)然,單元測試中還有 stub、mock 之類的模式可以很好的解決依賴不確定、難重現(xiàn)的問題,這里不做重點(diǎn),我們就不多說了。
使用 PHPUnit到現(xiàn)在之前,我們都是使用 test/test.php 這個(gè)文件寫一些測試,這種簡單的方式雖然做一些簡單測試沒有問題,但是完成單元測試就要大費(fèi)周章了。而 PHP 有著名的單元測試框架 PHPUnit,能很好的完成我們進(jìn)行測試的需求,所以單元測試這塊兒,我們使用 PHPUnit。
安裝 PHPUnit
PHPUnit 的安裝很簡單,在項(xiàng)目中執(zhí)行:
composer require "phpunit/phpunit" "~4.0" composer require "phpunit/dbunit" "~2.0"
注:我們的測試需要連接數(shù)據(jù)庫,所以要安裝 dbunit
現(xiàn)在在項(xiàng)目目錄下的 test 文件夾中新建以 Test.php 結(jié)尾的測試文件,命令行運(yùn)行 phpunit 即可運(yùn)行測試。【1】
單元測試的編寫單元測試的代碼簡單、代碼量大,我就不在這里展示了,所有的測試代碼見 WorkerF - tests - DB。
當(dāng)然,對于這個(gè)單元測試,還是要做一些說明的。
單元測試的結(jié)構(gòu):
項(xiàng)目目錄/ 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 執(zhí)行 SQL 得出的結(jié)果和查詢構(gòu)造器構(gòu)造執(zhí)行得出的結(jié)果相比較。
MysqlPDODMLTest.php、MysqlPDODQLTest.php 等數(shù)據(jù)庫開頭測試文件:
MysqlPDODMLTest 繼承自 PDODML,MysqlPDODQLTest 繼承自 PDODQL,Pgsql 和 Sqlite 同樣道理。
MysqlPDODMLTest、MysqlPDODQLTest 這些測試類中使用 phpunit 的 setUpBeforeClass() 方法和 dbunit 的 getConnection() 方法等創(chuàng)建了一個(gè)全局可用的數(shù)據(jù)庫連接,方便測試時(shí)對數(shù)據(jù)庫的訪問。
test.xml:
test.xml 中寫好了 dbunit 要求的固定格式的模擬數(shù)據(jù),用來測試時(shí)自動填充、恢復(fù)數(shù)據(jù)表 (因?yàn)?insert、update 等會更改數(shù)據(jù)表,這也是要用 dbunit 的原因)。
PDODriverTest.php:
里面包含了對基類所有方法的測試。這里要說明一下,基類中有很多 protected 方法,我的測試方案是寫一個(gè)新的類,繼承自基類,然后新建 public 方法包裹要測試的 protected 方法,對新建的 public 方法進(jìn)行測試,即達(dá)到了測試 protected 方法的目的。
這個(gè)文件中的測試更多是測試各個(gè)方法構(gòu)造的 SQL 字符串是否符合預(yù)期,使用的正則匹配斷言比較多。
sql 文件:
幾個(gè)數(shù)據(jù)庫測試表的建表 sql。
本地測試
想要在你的本地跑這些測試的話,打開 MysqlPDODMLTest.php、MysqlPDODQLTest.php 等數(shù)據(jù)庫開頭的文件,把數(shù)據(jù)庫配置中的 username、password、dbname 等改成你自己的即可。
集成測試 Travis CI 什么是 Travis CI?Travis CI 提供的是持續(xù)集成服務(wù)(Continuous Integration,簡稱 CI)。它綁定 Github 上面的項(xiàng)目,只要有新的代碼,就會自動抓取。然后,提供一個(gè)運(yùn)行環(huán)境,執(zhí)行測試,完成構(gòu)建,還能部署到服務(wù)器。【2】
持續(xù)集成指的是只要代碼有變更,就自動運(yùn)行構(gòu)建和測試,反饋運(yùn)行結(jié)果。確保符合預(yù)期以后,再將新代碼"集成"到主干。【2】
持續(xù)集成的好處在于,每次代碼的小幅變更,就能看到運(yùn)行結(jié)果,從而不斷累積小的變更,而不是在開發(fā)周期結(jié)束時(shí),一下子合并一大塊代碼。【2】
使用 Travis CI如果你要將項(xiàng)目推到 Github 上,可以接入 Travis CI。通過編寫 .travis.yml 配置文件,可以實(shí)現(xiàn)遠(yuǎn)程運(yùn)行環(huán)境的語言多版本切換、軟件安裝、腳本執(zhí)行等操作。
對于查詢構(gòu)造器這個(gè)項(xiàng)目,我們可以讓其在遠(yuǎn)程運(yùn)行環(huán)境安裝相關(guān)數(shù)據(jù)庫軟件,執(zhí)行數(shù)據(jù)表建立,數(shù)據(jù)導(dǎo)入,執(zhí)行單元測試等操作。
我的框架項(xiàng)目 WorkerA 就集成了 Travis CI ,相關(guān)配置見 WorkerF - .travis.yml,感興趣的可以了解下。
注釋PHP 中對方法的注釋一是為了提示,二是為了生成文檔。我這里的注釋寫法是標(biāo)明功能、參數(shù)、返回值和拋出的異常。一個(gè)清晰好懂的注釋對于項(xiàng)目來說還是很必要的。
例如:
/** * get paginate data * * @param int $step * @param int $page * @return array * @throws PDOException */ public function paginate($step, $page = NULL) { ... }尾聲
一個(gè)查詢構(gòu)造器的創(chuàng)建到此結(jié)束,希望對大家有用。如果發(fā)現(xiàn)文中的書寫和思路有錯(cuò)誤,或者對此項(xiàng)目有什么好的建議的話,歡迎提出。對文中的解釋有不解的地方,也歡迎提問。
查詢構(gòu)造器的完整代碼:WorkerF - DB
查詢構(gòu)造器的單元測試完整代碼:WorkerF - tests - DB。
參考【1】PHPUnit Doc
【2】持續(xù)集成服務(wù) Travis CI 教程 - 阮一峰
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/28715.html
摘要:而在項(xiàng)目開發(fā)中,我們想要的是一個(gè)更好用的可維護(hù)的工具,此時(shí),對代碼的封裝模塊化就顯得尤為重要,于是出現(xiàn)了兩種方案查詢構(gòu)造器,對象關(guān)系映射。典型環(huán)境下按照一般的查詢構(gòu)造器處理就行。 文章目錄 寫一個(gè)特殊的查詢構(gòu)造器 - (前言) 寫一個(gè)特殊的查詢構(gòu)造器 - (一、程序結(jié)構(gòu),基礎(chǔ)封裝) 寫一個(gè)特殊的查詢構(gòu)造器 - (二、第一條語句) 寫一個(gè)特殊的查詢構(gòu)造器 - (三、條件查詢) 寫一個(gè)特殊...
摘要:軟件測試自學(xué)秘訣面試失敗一天,心態(tài)穩(wěn)的一批,因?yàn)槊嬖嚨娜峭獍耸聨臀衣?lián)系的公司,工資全都是一萬以上,之前只有四五千的自己根本不覺得自己能勝任。 個(gè)人是去年年底零基礎(chǔ)轉(zhuǎn)行,兩三千培訓(xùn)費(fèi)學(xué)出來,學(xué)完后也是稀里糊涂,僅是知道功能測試就是找問題,其他接口,性能,數(shù)據(jù)庫,python基礎(chǔ),虛擬機(jī)搭建網(wǎng)站都實(shí)現(xiàn)了課程展示那樣。面試資...
摘要:注在常駐內(nèi)存單例模式下,這種多次用一個(gè)類進(jìn)行查詢的情形很常見。斷線重連對于典型環(huán)境而言,一次的查詢已經(jīng)隨著的請求而結(jié)束,的垃圾回收功能會回收一次請求周期內(nèi)的數(shù)據(jù)。但在常駐內(nèi)存的環(huán)境下,尤其是單例模式下,數(shù)據(jù)庫驅(qū)動類可能一直在內(nèi)存中不被銷毀。 構(gòu)造、執(zhí)行第一條語句 上一篇完成了代碼結(jié)構(gòu)的搭建和 PDO 的基礎(chǔ)封裝,這一篇我們來講如何構(gòu)造一個(gè)最基本的 SQL 語句,并執(zhí)行得到結(jié)果。 que...
摘要:幸運(yùn)的是,使用符號創(chuàng)建的構(gòu)造器,如果在不使用來調(diào)用,則始終會報(bào)錯(cuò),即使在非嚴(yán)格模式下也不會產(chǎn)生問題。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Bugs and Errors 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地采用谷歌翻譯 部分參考了《JavaScript 編程精解(第 2 版)》 調(diào)試的難度是開始編寫代碼的兩倍。 因此,如...
摘要:雖然現(xiàn)在這樣的情況已經(jīng)很少,但是對于查詢構(gòu)造器而言,還是要提供一個(gè)方便的方法來對表前綴進(jìn)行設(shè)置,特別是當(dāng)你沒有權(quán)限修改表名的時(shí)候。所以我們將表前綴作為一個(gè)配置參數(shù)傳入查詢構(gòu)造器,在查詢構(gòu)造器的底層進(jìn)行自動前綴添加。 關(guān)聯(lián)查詢是關(guān)系型數(shù)據(jù)庫典型的查詢語句,根據(jù)兩個(gè)或多個(gè)表中的列之間的關(guān)系,從這些表中查詢數(shù)據(jù)。在 SQL 標(biāo)準(zhǔn)中使用 JOIN 和 ON 關(guān)鍵字來實(shí)現(xiàn)關(guān)聯(lián)查詢。 Join 子...
閱讀 1066·2021-11-12 10:34
閱讀 996·2021-09-30 09:56
閱讀 674·2019-08-30 15:54
閱讀 2608·2019-08-30 11:14
閱讀 1473·2019-08-29 16:44
閱讀 3212·2019-08-29 16:35
閱讀 2498·2019-08-29 16:22
閱讀 2448·2019-08-29 15:39