rubymysql驱动:Ruby 101:行为驱动

  写下你期望

  在上篇文章里我们创建了个简易插件系统还为它写了个YAML导出器这次我们将会尝试写个SQLite导出器并探讨开发过程中遇到问题

  首先我们插件系统会自动装载插件这意味着当应用启动好后插件就应该准备就绪了这既是我们期望效果也是将来测试时候需要覆盖到内容其重要性犹如航标灯塔指引着正确开发方向既然如此何不把它正式地记录下来:

  当应用启动好后SQLite导出器就应该准备就绪了

  不难预料类似描述还有很多很多如果我们把这些描述收集起来我们将会得到份规范标准文档如果这份规范标准文档还可以执行我们就可以随时随地验证插件行为了别误会我不是在开玩笑下面我们来看看如何实现份可执行规范标准文档

  如果你和我样都是使用NetBeans那么你只需在Project窗口里右击RSpec Files文件夹然后选择New\RSpec File菜单项:



  图 1

  随后你将会看到下面这个对话框:



  查看原图(大图)

  图 2

  般而言每份规范标准文件都对应个目标类这点从上图也可以看出来但是我们要开发SQLite导出器不是个类而是AddIn类个例子那么Tested Class文本框应该如何填呢?随便填只要规范标准文件名字和路径没有问题就行了(规范标准文件名字通常以spec结尾)单击Finish按钮NetBeans将会为你创建如下代码:



  代码 1

  从上面代码可以看到NetBeans假定我们目标类(我们随便填Whatever类)是在whatever.rb文件里于是为我们加载这个文件还为我们创建个Whatever类例子当然我们心知肚明这些假设对于我们情况并不适用那么我们应该如何修改这份规范标准文档呢?首先插件是通过插件系统来访问为了访问插件系统我们需要加载addins.rb这个文件;其次我们要描述不是个类而是个例子我们可以为describe思路方法提供用于标识我们SQLite导出器;最后我们不需要例子化什么插件系统已经帮我们处理了所以我们可以去掉before这块代码了根据这些建议我们可以把规范标准文件修改如下:



  代码 2

  接下来我们可以实现前面提到那个描述了:



  代码 3

  我相信这份代码已经很直观地阐明它要做事了如果我们现在执行这份规范标准文档我们将会得到如下结果:



  图 3

  失败是正常毕竟我们还没有实现SQLite导出器嘛现在我们创建个sqlite_exp.rb文件并在里面写下这些代码:



  代码 4

  然后在addins.rb文件里注册sqlite_exp.rb文件:



  代码 5

  再次执行规范标准文档:



  图 4

  结果正如我们预料那样通过了!

  回顾上面整个过程有没有觉得它和TDD很像?如果有那么你感觉是对但它和TDD还是有点儿不它也有自己名字叫做BDD全名是行为驱动开发(Behavior-Driven Development)而RSpec则是RubyBDD框架如果你有兴趣进步了解BDD可以读读behaviour-driven.org上介绍这篇介绍也提到了TDD和BDD关系

  噢忘记说个事儿了如果你发现规范标准文档无法执行那么你很可能还没安装RSpec你可以通过如下方式进行确认:



  图 5

  如果确认没有安装可以通过

  gem rspec

  命令进行安装(Linux平台需要在前面加上

  sudo

  )

  期望2:把数据写入数据库

  毫无疑问这个期望是最重要试想如果SQLite输出器无法保存数据我还要它来干什么呢?事不宜迟了赶快在规范标准文档里写下这个期望吧:



  代码 6

  现在问题是里面逻辑如何办?别担心我们可以先界定整体方向然后逐步深入细节

  般而言每个期望都包含两个要点:执行预设操作和验证预期效果在这里预设操作包括查找并获取SQLite输出器、提供完整有效测试数据以及执行输出器主体逻辑这些都不难实现:



  代码 7

  接下来是验证预期效果执行上述操作肯定会产生个数据库文件我们可以先验证这个文件是否存在:



  代码 8

  我相信上面这句点都不难理解但你可能会好奇它是如何工作当RSpec执行规范标准文档时它会在每个对象上定义should和should_not思路方法这些思路方法接受Matcher对象作为参数代码2exist思路方法会返回个使用File对象exist?思路方法进行判断Matcher对象而我们传给exist思路方法参数也会传给File对象exist?思路方法那么如何验证数据是否写进去了呢?最直接办法就是查下数据库:



  代码 9

  我们首先创建个Database对象然后通过它execute思路方法执行条SQL语句这将会返回cart表所有数据由于前面写入是两本书我们期望现在查到也是两本书需要介绍说明第 3句最后books是RSpec提供语法糖你可以把它换成items、elements或者其它你认为更加合适单词这样当你把这句话符号都去掉后就会得到个标准英语句子了:

  cart should have 2 books

  这句话也充分表达了我们期望最后是关闭数据库对象当然要让这些代码工作你还需要加上require 'sqlite3'

  为了避免多个测试的间出现数据干扰我们应该在完成每个测试的后删除产生数据库文件这项任务可以在after思路方法代码块里完成:



  代码 10

  此外由于每个测试都需要使用SQLite输出器我们不妨把查找并获取SQLite输出器工作放到before思路方法代码块里:



  查看原图(大图)

  代码 11

  当然代码3和代码7都要做相应调整首先它们不需要查找并获取SQLite输出器了其次对本地变量sqlite_exporter引用要改为对例子变量@sqlite_exporter引用嗯?例子变量?谁例子变量?事实上describe思路方法会创建个ExampleGroup对象而我们在before思路方法代码块里创建例子变量则隶属于这个ExampleGroup对象还有点需要介绍说明默认情况下before思路方法代码块会在每个测试执行的前执行如果你想让它在所有测试执行的前执行而且就执行你可以通过参数来指定:



  代码 12

  这种做法也适用于于after思路方法

  噢又忘记说了在使用SQLite的前请先安装SQLite3数据库及其Ruby GemSQLite3数据库安装极其简单到官网下载sqlite-3_6_22.zip和sqlitedll-3_6_22.zip解压至任意目录(比如C:\SQLite)然后在PATH环境变量里添加这个路径就行了;SQLite3Ruby Gem也很容易安装只需在命令行输入

  gem sqlite3-ruby

  就行了

  实现2:把数据写入数据库

  SQLite输出器工作其实很简单就 3个事儿:创建数据库文件、创建表和把数据写入表

  第个事儿好办只需下面这句就搞定了:



  代码 13

  嗯?这句好像哪里见过?是前面打开数据库文件时用也是这句事实上当你执行这句时它会试图打开指定数据库文件如果这个文件不存在它就会创建个新在继续的前请允许我开个小差试想每次访问SQLite数据库时我们要创建个Database对象访问完后我们要close思路方法释放相关资源如果Ruby有像C#using语法就好了天啊难道这个想法只能是个想法吗?慢着!我记得F#就有using但F#using并不是语法部分而是我们可以这样使用using:



  图 6

  显然这个做法完全可以借鉴过来:



  代码 14

  事实上实现这个using思路方法点都不难:



  代码 15

  这里begin … ensure … end相当于C#try … finally由于begin … ensure … end以外没有其它代码我们可以把using思路方法实现进步简化成:



  代码 16

  嗯不错但如果我随便拿个对象来用呢比如这样:



  代码 17

  毫无疑问ensure部分会抛出异常没有close思路方法可以对于这种情况我期望using思路方法先看看有没有close思路方法有就没就拉倒如何做到呢?还记得respond_to?思路方法吗我们可以通过它来检测close思路方法是否存在:



  代码 18

  前几天ruby-talk上也有人问Ruby是否有像C#using语法于是我把代码放上去随即有人提出对象可以思路方法和respond_to?思路方法了解可能区别:



  代码 19

  并建议我们换用如下代码:



  代码 20

  我们可以这样理解ensure里面那句:尝试close思路方法忽略任何抛出异常相比的下这种做法简洁有效但是它带来风险也是不容忽视它可能会把close思路方法抛出其它异常也吃掉最保险做法应该是只处理NoMethodError异常但由于上面这种简化写法不能指定待处理异常类型于是我们只好换用完整异常处理语法了:



  代码 21

  那么如果目标资源是通过其它思路方法来释放呢?这样我们可以考虑让方配置用于释放资源思路方法:



  代码 22

  这个不难做到只需给using增加个参数就行了:



  代码 23

  默认情况下我们使用close思路方法此外在使用using思路方法时也不是定要通过代码块参数把资源传递给代码块事实上我们完全可以写成这样:



  代码 24

  嗯看起来和C#几乎样了但有点需要注意就是定要把{和using放在同否则不符合代码块语法毕竟这不是真货啊哈哈~

  回到正题SQLite输出器要做第 2件事儿是创建个cart表这其实也是句话事儿:



  代码 25

  嗯?奇怪了类型呢?不如我们试下存点东西看看存进去是什么:



  图 7

  噢!我类型啊!毫无疑问SQLite理解我期望并按我期望方式工作着那么如果我插入区别类型数据呢比如这样:



  图 8

  33.99是浮点数并被正确地解析为REAL了而35是整数它会被"正确地"解析为整数还是被转换为REAL呢?我们查查看:



  图 9

  噢!数据类型得到正确解析了但是不可思议事情也发生了列存在区别类型数据!由此可见SQLite对列处理更像动态语言对变量处理其类型取决于实际数据那么如果我希望把35处理成REAL呢?我们可以向SQLite建议由于SQLiteALTER TABLE只支持重命名和加字段要添加类型建议只好重新建个表了:



  图 10

  很好!如果我插入既不是REAL也不是INTEGER而是TEXT呢?我们试试看:



  图 11

  毫无疑问我们得到了期望结果但是如果SQLite发现你给串无法转换比如ABC它会原样保留而不是禁止你插入这就是为什么我刚才说"向SQLite建议"而不是"命令SQLite"现在我想建议SQLite把title和authors处理成TEXT排序时不区分大小写并且组合起来是唯(即书名和作者都书看作同本书)此外 3个字段都不允许为NULL那么我们可以把代码24改成这样:



  代码 26

  你可能会奇怪为什么没有主键事实上是有无论你是否创建你自己主键SQLite都会为你创建你可以通过ROWID、_ROWID_或者OID来访问(注意.width命令用于调整各列显式宽度):



  图 12

  如果你创建了个"类型"正好为INTEGER PRIMARY KEY字段SQLite将会把它看作ROWID"分身";如果你创建主键是其它类型甚至是组合主键那么PRIMARY KEY效果只是相当于UNIQUE真正参和到B-Tree实现是ROWID

Tags:  ruby下载 ruby是什么 ruby rubymysql驱动

延伸阅读

最新评论

发表评论