桃之夭夭
知天之所为,知人之所为者,至矣。知天之所为者,天而生也;知人之所为者,以其知之所知以养其知之所不知,终其天年而不中道夭者:是知之盛也。
BlogJava | 首页 | 发新随笔 | 发新文章 | 联系 | 聚合 | 管理

2009年3月18日

Add primitive Progression Enhancement support of Rails
Currently the concept of Progressive Enhancement is getting hotter and hotter. It emphasizes accessibility, semantic markup, and the importance of separating the complex rich interaction logic into well modularized javascript files. It's really a usefully way of thinking about how to modularize and manage web presentation components. But the Rails framework doesn't have good support for PE, so we have to define our own convention and helpers to make our life easier.

Usually, I'd like to organize js files in the Rails convention, which means we'll have something like this:

app
  |
  - views
      |
      - admin
          |
          _  new.html.erb
          -  index.html.erb
public
  |
  - javascripts
       |
       - admin
           |
           - new.js
           - index.js

And new.js looks similar to:
$(document).ready(function() {
  enhanceInteractionOnElements();
  
});

function helper_methods() {
  
}


Then, add the follow method to ApplicationHelper module:

def page_javascript_include_tag
  file 
= "#{params[:controller]}/#{params[:action]}.js"
  File.exist
?("#{RAILS_ROOT}/public/javascripts/#{file}") ? javascript_include_tag(file) : ""
end

this method will look for js file for a particular page. And in you layout file, add one line in the head sectin:
<%= page_javascript_include_tag %>

That's it. Whenever you request an action of a particular controller, it will find and include the PE js files automatically. Now we've very very primitive support of PE in Rails framework now.

 


 
posted @ 2009-03-18 14:38 Raimundox 阅读(4280) | 评论 (0) | 编辑 收藏
 

2007年11月28日

Misquotation
前几天在JavaEye海阔被标题党阴了一把,看了一篇转的文章叫《被中国人误传了数千年的七句话》,颇有些哭笑不得的感慨:

1. 这些话的确是被误传了不假,但是最多也就一百年吧。中国知识分子不读四书五经史子集的坏风气大抵是开始于所谓的新文化运动吧。再往前的人,对于这些典籍字字爬梳,提了上句马上背下句,就算是以章句式解读为主的宋元,也不应该随随便便就被忽悠了,更不用说反对宋儒理学讲究正本清源的明清了。

2. 古人断章取义是一种风雅的言谈习惯,所谓“雅言”是要字字出典的,有点像对暗号。比如我们家猫跑了,搁古代我肯定问“谁之过欤?”,十有八九会回答说,“言是典守者之过也”,这句射的是“虎兕出于柙”,正好应景。甚至为了诙谐应景,故意曲解文义的情况也是很常见的。如果以此为证说误传的话,恐怕只能算是牛嚼牡丹了。顺便多说一句,其实这个毛病现代人也有,不过不再是古文了,大多数是电影电视台词:“空气在颤抖仿佛天空在燃烧。是啊,暴风雨就要来了”,“道哥,牌子啊”,“你看我的英语,有没有长进”之类的,虽不复古韵,但也还算有趣。


P.S. : 今天team里有人把David Wheeler的名言,贴在了Quote Wall上:“Any problem in computer science can be solved with another layer of indirection.”

这到的确算是一句被误传的名言吧,原文是“Any problem in computer science can be solved with another layer of indirection. But that usually will create another problem.”
posted @ 2007-11-28 18:08 Raimundox 阅读(4387) | 评论 (1) | 编辑 收藏
 

2007年9月5日

A very brief introduction to Aurum
Aurum is a Ruby-based LALR(n) parser generator that you can use to develop your own domain specified languages, scripting languages and programming languages.Although it's just yet another parser generator, Aurum is slightly different from other widely used parser generators:

  1. One of major targets of Aurum is to simplify external DSL development, espectually Ruby external DSL.
  2. Aurum uses incremental LALR(n) algorithm instead of the common used LALR(1)/Full LALR(n) algorithm. That means:
    •   Allowing the user to express grammars in a more intuitive mannar.
    •   Making it easier to handle complicated grammars. For exmaple, COBOL(LALR(2 or 3)), simplified nature language(LALR(3+)) and etc.
    •   Closer to Generalized LR in language recognizing but much more faster.
    •   Smaller parsing table comparing to Full LALR/LR(n) algorithm.
  3. Aurum supports grammar reuse, and itslef'll be shipped with some pre-defined common structures. One of the pain points of external DSL is that you have to re-define lots of common structures, such as if statements, block structure and etc. With Aurum, you could simply reuse them.
  4. Aurum uses a Ruby interal DSL as meta-language, and provides a generic lexer/parser as well. You could test your grammar by the comprehensive testing libraries Ruby has(you could even develop your lexer/parser in the TDD fashion).
  5. As the name suggested, Aurum, the Latin word for Gold, is partially inspired by the GOLD Parsing System. The grammar you created with Aurum could be completely independent of any implementation language,even Ruby.(not implemented yet :) )

