有眼不识RoR
前天,学校的网页设计大赛,展示并且评审。我用的是Ruby on Rails开发一个Web日程管理的应用。结果评委老师们一直认为RoR是一个网站,和他们讲了许久,他们还是认为我和另一个也是用RoR开发的同学是抄袭所谓开源项目的。并且他们对开源项目这种东西,觉得十分的小儿科,觉得很新奇,很Toyful。当我在展示应用中的很多有趣的Ajax效果之后台下同学们还有有些兴趣,但是我看到老师们全部是低下头不听我讲的。虽然最后我获得了一个所谓的最佳代码奖,但是我和同学都不爽,不爽的是自己的作品被人认为是抄袭。更令我失望的是,虽然很多同学看到演示觉得很好玩,但是只有一个学生向我要了联系方式,而且也没有联系我。
今晚看了最新一期的程序员,这一期的主题讲的是开源,很多地方都提到了一点,就是开源要从大学教育开始,让学生在学习的过程中去阅读这些开源的项目,并且去参与其中,甚至去做出贡献。我觉得这个离国内的现实还是好远,国内的大学绝大多数的学生和老师都对开源没什么认识,他们只认得Ms,认得Windows,他们认为VC就是C++,SQL就是SQL Server,认为电脑就是Windows,课件就叫PPT。
我自从发现了Linux下校园网的上网解决方案之后,就再也没有怎么用过windows,并一年前就把windows删除了。其实我也向很多人推广了Linux,但是很多人都是一两天新鲜过后就删除了或者根本没有用过。现在我怎么用IDE,怎么不爽,总觉得写完一个东西之后一定要按下Esc,:,w。
难道中国大地这片土壤真的不适合开源,还是开源只是少数人的游戏?中国人很让我费解。
使用Skinny Spec的一个小技巧
使用Skinny Spec对Controller进行测试时需要定义shared_request来进行请求,但是这个方法的命名非常的令我费解,一般都是使用do_get,
,make_request,do_request等等,那要使用Skinny_Spec对原来的Spec进行测试时就挺麻烦的拉。
因为我是个懒人,我就加了些代码Hack一下,就不用去定义多个shared_request方法。
#spec/spec_helper.rb
module ControllerMacros
module ExampleMethods
def shared_request
verb = [:get, :post, :put, :delete].find{|verb| respond_to? :”do_#{verb}”}
raise “未定义do_get, do_post, do_put, 或do_delete方法 / No do_get, do_post_ do_put, or do_delete has been defined” unless verb
send(”do_#{verb}”)
end
end
end
Spec::Runner.configure do |config|
#…
config.include(ControllerMacros, :type => :controllers)
end
Rspec测试代码重构
消除Spec中的冗余,减少浪费。
看到ben的Blog写了一篇关于Rspec的测试宏的文章:
http://www.benmabey.com/2008/06/08/writing-macros-in-rspec/
其实很多人都是看到Tammer Saleh在MountainWest_Ruby_Conference2008上的Shoulda演示后,和我有一样的感想,就是如果如此DSL化,如此DRY的测试宏能用在Rspec上那就好了。那时我还把Shoulda的官方文档翻译了一下=_=,还有人和我讨论为什么要用Shoulda,还说他就是喜欢Rspec,其实我一次也没有用过Shoulda,但就是觉得这个DSL写的很好。不过Shoulda的缺点也很明显,AR的测试宏依赖于fixture,在业界不提倡使用fixture的情况下还有对测试数据的控制粒度的角度来说,这个做法是不受欢迎的。Rspec中提倡的是全部数据都是在测试时手动Mock出来,所以在1.1.4中才有了stub_mock!这个方法来减轻Mock对象的负担。
Rspec测试的粒度是比较细的,测试的覆盖面在Stroy框架出来之后也能和之前Rails提供的Unittest一样。但是Rspec的测试代码,看上去很冗余,一开始就有这种感觉,一直到看到了Shoulda才发现原来它冗余的地方是什么,这存在于很多地方,比如Controller中每次都是mock一个请求方法,然后就对各种会触发的行为和保存的状态进行断言。这些每次千篇一律的东西,我在想如果在每个context下定义一个请求方法(do_xxx之类的),然后在测试时会在测试方法内部的断言前或断言后自动调用它,这样就能减少很多冗余了。当然我在看完了Shoulda的文档后也尝试写出Rspec的测试宏,不过由于不知道怎么连接到Rspec中就放弃了,话说回来,Rspec中每个测试中上下文关系是很复杂的。
不说废话了,看看鬼佬们怎么减少Rspec中的冗余吧。
下面是一个常见的以Product为领域模型的Controller的测试,用Rspec-Rails插件生成的Scaffold的测试就类似下面这样:
describe ProductsController do
describe "handling GET /products" do
before(:each) do
@product = mock_model(Products)
Product.stub!(:find).and_return([@product])
end
def do_get
get :index
end
it “should be successful” do
do_get
response.should be_success
end
it “should render index template” do
do_get
response.should render_template(’index’)
end
it “should find all products” do
Product.should_receive(:find).with(:all).and_return([@product])
do_get
end
it “should assign the found products for the view” do
do_get
assigns[:products].should == [@product]
end
end
end
上面的代码一看过去就觉得很多重复吧。如果我想让它变成下面这些DSL化的测试代码要怎么办呢?
describe ProductsController do
describe "处理Get /products" do
before(:each) do
@products = mock_model(Product)
Products.stub!(:find).and_return([@product])
end
def do_get
get :index
end
it_should_response_success
it_should_render :template, “index”
it_should_receive Product, :find, :all, [@product]
it_should_assign :products, [@product]
end
end
那么首先为这些宏定义一个Module吧:
module ControllersMacro
module ExampleMethods
def do_action
verb = [:get, :post, :put, :delete].find{|verb| respond_to? :”do_#{verb}”}
raise “No do_get, do_post_ do_put, or do_delete has been defined!” unless verb
send(”do_#{verb}”)
end
end
module ExampleGroupMethods
def it_should_assign(variable_name, value=nil)
it “should assign #{variable_name} to the view” do
value ||= instance_variable_get(”@#{variable_name}”)
if value.kind_of?(String) && value.starts_with?(”@”)
value = instance_variable_get(value)
end
do_action
assigns[variable_name].should == value
end
end
def it_should_response_success
it “should be successful” do
do_action
response.should be_success
end
end
def it_should_receive model, action, with_value = anything, return_value = anything
it “should make #{model.to_s} #{action.to_s}” do
model.should_receive(action).with(with_value).and_return(return_value)
do_action
end
end
end
def self.included(receiver)
receiver.extend ExampleGroupMethods
receiver.send :include, ExampleMethods
end
end
这些就是Spec中的DSL,一般称为Rspec Macros,Rspec宏。那么那么把这些宏与Controller们挂钩呢?每次测试挂一次?当然不是,在ben那篇Blog的留言里,Rspec的核心开发成员David Chelimsky给出了答案,原来Rspec中本来就有接口开放了:
Spec::Runner.configure do |config|
#...
config.include(ControllerMacros, :type => :controllers)
end
这样就把测试宏挂到了Controller的Spec那里,那还等什么就直接在Spec用这些DSL来写出清爽的Spec吧,享受BDD。在这之后当然,我们不会止步于只在Controller中消除冗余,我立马就想到了Shoulda中几个Api,像下面那样,马上就能想到一些ActiveRecord的测试宏。
module ModelsMacro
module ExampleMethods
#......
end
module ExampleGroupMethods
def it_should_require_attributes(variable_name)
it "should require #{variable_name}" do
#TODO
end
end
def it_should_require_unique_attributes variable_name
it "should require unique attributes #{variable_name}" do
#TODO
end
end
def it_should_not_allow_values_for variable_name, not_allow = []
it “should require #{variable_name}” do
#TODO
end
end
def it_should_allow_values_for variable_name, allow_values = []
it “should allow values for #{variable_name}” do
#TODO
end
end
def it_should_protect_attributes variable_name
it “should protect attributes #{variable_name}” do
#TODO
end
end
def it_should_have_one variable_name
it “should have one #{variable_name}” do
#TODO
end
end
def it_should_have_many variable_name, option => {}
it “should have many #{variable_name}” do
#TODO
end
end
def it_should_belong_to variable_name
it “should belong to #{variable_name}” do
#TODO
end
end
end
def self.included(receiver)
receiver.extend ExampleGroupMethods
receiver.send :include, ExampleMethods
end
end
如果觉得自己写很麻烦,那就用别人做好的现成的东西吧:
一个现成的Rspec宏项目,Skinny Spec。它已经完成了Controller和AR的测试宏,页面的测试宏,还有一个生成器:
http://github.com/rsl/skinny_spec/tree
使用很简单,只要用script/plugin安装就好了。不过这个还有些不足,比如还没有shoulda中那个should_be_restful这个最魔幻的方法,在控制器中做出请求动作的那个方法的定义是实现一个名为shared_request方法,在其中加入请求的方法和参数。
其实我对Rspec还有很多想法,比如更加DSL化的测试,对Mock测试数据时更加强大的控制,测试中描述信息的国际化,动态生成测试方法等等。
清爽的Rspec代码,相信每个人都想把它放进自己的项目中。。。
收回来
大学三年里好像不知不觉就学到了很多东西,感觉自己学的很泛很散。我这个人好奇心很强,所以东学一下,西学一下,很多东西都是学到了皮毛。
现在要把自己的知识面收回来,收到一个比较小的知识面上,然后好好把这部分知识学习得深入些。还有就是不要研究过多的过程的东西,比如敏捷,只要了解一下就好了。
编程语言:Ruby,Cpp,C,Erlang,就这四门就够了。整个计算机世界的底层就是C构建出来的,如果要再上一层就是Cpp,要面向对象要DSL就是继承Smalltalk之魂的Ruby,并发的FP语言Erlang很有前途的。D语言的话,以后有机会要用到再说,而Java要用的时候查查书就可以。说什么语言都是一样的人,我觉得是放屁。
开发技术也有几个方向:Web开发,编译器解析器等语言底层构建技术,自动化测试和TDD/BDD。应该就这些了,对于敏捷还是看看就算了,到了实际软件开发的问题开始的时候再考虑这些方法,其实敏捷的经典书籍我也基本看了。
多看看源码。其实现在我看源码的感觉是这样,大概看得懂,可是不懂得怎么改。唉呀呀,还需要修炼。
实践也还是很少。自己有时会写写一些算法的实现,有时会写些小demo。其实我的想法蛮多的,想作的东西也是很多的,但是并不是十分有动力去把这些想法做出来。这次有机会参与到老师的科研项目就要努力去完成它,还有要下定决心完成一个编译器也是一个不错的开始。上次比赛作的那个日程管理平台也是一个不错的东西,其实可以把以前想做的,那个依照《高效人士七个习惯》里提出的日程管理的方法,以Web化的方式实现在这个平台上,要继续开发下去。
自己的不足,对于很多技术的简单应用其实还是游刃有余的状态,但是代码/系统开始复杂的时候就开始手足无措了;看书也很努力很认真,可是思考和总结还是很少,作笔记太少;平时开发时遇到问题时没有作记录下解决方法,之后遇到相同的问题又去查找了一次,太低效率的做法了;有买书强迫症,看到好书就想买,不会太管自己身上还有多少钱,买来又看不完,买了的一些书到现在还只是看完了前言,又浪费钱又阻住地方;英语现在还是太烂,能翻译文档又怎样,能用简单的词语和鬼佬交流又怎样,很多文章还是要一边查词一边看,口语又烂,英文技术写作能力又不行,英语真的是一个相当相当重要的一个工具;数学不行,越来越发现数学的重要,计算机世界本来就是构建在大量的数学定理和分析方法之上的。其实领导能力也是很重要的,自己一个人能做出一个东西,再怎样也是有限的,只有能领导一批人完成一个东西才是真的厉害。
看到了不足就要改进。还有有一个不错的工具,panda和toplang上的同学也经常提到的,那就是Google Notes,好好利用这个工具。
我相信,未来只能由自己创造,自己就是自己的救世主。现在还是和我的理想差的远了,还要努力才行,我要变得很厉害很厉害。
差距
我和一些同样在技术之路上的同龄人具有不小的差距,如Male,还有以前金中去了MS的那个人等。
其实这些差距很多人都说是来自小时候的教育,那些人家庭教育背景都是不错的,父母都是大学毕业,或者知识水平比较高。他们都很早就接触了计算机(是接触了计算机而不是接触游戏),小时候就学会了英语。比如云风,初中就开始学习编程,比如toplang里的一些人说到的,高中初中就读了编译原理这一类的计算机经典理论书籍。
不过大部分人应该都是到了大学才真正的开始接触,学习计算机知识。其实如果来到大学之前,对计算机有所了解但不是掌握很多的话,其实四年(其实还不够四年就是大一到大四前段)也就是全部的学习时间。大一还是在懵懵懂懂,在摸索自己的技术之路,到了大二开始知道怎么学习基础,到了大三,开始要想去作些东西,到了大四,没时间了。
其实四年很短,这就拉开了和一开始就接受了很好教育的人的差距。到了工作的时候,如果自己不挤榨时间来学习的话就没有学习的机会了,最近在忙的时候这种感觉特别的重。











