debug模式查询builder的调试并不难,从构造SQL->数据绑定->SQL执行的过程就可以发现为了方便调试,只要观察到以下信息即可:构建的SQL绑定数据PDO提供了方便的调试方法PDOStatement::debugDumpParams()打印SQL和绑定数据。我们将使用它进行调试工作。向基类添加_debug属性和withDebug()方法:protected$_debug=FALSE;...publicfunctionwithDebug(){$this->_debug=TRUE;//方便的链式调用,返回当前实例return$this;}修改_execute()方法:protectedfunction_execute(){try{$this->_wrapPrepareSql();$this->_pdoSt=$this->_pdo->prepare($this->_prepare_sql);$this->_bindParams();$this->_pdoSt->execute();$this->_reset();//如果处于调试模式,则打印相关信息if($this->_debug){$this->_pdoSt->debugDumpParams();//打印调试信息$this->_debug=FALSE;//debug只对本次访问有效,打印后关闭}}catch(PDOException$e){//超时重连if($this->_isTimeout($e)){$this->_closeConnection();$this->_connect();//重试try{$this->_wrapPrepareSql();$this->_pdoSt=$this->_pdo->prepare($this->_prepare_sql);$this->_bindParams();$this->_pdoSt->execute();$this->_reset();//如果是调试模式,打印相关信息if($this->_debug){$this->_pdoSt->debugDumpParams();//打印调试信息$this->_debug=FALSE;//debug仅对本次访问有效,打印后关闭}}catch(PDOException$e){throw$e;}}else{抛出$e;}}}这样,withDebug()方法可以在任何语句构造过程中使用(调用get()、row()等获取结果的方法之前),并打印调试输出。信息说明:因为我使用的是常驻内存模式,所以我选择直接打印到stdout,这样可以直接在终端界面调试。在传统的web模式下,可以使用OutputControl系列函数来获取调试信息。从项目角度看单元测试的必要性:当项目规模不大的时候,单元测试是没有用的。但是如果底层框架写好或者项目发展到一定规模,单元测试对于提高生产力的贡献是非常大的。从编程的角度来说:单元测试可以让你更好的把程序拆分成最小的单元,帮助你更好的解耦。单元测试的好处是给开发人员的,而不是机器。以我们编写的查询生成器为例。where()、get()等方法依赖于很多底层方法,底层方法之间也相互调用。情况一:你想在一个底层方法中增加一个功能,如何判断修改后是否会影响上层调用?调用所有调用它的方法看结果?不是,只是用单元测试来确定这个方法的输入和输出,可能的运行条件和边界状态,也就是保证最小的单元是可用的。只要单元测试通过,这种方法是没有问题的(当然这里的程序结构要设计合理,测试要准确有效)。情况2:有一天,你想为你的querybuilder支持一个新的数据库,这个数据库的驱动类继承自基类。但是你不知道基类的这些方法对新数据库是否仍然有效(比如postgresql中lastInsertId的区别),是否应该把所有的方法都运行一遍?不需要,你只需要提前为这些通用方法写好单元测试,将驱动类替换成新数据库的驱动类进行单元测试,运行一次你就会发现哪些方法有问题。情况三:和情况一类似,当一个方法有bug时,你不能立即定位到这个方法或者这个方法所依赖的方法中的bug。而当你定位到BUG并修复它的时候,你发现其他方法因为修复而产生了新的BUG,这是又一轮的BUG查找。使用单元测试后,每次有修改就运行单元测试,可以很快的找出修改对整个程序的影响,为我们节省了很多时间。当然,单元测试中也有stub、mock等模式,可以很好的解决依赖不确定、难以复现的问题。我们不会在这里谈论它,因为我们不会在这里关注它。在使用PHPUnit之前,我们使用test/test.php文件编写了一些测试。虽然用这种简单的方式做一些简单的测试没有问题,但是要完成单元测试就需要做很多工作了。而PHP有一个著名的单元测试框架PHPUnit,可以很好的完成我们的测试需求,所以我们使用PHPUnit进行单元测试。安装PHPUnitPHPUnit的安装很简单,在项目中执行:composerrequire"phpunit/phpunit""~4.0"composerrequire"phpunit/dbunit""~2.0"注意:我们的测试需要连接数据库,所以我们现在需要在项目中安装dbunit在该目录下的test文件夹中新建一个以Test.php结尾的测试文件,在命令行运行phpunit即可运行测试。[1]编写单元测试单元测试的代码简单,代码量大,这里就不展示了。所有测试代码参见WorkerF-tests-DB。当然,对于这个单元测试,还有一些需要说明的地方。单元测试结构:项目目录/test/PDODML.phpPDODQL.phpMysqlPDODQLTest.phpMysqlPDODQLTest.phpPgsqlPDODQLTest.phpPgsqlPDODQLTest.phpSqlitePDODQLTest.phpSqlitePDODQLTest.phpPDODriverTest.phptest.xmltestMysql.sqltestPgsql.sqltestSqlite.sqlPDODML。php和PDODQL.php文件:先看PDODML和PDODQL类,包括通用的DQL和DML方法测试,将通过nativePDO执行SQL得到的结果和querybuilder构造得到的结果进行比较。MysqlPDODMLTest.php、MysqlPDODQLTest.php等数据库测试文件:MysqlPDODMLTest继承PDODML,MysqlPDODQLTest继承PDODQL,Pgsql和Sqlite同理。测试类MysqlPDODMLTest和MysqlPDODQLTest使用phpunit的setUpBeforeClass()方法和dbunit的getConnection()方法创建全局可用的数据库连接,方便测试时访问数据库。test.xml:dbunit需要的固定格式的模拟数据,写在test.xml中,用于测试时自动填充和恢复数据表(因为insert,update等操作会改变数据表,这就是为什么使用dbunit)。PDODriverTest.php:它包含对基类所有方法的测试。这里要说明一下,基类中有很多受保护的方法。我的测试计划是写一个新的类,从基类继承,然后新建一个public方法来包装要测试的protected方法。测试新创建的公共方法,即,用于测试受保护的方法。该文件中的测试更多的是测试各个方法构造的SQL字符串是否符合预期,使用了很多正则匹配断言。sql文件:为几个数据库测试表创建表sql。本地测试如果想在本地运行这些测试,打开数据库开头的文件MysqlPDODMLTest.php、MysqlPDODQLTest.php等,将数据库配置中的username、password、dbname等修改为你的自己的。集成测试TravisCI什么是TravisCI?TravisCI提供持续集成服务(ContinuousIntegration,简称CI)。与Github上的项目绑定,只要有新代码,就会自动抓取。然后,提供运行环境,执行测试,完成构建,部署到服务器。[2]持续集成是指每当代码发生变化时自动运行构建和测试,并反馈运行结果。确保符合预期后,将新代码“集成”到主干中。[2]持续集成的好处是,每做一个小的代码改动,就可以看到运行结果,从而不断积累小改动,而不是在开发周期结束时合并一大段代码。[2]使用TravisCI如果你想将项目推送到Github,你可以访问TravisCI。通过编写.travis.yml配置文件,可以实现远程运行环境的语言多版本切换、软件安装、脚本执行等操作。对于querybuilder项目,我们可以让它在远程运行环境中安装相关的数据库软件,进行数据表创建、数据导入、单元测试等操作。我的框架项目WorkerA集成了TravisCI。相关配置参见WorkerF-.travis.yml。如果你有兴趣,你可以了解更多。注释PHP中方法的注释用于提示和生成文档。我这里写注释的方式是指明函数、参数、返回值和抛出的异常。该项目仍然需要一个清晰易懂的注释。例如:/***获取分页数据**@paramint$step*@paramint$page*@returnarray*@throws\PDOException*/publicfunctionpaginate($step,$page=NULL){...结语查询构建器的创建到此结束,希望对大家有用。如果您发现文章在写作和思考上有什么错误,或者您对本项目有什么好的建议,欢迎提出。如果对文中的解释有疑问,也欢迎大家提问。查询生成器的完整代码:WorkerF-DB查询生成器的单元测试完整代码:WorkerF-测试-DB。参考[1]PHPUnitDoc[2]持续集成服务TravisCI教程-阮一峰