Ok, let's start from the 'Hello World in Compiler Construction' —— Expression Evaluation

 1 require 'aurum'
 2 
 3 class ExpressionGrammar < Aurum::Grammar
 4   tokens do
 5     ignore string(' ').one_or_more     # <= a
 6     _number range(?0, ?9).one_or_more  # <= b
 7   end
 8 
 9   precedences do  # <= c
10     left '*', '/'
11     left '+', '-'
12   end
13 
14   productions do # <= d
15     expression expression, '+', expression {expression.value = expression1.value + expression2.value} # <= e
16     expression expression, '-', expression {expression.value = expression1.value - expression2.value}
17     expression expression, '*', expression {expression.value = expression1.value * expression2.value}
18     expression expression, '/', expression {expression.value = expression1.value / expression2.value}
19     expression '(', expression, ')'        do expression.value = expression1.value end # <= f
20     expression _number                     {expression.value = _number.value.to_i}
21     expression '+', _number                {expression.value = _number.value.to_i}
22     expression '-', _number                {expression.value = -_number.value.to_i}
23   end
24 end

If you has any experience with other compiler compiler/parser generator, you probably could understand what happens above quite easily. Instead of explaining things like token, character class, and production, I'd like to emphasise some Aurum conventions:
  1. At point a, we use 'ignore' directive to declare the ignored pattern, such as whitespaces etc.'string' is one of the helper methods(others are enum, range and concat), which is used to define lexical patterns. It will create a pattern matching the given string exactly.
  2. At point b, we declare a lexical token named '_number'. In Aurum, lexical tokens, or terminals from syntax perspective, always start with '_'. The expression '_token_name pattern' is equivalent to 'match pattern, :recognized => :_toke_name'. The 'match' directive is a common way to associate lexical action with leixcal pattern.
  3. At point c, we declare operator precedences of the Expression grammar.The eariler the operators definied, the higher precedence they will have.
  4. At point d, we declare syntax rules of Expression grammar. According to Aurum naming convention, all terminals should start with '_' while all nontermainls start with lower case alphabet character. String literals will be interpreted as reserve words, and added to lexer automatically.
  5. At point e, we define a semantic action to the Addition rule. In semantic action, you could access to the objects in value stack via the name of corresponding symbols.If there are more than one symbol with the same name, you could differentiate them by the order they appered in the production.
  6. At point f, we use do..end instead of {..}. Using Ruby internal DSL as meta-langauge is a double-side sword, you have to bear its flaws while enjoying the remaining parts. There is no perfect world, isn't it?

Now, let's find out how we could use this expression grammar. You could use the helper method as below(it will recalcuate lexical table and parsing table for every call, could be quite slow):

1 puts ExpressionGrammar.parse_expression('1+1').value

or use the lexical table and parsing table to create your own lexer & parser:
 
1   lexer = Aurum::Engine::Lexer.new(ExpressionGrammar.lexical_table, '1+1')
2   parser = Aurum::Engine::Parser.new(ExpressionGrammar.parsing_table(:expression))
3   puts parser.parse(lexer).value


At the end of this post, I'd like to give another grammar example coming from Martin Fowler's HelloParserGenerator series:

 1 require 'aurum'
 2 
 3 Item = Struct.new(:name)
 4 
 5 class Catalog < Aurum::Grammar
 6   tokens do
 7     ignore enum(" \r\n").one_or_more
 8     _item range(?a,?z).one_or_more
 9   end
10 
11   productions do
12     configuration configuration, item {configuration.value = configuration1.value.merge({item.value.name => item.value})}
13     configuration _                   {configuration.value = {}}
14     item 'item', _item                {item.value = Item.new(_item.value)}
15   end
16 end
17 
18 config = Catalog.parse_configuration(<<EndOfDSL).value
19   item camera
20   item laser
21 EndOfDSL
22 
23 puts config['camera'].name


P.S.:The post is based on the developing version of Aurum(0.2.0). You could get it from the svn repository.
P.S.P.S.: There is a more complicated example in the examples directory, a simple Smalltalk interpreter. Have fun:)
posted @ 2007-09-05 23:21 Raimundox 阅读(5615) | 评论 (0) | 编辑 收藏
 
A very brief introduction to Aurum
A very brief introduction to Aurum

Aurum是一个用Ruby实现的LALR(n) parser generator(是的,又是一个parser generator),不过它和其他一些广泛应用的parser generator相比略有不同的:

1.Aurum的主要目标之一,是简化external DSL的开发(尤其是ruby external DSL)。
2.Aurum采用增量LALR(n)算法,而不是通常的LALR(1)。这意味着:
  a.不必由于LALR(1)能力的限制,而改写语法,很多在LALR(1)中冲突的语法在LALR(n)中可以比较自然地表达。
  b.由于识别能力的增强,可以处理一些比较复杂的语法,比如COBOL(LALR(2)或LALR(3)),比如一些简化的自然语言(LALR(3+))。
  c.处理能力接近Generalized LR,却快很多
  d.比起Full LALR/LR(n),增量算法生成的语法表更小。
3.出于简化external DSL实现的考虑,Aurum支持语法重用。
4.Aurum采用Ruby internal DSL作为语法声明的元语言,可以利用Ruby丰富的测试框架,有效地对编译/解释/分析器进行测试。
5.正如名字所暗示的,Aurum(Gold的化学名称)的一部分灵感来自GOLD parsing system,它将支持独立于平台和语言的编译器开发。

好,闲话少说,看一个例子,编译原理中的Hello World —— 表达式求值:
 1 require 'aurum'
 2 
 3 class ExpressionGrammar < Aurum::Grammar
 4   tokens do
 5     ignore string(' ').one_or_more     # <= a
 6     _number range(?0, ?9).one_or_more  # <= b
 7   end
 8 
 9   precedences do  # <= c
10     left '*', '/'
11     left '+', '-'
12   end
13 
14   productions do # <= d
15     expression expression, '+', expression {expression.value = expression1.value + expression2.value} # <= e
16     expression expression, '-', expression {expression.value = expression1.value - expression2.value}
17     expression expression, '*', expression {expression.value = expression1.value * expression2.value}
18     expression expression, '/', expression {expression.value = expression1.value / expression2.value}
19     expression '(', expression, ')'        do expression.value = expression1.value end # <= f
20     expression _number                     {expression.value = _number.value.to_i}
21     expression '+', _number                {expression.value = _number.value.to_i}
22     expression '-', _number                {expression.value = -_number.value.to_i}
23   end
24 end


如果诸位对之前有用过compiler compiler或者parser generator的话,应该能看个七七八八吧。我大概解释一下:
 a.这里定义了文法空白,也就是被lexer忽略的部分,在通常的语言中,是空格回车换行之类的字符;string是用于定义lexical pattern的helper方法(出了string之外,还有range, enum和concat);ignore是一个预定义的说明指令,表示若文本匹配给定模式则该文本会被lexer自动忽略,其格式为:
    ignore pattern {//lexical action}
 b.此处为lexical token声明,所有lexical token必须以_开头,其格式为:
    _token_name pattern {//lexical action}
   这里其实是一个简略写法,等价于
    match pattern, :recognize => :_token_name
 c.此处为运算符优先级声明,支持左/右结合运算符(无结合属性运算符开发中);每一行中所有运算符具有相同优先级;比它下一行的运算符高一个优先级。比如在这个例子中,'*'和'/'具有相同优先级,但是比'+'和'-'的优先级别高。
 d.此处为语法规则声明,所使用的symbol主要有三种,nonterminal(小写字母开头),terminal(其实就是lexical token,以_开头)和literal(字符串常量),其中所有literal都会被自动声明为保留字。
 e.此处定义了一条文法规则(加法),以及对应的semantic action。在semantic action中可以直接通过symbol的名字来获取值栈中的对象。如遇到同名symbol,则按照出现顺序进行编号即可。
 f.其实这个没啥,只不过由于我们使用的是Ruby DSL,所以有时候不能都用{},需要do end,这就是一个例子。

最后测试一下实际中如何使用定义好的语法(使用helper method,注意由于分析表没有缓存,每次都会重算语法表,仅仅适用于debug mode。)
  puts ExpressionGrammar.parse_expression('1+1').value
或者通过分析表自己构造lexer和parser
  lexer = Aurum::Engine::Lexer.new(ExpressionGrammar.lexical_table, '1+1')
  parser = Aurum::Engine::Parser.new(ExpressionGrammar.parsing_table(:expression))
  puts parser.parse(lexer).value

最后最后,给另外一个例子,就是Martin Fowler Blog上的HelloParserGenerator系列中所用的语法:

 1 require 'aurum'
 2 
 3 Item = Struct.new(:name)
 4 
 5 class Catalog < Aurum::Grammar
 6   tokens do
 7     ignore enum(" \r\n").one_or_more
 8     _item range(?a,?z).one_or_more
 9   end
10 
11   productions do
12     configuration configuration, item {configuration.value = configuration1.value.merge({item.value.name => item.value})}
13     configuration _                   {configuration.value = {}}
14     item 'item', _item                {item.value = Item.new(_item.value)}
15   end
16 end
17 
18 config = Catalog.parse_configuration(<<EndOfDSL).value
19   item camera
20   item laser
21 EndOfDSL
22 
23 puts config['camera'].name


P.S.:本文是根据Aurum0.2.0写成的,你可以从rubyforge的svn上得到它。
P.S.P.S.: 在exmaples目录里有一个更复杂一些的例子,是一个简单的Smalltalk解释器。


posted @ 2007-09-05 23:12 Raimundox 阅读(5520) | 评论 (0) | 编辑 收藏
 

2007年8月1日

Erlang Ring Benchmark
《Programming Erlang》第8章后面有一个练习,Ring Benchmark。就是说创建N个进程,把它们组合成环状。然后在这个环上把一条消息在环上传递M圈,然后记录所有的时间。实现起来也挺简单,20行左右吧:

 1 -module(ring_benchmark). 
 2 -export([start/2]).
 3 
 4 start(N, M) -> 
 5   Pid = create_process(self(), N - 1, M),
 6   time(fun() -> Pid ! start, loop(Pid, M) end).
 7 
 8 time(Fun) -> 
 9   statistics(wall_clock),
10   Fun(),
11   {_,Time} = statistics(wall_clock),
12   io:format("Run : ~w s ~n", [Time/1000]).  
13 
14 create_process(Pid, 0, _) -> Pid;
15 create_process(Pid, N, M) -> create_process(spawn(fun() -> loop(Pid, M) end),  N - 1, M).
16 
17 loop(_, 0) -> void;
18 loop(Next, M) -> 
19   receive
20     Message -> Next ! Message,
21     loop(Next, M - 1)
22   end.
23 
24 

有意思是它还有一个第二问,让你用另外一种熟悉的语言实现同样的功能,发送同样多的消息,也把时间记录下来,然后写一篇blog来publish你的结果。其实,大家心知肚明,这种lightweight process啊,message passing concurrency啊都是Erlang的强项,而且实测结果也着实颇为恐怖,一般也就没那闲心拿别的东西来陪衬一把了(Armstrong同学自己实现了一个Java version,效率大约能差到百倍吧)。不过还真有那写不信邪的老大,用stackless python实现了同样的ring benchmark,发现比erlang还快...后来修改代码去掉io操作,Erlang倒是比stackless python快些,但也只是一些而已。




posted @ 2007-08-01 20:34 Raimundox 阅读(6656) | 评论 (4) | 编辑 收藏
 

2007年7月10日

Feng Shui for Standard ML Programmers
http://www.dcs.ed.ac.uk/home/stg/fengshui.ps.gz

今天早上打开Google Reader就看见这么一篇,内容倒也罢了,不过是bad smell的另一个名字而已,硬要扯上分水也只能算是勉勉强强。不过郁闷的是,竟然是个洋人的手笔,国学不昌实不能不令我辈心忧啊。

p.s. 预计未来6个月口头禅:"你这写当心坏了项目的风水"
posted @ 2007-07-10 21:53 Raimundox 阅读(7071) | 评论 (4) | 编辑 收藏
 

2007年6月13日

Domain Oriented Web Testing with Selenium & Ruby (Posted@InfoQ China)
http://www.infoq.com/cn/articles/domain-web-testing

应用Selenium进行Web测试往往会存在几个bad smell:
1.大量使用name, id, xpath等页面元素。无论是功能修改、UI重构还是交互性改进都会影响到这些元素,这使得Selenium测试变得非常脆弱。
2.过于细节的页面操作不容易体现出行为的意图,一段时间之后就很难真正把握测试原有的目的了,这使得Selenium测试变得难于维护。
3.对具体数据取值的存在依赖,当个别数据不再合法的时候,测试就会失败,但这样的失败并不能标识功能的缺失,这使得Selenium测试变得脆弱且难以维护。

而这几点直接衍生的结果就是不断地添加新的测试,而极少地去重构、利用原有测试。其实这到也是正常,单元测试测试写多了,也有会有这样的问题。不过比较要命的是,Selenium的执行速度比较慢(相对单元测试),随着测试逐渐的增多,运行时间会逐渐增加到不可忍受的程度。一组意图不明难以维护的Selenium测试,可以很轻松地在每次build的时候杀掉40分钟甚至2个小时的时间,在下就有花2个小时坐在电脑前面等待450个Selenium测试运行通过的悲惨经历。因此合理有效地规划Selenium测试就显得格外的迫切和重要了。而目前比较行之有效的办法,往大了说,可以叫domain based web testing,具体来讲,就是Page Object Pattern。

Page Object Pattern里有四个基本概念:Driver, Page, Navigator和Shortcut。Driver是测试真正的实现机制,比如Selenium,比如Watir,比如HttpUnit。它们懂得如何去真正执行一个web行为,通常包含像click,select,type这样的表示具体行为的方法;Page是对一个具体页面的封装,它们了解页面的结构,知道诸如id, name, class,xpath这类实现细节,并描述用户可以在其上进行何种操作;Navigator则代表了URL,表示一些不经页面操作的直接跳转;最后Shortcut就是helper方法了,需要看具体的需要了。下面来看一个超级简单的例子——测试登录页面。

1. Page Object

假设我们使用一个单独的Login Page进行登录,那么我们可能会将登录的操作封装在一个名为LoginPage的page object里:

 1 class LoginPage
 2   def initialize driver
 3     @driver = driver
 4   end
 5  
 6   def login_as user
 7     @driver.type 'id=', user[:name]
 8     @driver.type 'xpath=', user[:password]
 9     @driver.click 'name='
10     @driver.wait_for_page_to_load
11   end
12 end


login_as是一个具有业务含义的页面行为。在login_as方法中,page object负责通过依靠id,xpath,name等信息完成登录操作。在测试中,我们可以这样来使用这个page object:

1 page = LoginPage.new $selenium
2 page.login_as :name => 'xxx', :password => 'xxx'
3 

不过既然用了ruby,总要用一些ruby sugar吧,我们定义一个on方法来表达页面操作的环境:

1 def on page_type, &block
2   page = page_type.new $selenium
3   page.instance_eval &block if block_given?
4 end

之后我们就可以使用page object的类名常量和block描述在某个特定页面上操作了:

1 on LoginPage do
2   login_as :name => 'xxx', :password => 'xxx'
3 end
4 

除了行为方法之外,我们还需要在page object上定义一些获取页面信息的方法,比如获取登录页面的欢迎词的方法:

def welcome_message
  @driver.get_text 
'xpath='
end


这样测试也可表达得更生动一些:

1 on LoginPage do
2   assert_equal 'Welcome!', welcome_message
3   login_as :name => 'xxx', :password => 'xxx'
4 end

当你把所有的页面都用Page Object封装了之后,就有效地分离了测试和页面结构的耦合。在测试中,只需使用诸如login_as, add_product_to_cart这样的业务行为,而不必依靠像id,name这些具体且易变的页面元素了。当这些页面元素发生变化时,只需修改相应的page object就可以了,而原有测试基本不需要太大或太多的改动。

2. Assertation

只有行为还够不成测试,我们还要判断行为结果,并进行一些断言。简单回顾一下上面的例子,会发现还有一些很重要的问题没有解决:我怎么判断登录成功了呢?我如何才能知道真的是处在登录页面了呢?如果我调用下面的代码会怎样呢?

1 $selenium.open url_of_any_page_but_not_login
2 on LoginPage {}

因此我们还需要向page object增加一些断言性方法。至少,每个页面都应该有一个方法用于判断是否真正地达到了这个页面,如果不处在这个页面中的话,就不能进行任何的业务行为。下面修改LoginPage使之包含这样一个方法:

1 LoginPage.class_eval do
2   include Test::Unit::Asseration
3   def visible?
4     @driver.is_text_present() && @driver.get_location == 
5   end
6 end


在visible?方法中,我们通过对一些特定的页面元素(比如URL地址,特定的UI结构或元素)进行判断,从而可以得之是否真正地处在某个页面上。而我们目前表达测试的基本结构是由on方法来完成,我们也就顺理成章地在on方法中增加一个断言,来判断是否真的处在某个页面上,如果不处在这个页面则不进行任何的业务操作:

1 def on page_type, &block
2   page = page_type.new $selenium
3   assert page.visible?, "not on #{page_type}"
4   page.instance_eval &block if block_given?
5   page
6 end
7 

这个方法神秘地返回了page对象,这里是一个比较tricky的技巧。实际上,我们只想利用page != nil这个事实来断言页面的流转,比如,下面的代码描述登录成功的页面流转过程:

on LoginPage do
  assert_equal 
'Welcome!', welcome_message
  login_as :name 
=> 'xxx', :password => 'xxx'
end
assert on WelcomeRegisteredUserPage

除了这个基本断言之外,我们还可以定义一些业务相关的断言,比如在购物车页面里,我们可以定义一个判断购物车是否为空的断言:

1 def cart_empty?
2   @driver.get_text('xpath=') == 'Shopping Cart(0)'
3 end

需要注意的是,虽然我们在page object里引入了Test::Unit::Asseration模块,但是并没有在断言方法里使用任何assert*方法。这是因为,概念上来讲page object并不是测试。使之包含一些真正的断言,一则概念混乱,二则容易使page object变成针对某些场景的test helper,不利于以后测试的维护,因此我们往往倾向于将断言方法实现为一个普通的返回值为boolean的方法。

3. Test Data

测试意图的体现不仅仅是在行为的描述上,同样还有测试数据,比如如下两段代码:

 1 on LoginPage do
 2   login_as :name => 'userA', :password => 'password'
 3 end
 4 assert on WelcomeRegisteredUserPage
 5 
 6 registered_user = {:name => 'userA', :password => 'password'}
 7 on LoginPage do
 8   login_as registered_user
 9 end
10 assert on WelcomeRegisteredUserPage


测试的是同一个东西,但是显然第二个测试更好的体现了测试意图:使用一个已注册的用户登录,应该进入欢迎页面。我们看这个测试的时候,往往不会关心用户名啊密码啊具体是什么,我们关心它们表达了怎样的测试案例。我们可以通过DataFixture来实现这一点:

 1 module DataFixture
 2   USER_A = {:name => 'userA', :password => 'password'}
 3   USER_B = {:name => 'userB', :password => 'password'}
 4 
 5   def get_user identifier
 6     case identifier
 7     when :registered then return USER_A
 8     when :not_registered then return USER_B
 9     end
10   end
11 end


在这里,我们将测试案例和具体数据做了一个对应:userA是注册过的用户,而userB是没注册的用户。当有一天,我们需要将登录用户名改为邮箱的时候,只需要修改DataFixture模块就可以了,而不必修改相应的测试:
1 include DataFixtureDat
2 
3 user = get_user :registered
4 on LoginPage do
5   login_as user
6 end
7 assert on WelcomeRegisteredUserPage

当然,在更复杂的测试中,DataFixture同样可以使用真实的数据库或是Rails Fixture来完成这样的对应,但是总体的目的就是使测试和测试数据有效性的耦合分离:

1 def get_user identifier
2   case identifier
3   when :registered then return User.find '.' 
4   end
5 end


4.Navigator

与界面元素类似,URL也是一类易变且难以表达意图的元素,因此我们可以使用Navigator使之与测试解耦。具体做法和Test Data相似,这里就不赘述了,下面是一个例子:

1 navigate_to detail_page_for @product
2 on ProductDetailPage do
3   .
4 end

5. Shortcut

前面我们已经有了一个很好的基础,将Selenium测试与各种脆弱且意图不明的元素分离开了,那么最后shortcut不过是在蛋糕上面最漂亮的奶油罢了——定义具有漂亮语法的helper:

1 def should_login_successfully user
2   on LoginPage do
3     assert_equal 'Welcome!', welcome_message
4     login_as user
5   end
6   assert on WelcomeRegisteredUserPage
7 end

然后是另外一个magic方法:

1 def given identifer
2   words = identifier.to_s.split '_'
3   eval "get_#{words.last} :#{words[0..-2].join '_'}"
4 end

之前的测试就可以被改写为:

def test_should_xxxx
  should_login_successfully given :registered_user
end


这是一种结论性的shortcut描述,我们还可以有更behaviour的写法:

 1 def login_on page_type
 2   on page_type do
 3     assert_equal 'Welcome!', welcome_message
 4     login_as @user
 5   end
 6 end
 7 
 8 def login_successfully
 9   on WelcomeRegisteredUserPage
10 end
11 
12 def given identifer
13   words = identifier.to_s.split '_'
14   eval "@#{words.last} = get_#{words.last} :#{words[0..-2].join '_'}"
15 end


最后,测试就会变成类似验收条件的样子:

1 def test_should_xxx
2   given :registered_user
3   login_on LoginPage
4   assert login_successfully
5 end

总之shortcut是一个无关好坏,只关乎想象力的东西,尽情挥洒Ruby DSL吧:D

结论

Selenium是一个让人又爱又恨的东西,错误地使用Selenium会给整个敏捷团队的开发节奏带来灾难性的影响。不过值得庆幸的是正确地使用Selenium的原则也是相当的简单:

1.通过将脆弱易变的页面元素和测试分离开,使得页面的变化不会对测试产生太大的影响。
2.明确指定测试数据的意图,不在测试用使用任何具体的数据。
3.尽一切可能,明确地表达出测试的意图,使测试易于理解。

当然,除了遵循这几个基本原则之外,使用page object或其他domain based web testing技术是个不错的选择。它们将会帮助你更容易地控制Selenium测试的规模,更好地平衡覆盖率和执行效率,从而更加有效地交付高质量的Web项目。

鸣谢

此文中涉及的都是我最近三周以来对Selenium测试进行重构时所采用的真实技术。感谢Nick Drew帮助我清晰地划分了Driver, Page, Nagivator和Shortcut的层次关系,它们构成我整个实践的基石;感谢Chris Leishman,在和他pairing programming的过程中,他帮助我锤炼了Ruby DSL;还有Mark Ryall和Abhi,是他们第一次在项目中引入了Test Data Fixture,使得所有人的工作都变得简单起来。

posted @ 2007-06-13 09:44 Raimundox 阅读(6473) | 评论 (1) | 编辑 收藏
 

2007年5月11日

The Keyword 'end' Drives Me Crazy
最近很多时间都在用Ruby,逐渐地发现了一件很不爽的事情,就是Ruby的end关键字。block多套几层,很容易就最后一页都是end了...难怪有人说,ruby不过是另一种acceptable Lisp,“最后一页都是括号”的经典标志以另外一种形式复现了...对于Lisp的括号,我还是可以接受的,但是满眼的end,直接让我回忆起10年前冲刺NOI的种种,CPU直接切换到实模式,什么可读啊小粒度方法全都没有了,审美观赤裸地变为短小精悍...最后杀红了眼,一行算出文法定义的所有nullable symbols...

1 while @productions.inject(false) {|c, p| c |= !nullable?(p.nonterminal) && p.symbols.all? {|s| nullable? s} && @nullables << p.nonterminal}

注意1不是行号...这句用的statement modifier, 1是我能想到的最小ruby语句了...

p.s.
我现在已经恢复到OO保护模式了...刚才追求短小过了头的同时,发现了ruby bulid-in object的一个陷阱...
a = Array.new 5, []
[[],[],[],[],[]]
a[0] << 1
[[1],[1],[1],[1],[1]]

想不到华丽的Array直接假设传进去的都是值对象了,好歹您也调个dup啊...


posted @ 2007-05-11 20:32 Raimundox 阅读(6640) | 评论 (1) | 编辑 收藏
 

2007年5月2日

I'm Smalltalk, Which Programming Language are You?
You are smalltalk. you like to treat everyone the same way, but this lack of individuality makes everyone feel like objects.
Which Programming Language are You?

p.s. 这个可能不准...因为李默同学竟然是Lisp...怎么可能...
posted @ 2007-05-02 17:48 Raimundox 阅读(6427) | 评论 (3) | 编辑 收藏
 

2007年3月30日

X-Files
刚才和李默同学回忆了一下,发现我自从入行以来做了很多x项目...下面一一列举一下。

1. IEC61970 Metadata: Electricity Power Trading System

当时刚上班,team里有一个Doamin知识很厉害的清华的博士,毕业的论文就是电力市场,而清华又是国家引入IEC61970的五家之一。所以他很超前的把这两个东西结合在一起,做成了一个系统。说实话,刚了解IEC61970的时候,我是相当的震撼的,有赶上那时候MDA风气刚起,IEC61970又是同时MOF(Meta Object Facility)和RDF based,华丽得不行。一下子我就变成了一个MDA guy,一个metadata guy...以至于,在BJUG最初的2年里,MDA/MOF/Metadata成为了主旋律...

2. IEC61970 & CWM(Common Warehouse Metamodel) & Office Plugin : Data Warehouse Integration System

这是迄今为止,我最不愿意回忆的一个项目...因为Office Plugin...动辄蓝屏的遭遇让我心有余悸...这是一个backend是J2EE,frontend是.Net的office插件系统,主要是报表...两边都使用CWM作为数据统一的形式...基本上做到一半我的意志就崩溃了...

3. DB Migration/Refactoring : Jyxpearl

这个项目...是李默同学的私房最爱,从大学一直做了很久,改版无数次...当时没有这么流行的好词,什么DB Migration啊,DB Refactoring啊,那时候我们统称导数据...我导了好多会...基本上线一回导一回...时至今日...李默同学总是不无得意的说:你看,你DB Migration的能力就是我培养的...

4. JMI(Java Metadata Interface) & Eclipse RCP : Multi/Rich Client ERP Product

这个team其实挺华丽的,老栾的产品经理,李默是开发经理,超级资深行业专家(人家实际做过生产科长,MRPII,ERP都是人家玩剩下的)老齐做需求,俺是Architect,还有动物园里的猪Senior Dev,我认识人中美工能力第一交互设计能力第一的米米姐做UI和交互。由于当时看了netbeans和sun的官方JMI实现得太玩具。我们决定从自己的JMI实现开始,系统结构要求多客户端,web,rcp都要...所以是超轻http协议的b/s,c/s。结构还是不错的,过程李默和我当然是敏捷了。似乎一起都超级完美的时候,就是要坏菜的时候...企业事业部解散了...

5. Java Communication & Eclipse RCP : IC Card Reader

上面那个项目解散之后,我跟李默赋闲在家,有不忍心打扰政府,自谋生路找的项目...这个项目要用IC卡读卡器,为了锻炼我们的Eclipse RCP能力,我们决定用eclipse rcp来做。于是问题就出来了...IC卡怎么办?google一把发现天无绝人之路...Java有一个Communication包,可以连接serial port...不过当时tricky的是...我的本子没有串口,我们买了一个串口到usb的转换器...发现根本不能用...于是只好跑到李默家用他华丽的台式机(这厮当年誓言旦旦的说,laptop太慢,一定要用台式机,东借西借搞了个2G RAM SATA[注意,这是伏笔]的机器)。我当时就觉得,Java的这个东西基本就是充数的,貌似完全没有人用过,文档啥的都特少...只能自己摸索。在经历了无数次失败之后,终于成功了。在showcase那天的上午,我最后实验了读卡什么的,都没问题。兴高采烈的把jar拷到优盘上,刚插到usb口上...只见一道闪电...机器黑了...据李默后来分析是主板烧了...我说没事,拿上硬盘,土一点也不影响showcase。李默说...这个...SATA耶...还不流行呢...我绿...此后很长时间,我都怀疑是我跟李默同学范冲,超级项目杀手...

6. RDF, Semantic Web, SparQL : Ontology-Relationship DB Mapping

这是在一家公司做产品,当时我元数据/MDA领域颇有积累...跟这家公司做得类似,就过来负责研发本体到关系数据库的映射...兼带在D2RQ的基础上实现一个SparQL查询语言。怎么样...听上去很华丽吧...到现在我都认为,这个项目是我最有潜力的牛皮,不定那天web x.0了,我也老了,我就可以拉着小朋友的手去吹牛b了"05年我就做semantic web,O/R mapping知道不?Ontology啊,你们啊,sometime too simple"...不过估计这一天还早得很呢

7. Agile Domain Specified Language : Goodhope

这个也是李默同学有份的项目...话里的敏捷DSL实践...不过说实话,也有点X...
posted @ 2007-03-30 22:52 Raimundox 阅读(6470) | 评论 (3) | 编辑 收藏
 
仅列出标题  下一页
随笔:36 文章:0 评论:93 引用:0
<2023年3月>
日一二三四五六
2627281234
567891011
12131415161718
19202122232425
2627282930311
2345678

常用链接

  • 我的随笔
  • 我的评论
  • 我的参与
  • 最新评论

留言簿(21)

  • 给我留言
  • 查看公开留言
  • 查看私人留言

随笔分类

  • ThoughtBlog(21) (rss)
  • 学而篇第一(15) (rss)

随笔档案

  • 2009年3月 (1)
  • 2007年11月 (1)
  • 2007年9月 (2)
  • 2007年8月 (1)
  • 2007年7月 (1)
  • 2007年6月 (1)
  • 2007年5月 (2)
  • 2007年3月 (8)
  • 2007年1月 (1)
  • 2006年10月 (1)
  • 2006年8月 (1)
  • 2006年3月 (1)
  • 2006年1月 (3)
  • 2005年12月 (12)

搜索

  •  

最新评论

  • 1. re: 丧钟为谁鸣?(4)
  • 通讯模型!
  • --ic
  • 2. re: A Patch for Ruby Watir Test Framework
  • 忘记附上我的邮箱(如果能得到你的答复,万分感谢)
    mac_cf@163.com
  • --mac_cf
  • 3. re: A Patch for Ruby Watir Test Framework
  • 评论内容较长,点击标题查看
  • --mac_cf
  • 4. re: 丧钟为谁鸣?(1)
  • 反复研究了几遍SICP。同时看了录像和书(感谢曹老师下的Video),是什么?求~
  • --qixinkui@qq.com
  • 5. re: Apologize to Object-Oriented Methodology
  • 拜读!开阔了视野!
  • --qixinkui@qq.com

阅读排行榜

  • 1. Selenium Better Pratice(8108)
  • 2. Feng Shui for Standard ML Programmers(7071)
  • 3. 丧钟为谁鸣?(1)(6856)
  • 4. Erlang Ring Benchmark(6656)
  • 5. The Keyword 'end' Drives Me Crazy(6640)

评论排行榜

  • 1. Selenium Better Pratice(11)
  • 2. 丧钟为谁鸣?(1)(11)
  • 3. A Day of Two Pragmatic Programmers' Life(9)
  • 4. ANTLR Lexer Tips(4)
  • 5. A Patch for Ruby Watir Test Framework(4)

Powered by: 博客园
模板提供:沪江博客
Copyright ©2023 Raimundox