2006年1月10日
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.
前幾天在JavaEye海闊被標(biāo)題黨陰了一把,看了一篇轉(zhuǎn)的文章叫《 被中國(guó)人誤傳了數(shù)千年的七句話(huà)》,頗有些哭笑不得的感慨:
1. 這些話(huà)的確是被誤傳了不假,但是最多也就一百年吧。中國(guó)知識(shí)分子不讀四書(shū)五經(jīng)史子集的壞風(fēng)氣大抵是開(kāi)始于所謂的新文化運(yùn)動(dòng)吧。再往前的人,對(duì)于這些典籍字字爬梳,提了上句馬上背下句,就算是以章句式解讀為主的宋元,也不應(yīng)該隨隨便便就被忽悠了,更不用說(shuō)反對(duì)宋儒理學(xué)講究正本清源的明清了。
2. 古人斷章取義是一種風(fēng)雅的言談習(xí)慣,所謂“雅言”是要字字出典的,有點(diǎn)像對(duì)暗號(hào)。比如我們家貓跑了,擱古代我肯定問(wèn)“誰(shuí)之過(guò)歟?”,十有八九會(huì)回答說(shuō),“言是典守者之過(guò)也”,這句射的是“虎兕出于柙”,正好應(yīng)景。甚至為了詼諧應(yīng)景,故意曲解文義的情況也是很常見(jiàn)的。如果以此為證說(shuō)誤傳的話(huà),恐怕只能算是牛嚼牡丹了。順便多說(shuō)一句,其實(shí)這個(gè)毛病現(xiàn)代人也有,不過(guò)不再是古文了,大多數(shù)是電影電視臺(tái)詞:“空氣在顫抖仿佛天空在燃燒。是啊,暴風(fēng)雨就要來(lái)了”,“道哥,牌子啊”,“你看我的英語(yǔ),有沒(méi)有長(zhǎng)進(jìn)”之類(lèi)的,雖不復(fù)古韻,但也還算有趣。
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.”
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:
- One of major targets of Aurum is to simplify external DSL development, espectually Ruby external DSL.
- 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.
- 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.
- 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).
- 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:
- 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.
- 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.
- At point c, we declare operator precedences of the Expression grammar.The eariler the operators definied, the higher precedence they will have.
- 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.
- 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.
- 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:)
A very brief introduction to Aurum
Aurum是一個(gè)用Ruby實(shí)現(xiàn)的LALR(n) parser generator(是的,又是一個(gè)parser generator),不過(guò)它和其他一些廣泛應(yīng)用的parser generator相比略有不同的:
1.Aurum的主要目標(biāo)之一,是簡(jiǎn)化external DSL的開(kāi)發(fā)(尤其是ruby external DSL)。
2.Aurum采用增量LALR(n)算法,而不是通常的LALR(1)。這意味著:
a.不必由于LALR(1)能力的限制,而改寫(xiě)語(yǔ)法,很多在LALR(1)中沖突的語(yǔ)法在LALR(n)中可以比較自然地表達(dá)。
b.由于識(shí)別能力的增強(qiáng),可以處理一些比較復(fù)雜的語(yǔ)法,比如COBOL(LALR(2)或LALR(3)),比如一些簡(jiǎn)化的自然語(yǔ)言(LALR(3+))。
c.處理能力接近Generalized LR,卻快很多
d.比起Full LALR/LR(n),增量算法生成的語(yǔ)法表更小。
3.出于簡(jiǎn)化external DSL實(shí)現(xiàn)的考慮,Aurum支持語(yǔ)法重用。
4.Aurum采用Ruby internal DSL作為語(yǔ)法聲明的元語(yǔ)言,可以利用Ruby豐富的測(cè)試框架,有效地對(duì)編譯/解釋?zhuān)治銎鬟M(jìn)行測(cè)試。
5.正如名字所暗示的,Aurum(Gold的化學(xué)名稱(chēng))的一部分靈感來(lái)自 GOLD parsing system,它將支持獨(dú)立于平臺(tái)和語(yǔ)言的編譯器開(kāi)發(fā)。
好,閑話(huà)少說(shuō),看一個(gè)例子,編譯原理中的Hello World —— 表達(dá)式求值:
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
如果諸位對(duì)之前有用過(guò)compiler compiler或者parser generator的話(huà),應(yīng)該能看個(gè)七七八八吧。我大概解釋一下:
a.這里定義了文法空白,也就是被lexer忽略的部分,在通常的語(yǔ)言中,是空格回車(chē)換行之類(lèi)的字符;string是用于定義lexical pattern的helper方法(出了string之外,還有range, enum和concat);ignore是一個(gè)預(yù)定義的說(shuō)明指令,表示若文本匹配給定模式則該文本會(huì)被lexer自動(dòng)忽略,其格式為:
ignore pattern {//lexical action}
b.此處為lexical token聲明,所有l(wèi)exical token必須以_開(kāi)頭,其格式為:
_token_name pattern {//lexical action}
這里其實(shí)是一個(gè)簡(jiǎn)略寫(xiě)法,等價(jià)于
match pattern, :recognize => :_token_name
c.此處為運(yùn)算符優(yōu)先級(jí)聲明,支持左/右結(jié)合運(yùn)算符(無(wú)結(jié)合屬性運(yùn)算符開(kāi)發(fā)中);每一行中所有運(yùn)算符具有相同優(yōu)先級(jí);比它下一行的運(yùn)算符高一個(gè)優(yōu)先級(jí)。比如在這個(gè)例子中,'*'和'/'具有相同優(yōu)先級(jí),但是比'+'和'-'的優(yōu)先級(jí)別高。
d.此處為語(yǔ)法規(guī)則聲明,所使用的symbol主要有三種,nonterminal(小寫(xiě)字母開(kāi)頭),terminal(其實(shí)就是lexical token,以_開(kāi)頭)和literal(字符串常量),其中所有l(wèi)iteral都會(huì)被自動(dòng)聲明為保留字。
e.此處定義了一條文法規(guī)則(加法),以及對(duì)應(yīng)的semantic action。在semantic action中可以直接通過(guò)symbol的名字來(lái)獲取值棧中的對(duì)象。如遇到同名symbol,則按照出現(xiàn)順序進(jìn)行編號(hào)即可。
f.其實(shí)這個(gè)沒(méi)啥,只不過(guò)由于我們使用的是Ruby DSL,所以有時(shí)候不能都用{},需要do end,這就是一個(gè)例子。
最后測(cè)試一下實(shí)際中如何使用定義好的語(yǔ)法(使用helper method,注意由于分析表沒(méi)有緩存,每次都會(huì)重算語(yǔ)法表,僅僅適用于debug mode。)
puts ExpressionGrammar.parse_expression('1+1').value
或者通過(guò)分析表自己構(gòu)造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
最后最后,給另外一個(gè)例子,就是 Martin Fowler Blog上的 HelloParserGenerator系列中所用的語(yǔ)法:
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.:本文是根據(jù)Aurum0.2.0寫(xiě)成的,你可以從rubyforge的svn上得到它。
P.S.P.S.: 在exmaples目錄里有一個(gè)更復(fù)雜一些的例子,是一個(gè)簡(jiǎn)單的Smalltalk解釋器。
《Programming Erlang》第8章后面有一個(gè)練習(xí),Ring Benchmark。就是說(shuō)創(chuàng)建N個(gè)進(jìn)程,把它們組合成環(huán)狀。然后在這個(gè)環(huán)上把一條消息在環(huán)上傳遞M圈,然后記錄所有的時(shí)間。實(shí)現(xiàn)起來(lái)也挺簡(jiǎn)單,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
有意思是它還有一個(gè)第二問(wèn),讓你用另外一種熟悉的語(yǔ)言實(shí)現(xiàn)同樣的功能,發(fā)送同樣多的消息,也把時(shí)間記錄下來(lái),然后寫(xiě)一篇blog來(lái)publish你的結(jié)果。其實(shí),大家心知肚明,這種lightweight process啊,message passing concurrency啊都是Erlang的強(qiáng)項(xiàng),而且實(shí)測(cè)結(jié)果也著實(shí)頗為恐怖,一般也就沒(méi)那閑心拿別的東西來(lái)陪襯一把了(Armstrong同學(xué)自己實(shí)現(xiàn)了一個(gè)Java version,效率大約能差到百倍吧)。不過(guò)還真有那寫(xiě)不信邪的老大, 用stackless python實(shí)現(xiàn)了同樣的ring benchmark,發(fā)現(xiàn)比erlang還快...后來(lái)修改代碼去掉io操作,Erlang倒是比stackless python快些,但也只是一些而已。
http://www.dcs.ed.ac.uk/home/stg/fengshui.ps.gz今天早上打開(kāi)Google Reader就看見(jiàn)這么一篇,內(nèi)容倒也罷了,不過(guò)是bad smell的另一個(gè)名字而已,硬要扯上分水也只能算是勉勉強(qiáng)強(qiáng)。不過(guò)郁悶的是,竟然是個(gè)洋人的手筆,國(guó)學(xué)不昌實(shí)不能不令我輩心憂(yōu)啊。 p.s. 預(yù)計(jì)未來(lái)6個(gè)月口頭禪:"你這寫(xiě)當(dāng)心壞了項(xiàng)目的風(fēng)水"
http://www.infoq.com/cn/articles/domain-web-testing 應(yīng)用Selenium進(jìn)行Web測(cè)試往往會(huì)存在幾個(gè)bad smell: 1.大量使用name, id, xpath等頁(yè)面元素。無(wú)論是功能修改、UI重構(gòu)還是交互性改進(jìn)都會(huì)影響到這些元素,這使得Selenium測(cè)試變得非常脆弱。 2.過(guò)于細(xì)節(jié)的頁(yè)面操作不容易體現(xiàn)出行為的意圖,一段時(shí)間之后就很難真正把握測(cè)試原有的目的了,這使得Selenium測(cè)試變得難于維護(hù)。 3.對(duì)具體數(shù)據(jù)取值的存在依賴(lài),當(dāng)個(gè)別數(shù)據(jù)不再合法的時(shí)候,測(cè)試就會(huì)失敗,但這樣的失敗并不能標(biāo)識(shí)功能的缺失,這使得Selenium測(cè)試變得脆弱且難以維護(hù)。 而這幾點(diǎn)直接衍生的結(jié)果就是不斷地添加新的測(cè)試,而極少地去重構(gòu)、利用原有測(cè)試。其實(shí)這到也是正常,單元測(cè)試測(cè)試寫(xiě)多了,也有會(huì)有這樣的問(wèn)題。不過(guò)比較要命的是,Selenium的執(zhí)行速度比較慢(相對(duì)單元測(cè)試),隨著測(cè)試逐漸的增多,運(yùn)行時(shí)間會(huì)逐漸增加到不可忍受的程度。一組意圖不明難以維護(hù)的Selenium測(cè)試,可以很輕松地在每次build的時(shí)候殺掉40分鐘甚至2個(gè)小時(shí)的時(shí)間,在下就有花2個(gè)小時(shí)坐在電腦前面等待450個(gè)Selenium測(cè)試運(yùn)行通過(guò)的悲慘經(jīng)歷。因此合理有效地規(guī)劃Selenium測(cè)試就顯得格外的迫切和重要了。而目前比較行之有效的辦法,往大了說(shuō),可以叫domain based web testing,具體來(lái)講,就是Page Object Pattern。 Page Object Pattern里有四個(gè)基本概念:Driver, Page, Navigator和Shortcut。Driver是測(cè)試真正的實(shí)現(xiàn)機(jī)制,比如Selenium,比如Watir,比如HttpUnit。它們懂得如何去真正執(zhí)行一個(gè)web行為,通常包含像click,select,type這樣的表示具體行為的方法;Page是對(duì)一個(gè)具體頁(yè)面的封裝,它們了解頁(yè)面的結(jié)構(gòu),知道諸如id, name, class,xpath這類(lèi)實(shí)現(xiàn)細(xì)節(jié),并描述用戶(hù)可以在其上進(jìn)行何種操作;Navigator則代表了URL,表示一些不經(jīng)頁(yè)面操作的直接跳轉(zhuǎn);最后Shortcut就是helper方法了,需要看具體的需要了。下面來(lái)看一個(gè)超級(jí)簡(jiǎn)單的例子——測(cè)試登錄頁(yè)面。 1. Page Object 假設(shè)我們使用一個(gè)單獨(dú)的Login Page進(jìn)行登錄,那么我們可能會(huì)將登錄的操作封裝在一個(gè)名為L(zhǎng)oginPage的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是一個(gè)具有業(yè)務(wù)含義的頁(yè)面行為。在login_as方法中,page object負(fù)責(zé)通過(guò)依靠id,xpath,name等信息完成登錄操作。在測(cè)試中,我們可以這樣來(lái)使用這個(gè)page object:
1 page = LoginPage.new $selenium 2 page.login_as :name => 'xxx', :password => 'xxx' 3
不過(guò)既然用了ruby,總要用一些ruby sugar吧,我們定義一個(gè)on方法來(lái)表達(dá)頁(yè)面操作的環(huán)境:
1 def on page_type, &block 2 page = page_type.new $selenium 3 page.instance_eval &block if block_given? 4 end
之后我們就可以使用page object的類(lèi)名常量和block描述在某個(gè)特定頁(yè)面上操作了:
1 on LoginPage do 2 login_as :name => 'xxx', :password => 'xxx' 3 end 4
除了行為方法之外,我們還需要在page object上定義一些獲取頁(yè)面信息的方法,比如獲取登錄頁(yè)面的歡迎詞的方法:
def welcome_message @driver.get_text 'xpath= ' end
這樣測(cè)試也可表達(dá)得更生動(dòng)一些:
1 on LoginPage do 2 assert_equal 'Welcome!', welcome_message 3 login_as :name => 'xxx', :password => 'xxx' 4 end
當(dāng)你把所有的頁(yè)面都用Page Object封裝了之后,就有效地分離了測(cè)試和頁(yè)面結(jié)構(gòu)的耦合。在測(cè)試中,只需使用諸如login_as, add_product_to_cart這樣的業(yè)務(wù)行為,而不必依靠像id,name這些具體且易變的頁(yè)面元素了。當(dāng)這些頁(yè)面元素發(fā)生變化時(shí),只需修改相應(yīng)的page object就可以了,而原有測(cè)試基本不需要太大或太多的改動(dòng)。 2. Assertation 只有行為還夠不成測(cè)試,我們還要判斷行為結(jié)果,并進(jìn)行一些斷言。簡(jiǎn)單回顧一下上面的例子,會(huì)發(fā)現(xiàn)還有一些很重要的問(wèn)題沒(méi)有解決:我怎么判斷登錄成功了呢?我如何才能知道真的是處在登錄頁(yè)面了呢?如果我調(diào)用下面的代碼會(huì)怎樣呢?
1 $selenium.open url_of_any_page_but_not_login 2 on LoginPage { }
因此我們還需要向page object增加一些斷言性方法。至少,每個(gè)頁(yè)面都應(yīng)該有一個(gè)方法用于判斷是否真正地達(dá)到了這個(gè)頁(yè)面,如果不處在這個(gè)頁(yè)面中的話(huà),就不能進(jìn)行任何的業(yè)務(wù)行為。下面修改LoginPage使之包含這樣一個(gè)方法:
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?方法中,我們通過(guò)對(duì)一些特定的頁(yè)面元素(比如URL地址,特定的UI結(jié)構(gòu)或元素)進(jìn)行判斷,從而可以得之是否真正地處在某個(gè)頁(yè)面上。而我們目前表達(dá)測(cè)試的基本結(jié)構(gòu)是由on方法來(lái)完成,我們也就順理成章地在on方法中增加一個(gè)斷言,來(lái)判斷是否真的處在某個(gè)頁(yè)面上,如果不處在這個(gè)頁(yè)面則不進(jìn)行任何的業(yè)務(wù)操作:
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
這個(gè)方法神秘地返回了page對(duì)象,這里是一個(gè)比較tricky的技巧。實(shí)際上,我們只想利用page != nil這個(gè)事實(shí)來(lái)斷言頁(yè)面的流轉(zhuǎn),比如,下面的代碼描述登錄成功的頁(yè)面流轉(zhuǎn)過(guò)程:
on LoginPage do assert_equal 'Welcome!', welcome_message login_as :name => 'xxx', :password => 'xxx' end assert on WelcomeRegisteredUserPage
除了這個(gè)基本斷言之外,我們還可以定義一些業(yè)務(wù)相關(guān)的斷言,比如在購(gòu)物車(chē)頁(yè)面里,我們可以定義一個(gè)判斷購(gòu)物車(chē)是否為空的斷言:
1 def cart_empty? 2 @driver.get_text('xpath= ') == 'Shopping Cart(0)' 3 end
需要注意的是,雖然我們?cè)趐age object里引入了Test::Unit::Asseration模塊,但是并沒(méi)有在斷言方法里使用任何assert*方法。這是因?yàn)椋拍钌蟻?lái)講page object并不是測(cè)試。使之包含一些真正的斷言,一則概念混亂,二則容易使page object變成針對(duì)某些場(chǎng)景的test helper,不利于以后測(cè)試的維護(hù),因此我們往往傾向于將斷言方法實(shí)現(xiàn)為一個(gè)普通的返回值為boolean的方法。 3. Test Data 測(cè)試意圖的體現(xiàn)不僅僅是在行為的描述上,同樣還有測(cè)試數(shù)據(jù),比如如下兩段代碼:
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
測(cè)試的是同一個(gè)東西,但是顯然第二個(gè)測(cè)試更好的體現(xiàn)了測(cè)試意圖:使用一個(gè)已注冊(cè)的用戶(hù)登錄,應(yīng)該進(jìn)入歡迎頁(yè)面。我們看這個(gè)測(cè)試的時(shí)候,往往不會(huì)關(guān)心用戶(hù)名啊密碼啊具體是什么,我們關(guān)心它們表達(dá)了怎樣的測(cè)試案例。我們可以通過(guò)DataFixture來(lái)實(shí)現(xiàn)這一點(diǎn):
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
在這里,我們將測(cè)試案例和具體數(shù)據(jù)做了一個(gè)對(duì)應(yīng):userA是注冊(cè)過(guò)的用戶(hù),而userB是沒(méi)注冊(cè)的用戶(hù)。當(dāng)有一天,我們需要將登錄用戶(hù)名改為郵箱的時(shí)候,只需要修改DataFixture模塊就可以了,而不必修改相應(yīng)的測(cè)試:
1 include DataFixtureDat 2 3 user = get_user :registered 4 on LoginPage do 5 login_as user 6 end 7 assert on WelcomeRegisteredUserPage
當(dāng)然,在更復(fù)雜的測(cè)試中,DataFixture同樣可以使用真實(shí)的數(shù)據(jù)庫(kù)或是Rails Fixture來(lái)完成這樣的對(duì)應(yīng),但是總體的目的就是使測(cè)試和測(cè)試數(shù)據(jù)有效性的耦合分離:
1 def get_user identifier 2 case identifier 3 when :registered then return User.find ' .' 4 end 5 end
4.Navigator 與界面元素類(lèi)似,URL也是一類(lèi)易變且難以表達(dá)意圖的元素,因此我們可以使用Navigator使之與測(cè)試解耦。具體做法和Test Data相似,這里就不贅述了,下面是一個(gè)例子:
1 navigate_to detail_page_for @product 2 on ProductDetailPage do 3 . 4 end
5. Shortcut 前面我們已經(jīng)有了一個(gè)很好的基礎(chǔ),將Selenium測(cè)試與各種脆弱且意圖不明的元素分離開(kāi)了,那么最后shortcut不過(guò)是在蛋糕上面最漂亮的奶油罷了——定義具有漂亮語(yǔ)法的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
然后是另外一個(gè)magic方法:
1 def given identifer 2 words = identifier.to_s.split '_' 3 eval "get_#{words.last} :#{words[0..-2].join '_'}" 4 end
之前的測(cè)試就可以被改寫(xiě)為:
def test_should_xxxx should_login_successfully given :registered_user end
這是一種結(jié)論性的shortcut描述,我們還可以有更behaviour的寫(xiě)法:
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
最后,測(cè)試就會(huì)變成類(lèi)似驗(yàn)收條件的樣子:
1 def test_should_xxx 2 given :registered_user 3 login_on LoginPage 4 assert login_successfully 5 end
總之shortcut是一個(gè)無(wú)關(guān)好壞,只關(guān)乎想象力的東西,盡情揮灑Ruby DSL吧:D 結(jié)論 Selenium是一個(gè)讓人又愛(ài)又恨的東西,錯(cuò)誤地使用Selenium會(huì)給整個(gè)敏捷團(tuán)隊(duì)的開(kāi)發(fā)節(jié)奏帶來(lái)災(zāi)難性的影響。不過(guò)值得慶幸的是正確地使用Selenium的原則也是相當(dāng)?shù)暮?jiǎn)單: 1.通過(guò)將脆弱易變的頁(yè)面元素和測(cè)試分離開(kāi),使得頁(yè)面的變化不會(huì)對(duì)測(cè)試產(chǎn)生太大的影響。 2.明確指定測(cè)試數(shù)據(jù)的意圖,不在測(cè)試用使用任何具體的數(shù)據(jù)。 3.盡一切可能,明確地表達(dá)出測(cè)試的意圖,使測(cè)試易于理解。 當(dāng)然,除了遵循這幾個(gè)基本原則之外,使用page object或其他domain based web testing技術(shù)是個(gè)不錯(cuò)的選擇。它們將會(huì)幫助你更容易地控制Selenium測(cè)試的規(guī)模,更好地平衡覆蓋率和執(zhí)行效率,從而更加有效地交付高質(zhì)量的Web項(xiàng)目。 鳴謝 此文中涉及的都是我最近三周以來(lái)對(duì)Selenium測(cè)試進(jìn)行重構(gòu)時(shí)所采用的真實(shí)技術(shù)。感謝Nick Drew幫助我清晰地劃分了Driver, Page, Nagivator和Shortcut的層次關(guān)系,它們構(gòu)成我整個(gè)實(shí)踐的基石;感謝Chris Leishman,在和他pairing programming的過(guò)程中,他幫助我錘煉了Ruby DSL;還有Mark Ryall和Abhi,是他們第一次在項(xiàng)目中引入了Test Data Fixture,使得所有人的工作都變得簡(jiǎn)單起來(lái)。
最近很多時(shí)間都在用Ruby,逐漸地發(fā)現(xiàn)了一件很不爽的事情,就是Ruby的end關(guān)鍵字。block多套幾層,很容易就最后一頁(yè)都是end了...難怪有人說(shuō),ruby不過(guò)是另一種acceptable Lisp,“最后一頁(yè)都是括號(hào)”的經(jīng)典標(biāo)志以另外一種形式復(fù)現(xiàn)了...對(duì)于Lisp的括號(hào),我還是可以接受的,但是滿(mǎn)眼的end,直接讓我回憶起10年前沖刺N(yùn)OI的種種,CPU直接切換到實(shí)模式,什么可讀啊小粒度方法全都沒(méi)有了,審美觀赤裸地變?yōu)槎绦【?..最后殺紅了眼,一行算出文法定義的所有nullable symbols...
1 while @productions.inject(false) {|c, p| c |= !nullable?(p.nonterminal) && p.symbols.all? {|s| nullable? s} && @nullables << p.nonterminal}
注意1不是行號(hào)...這句用的statement modifier, 1是我能想到的最小ruby語(yǔ)句了... p.s. 我現(xiàn)在已經(jīng)恢復(fù)到OO保護(hù)模式了...剛才追求短小過(guò)了頭的同時(shí),發(fā)現(xiàn)了ruby bulid-in object的一個(gè)陷阱... a = Array.new 5, [] [[],[],[],[],[]] a[0] << 1 [[1],[1],[1],[1],[1]] 想不到華麗的Array直接假設(shè)傳進(jìn)去的都是值對(duì)象了,好歹您也調(diào)個(gè)dup啊...
Which Programming Language are You?p.s. 這個(gè)可能不準(zhǔn)...因?yàn)槔钅瑢W(xué)竟然是Lisp...怎么可能...
剛才和李默同學(xué)回憶了一下,發(fā)現(xiàn)我自從入行以來(lái)做了很多x項(xiàng)目...下面一一列舉一下。
1. IEC61970 Metadata: Electricity Power Trading System
當(dāng)時(shí)剛上班,team里有一個(gè)Doamin知識(shí)很厲害的清華的博士,畢業(yè)的論文就是電力市場(chǎng),而清華又是國(guó)家引入IEC61970的五家之一。所以他很超前的把這兩個(gè)東西結(jié)合在一起,做成了一個(gè)系統(tǒng)。說(shuō)實(shí)話(huà),剛了解IEC61970的時(shí)候,我是相當(dāng)?shù)恼鸷车模汹s上那時(shí)候MDA風(fēng)氣剛起,IEC61970又是同時(shí)MOF(Meta Object Facility)和RDF based,華麗得不行。一下子我就變成了一個(gè)MDA guy,一個(gè)metadata guy...以至于,在BJUG最初的2年里,MDA/MOF/Metadata成為了主旋律...
2. IEC61970 & CWM(Common Warehouse Metamodel) & Office Plugin : Data Warehouse Integration System
這是迄今為止,我最不愿意回憶的一個(gè)項(xiàng)目...因?yàn)镺ffice Plugin...動(dòng)輒藍(lán)屏的遭遇讓我心有余悸...這是一個(gè)backend是J2EE,frontend是.Net的office插件系統(tǒng),主要是報(bào)表...兩邊都使用CWM作為數(shù)據(jù)統(tǒng)一的形式...基本上做到一半我的意志就崩潰了...
3. DB Migration/Refactoring : Jyxpearl
這個(gè)項(xiàng)目...是李默同學(xué)的私房最?lèi)?ài),從大學(xué)一直做了很久,改版無(wú)數(shù)次...當(dāng)時(shí)沒(méi)有這么流行的好詞,什么DB Migration啊,DB Refactoring啊,那時(shí)候我們統(tǒng)稱(chēng)導(dǎo)數(shù)據(jù)...我導(dǎo)了好多會(huì)...基本上線(xiàn)一回導(dǎo)一回...時(shí)至今日...李默同學(xué)總是不無(wú)得意的說(shuō):你看,你DB Migration的能力就是我培養(yǎng)的...
4. JMI(Java Metadata Interface) & Eclipse RCP : Multi/Rich Client ERP Product
這個(gè)team其實(shí)挺華麗的,老欒的產(chǎn)品經(jīng)理,李默是開(kāi)發(fā)經(jīng)理,超級(jí)資深行業(yè)專(zhuān)家(人家實(shí)際做過(guò)生產(chǎn)科長(zhǎng),MRPII,ERP都是人家玩剩下的)老齊做需求,俺是Architect,還有動(dòng)物園里的豬Senior Dev,我認(rèn)識(shí)人中美工能力第一交互設(shè)計(jì)能力第一的米米姐做UI和交互。由于當(dāng)時(shí)看了netbeans和sun的官方JMI實(shí)現(xiàn)得太玩具。我們決定從自己的JMI實(shí)現(xiàn)開(kāi)始,系統(tǒng)結(jié)構(gòu)要求多客戶(hù)端,web,rcp都要...所以是超輕http協(xié)議的b/s,c/s。結(jié)構(gòu)還是不錯(cuò)的,過(guò)程李默和我當(dāng)然是敏捷了。似乎一起都超級(jí)完美的時(shí)候,就是要壞菜的時(shí)候...企業(yè)事業(yè)部解散了...
5. Java Communication & Eclipse RCP : IC Card Reader
上面那個(gè)項(xiàng)目解散之后,我跟李默賦閑在家,有不忍心打擾政府,自謀生路找的項(xiàng)目...這個(gè)項(xiàng)目要用IC卡讀卡器,為了鍛煉我們的Eclipse RCP能力,我們決定用eclipse rcp來(lái)做。于是問(wèn)題就出來(lái)了...IC卡怎么辦?google一把發(fā)現(xiàn)天無(wú)絕人之路...Java有一個(gè)Communication包,可以連接serial port...不過(guò)當(dāng)時(shí)tricky的是...我的本子沒(méi)有串口,我們買(mǎi)了一個(gè)串口到usb的轉(zhuǎn)換器...發(fā)現(xiàn)根本不能用...于是只好跑到李默家用他華麗的臺(tái)式機(jī)(這廝當(dāng)年誓言旦旦的說(shuō),laptop太慢,一定要用臺(tái)式機(jī),東借西借搞了個(gè)2G RAM SATA[注意,這是伏筆]的機(jī)器)。我當(dāng)時(shí)就覺(jué)得,Java的這個(gè)東西基本就是充數(shù)的,貌似完全沒(méi)有人用過(guò),文檔啥的都特少...只能自己摸索。在經(jīng)歷了無(wú)數(shù)次失敗之后,終于成功了。在showcase那天的上午,我最后實(shí)驗(yàn)了讀卡什么的,都沒(méi)問(wèn)題。興高采烈的把jar拷到優(yōu)盤(pán)上,剛插到usb口上...只見(jiàn)一道閃電...機(jī)器黑了...據(jù)李默后來(lái)分析是主板燒了...我說(shuō)沒(méi)事,拿上硬盤(pán),土一點(diǎn)也不影響showcase。李默說(shuō)...這個(gè)...SATA耶...還不流行呢...我綠...此后很長(zhǎng)時(shí)間,我都懷疑是我跟李默同學(xué)范沖,超級(jí)項(xiàng)目殺手...
6. RDF, Semantic Web, SparQL : Ontology-Relationship DB Mapping
這是在一家公司做產(chǎn)品,當(dāng)時(shí)我元數(shù)據(jù)/MDA領(lǐng)域頗有積累...跟這家公司做得類(lèi)似,就過(guò)來(lái)負(fù)責(zé)研發(fā)本體到關(guān)系數(shù)據(jù)庫(kù)的映射...兼帶在D2RQ的基礎(chǔ)上實(shí)現(xiàn)一個(gè)SparQL查詢(xún)語(yǔ)言。怎么樣...聽(tīng)上去很華麗吧...到現(xiàn)在我都認(rèn)為,這個(gè)項(xiàng)目是我最有潛力的牛皮,不定那天web x.0了,我也老了,我就可以拉著小朋友的手去吹牛b了"05年我就做semantic web,O/R mapping知道不?Ontology啊,你們啊,sometime too simple"...不過(guò)估計(jì)這一天還早得很呢
7. Agile Domain Specified Language : Goodhope
這個(gè)也是李默同學(xué)有份的項(xiàng)目...話(huà)里的敏捷DSL實(shí)踐...不過(guò)說(shuō)實(shí)話(huà),也有點(diǎn)X...
我常常聽(tīng)到這樣的觀點(diǎn):敏捷軟件開(kāi)發(fā)并不是真正的革命性的方法,它所采用的技術(shù)大多都是古已有之的。比如迭代,你看很哪本軟件工程的教科書(shū)上沒(méi)有提到迭代開(kāi)發(fā)呢?在比如說(shuō)User Story,看上去也不只不過(guò)是Use Case的翻版而已吧!甚至我看RUP也和敏捷方法沒(méi)有太大的區(qū)別吧! 要我說(shuō),這些人要么是不真的了解敏捷開(kāi)發(fā),沒(méi)有認(rèn)識(shí)到敏捷開(kāi)發(fā)的革命性,只是用外在的形式來(lái)把它和其他方法進(jìn)行了比較。有又或者是實(shí)施敏捷方法的時(shí)候不徹底,所以四處碰壁以至于搞起了修正主義。最可怕的就是某些大公司,看敏捷火了,總有包裝一下,到底還是要賣(mài)產(chǎn)品。敏捷軟件開(kāi)發(fā)就是一個(gè)革命性的方法,只不過(guò)它要顛覆的不僅僅是低質(zhì)量的軟件開(kāi)發(fā)方式,更重要的是,它要顛覆軟件生產(chǎn)企業(yè)和軟件的使用企業(yè)之間的生產(chǎn)關(guān)系!!這一點(diǎn)在敏捷宣言里寫(xiě)得再明白不過(guò)了
Customer collaboration over Contract negotiation
敏捷軟件開(kāi)發(fā),就是要以一種更合理的共贏的合作關(guān)系,代替以前畸形的采購(gòu)式的合約關(guān)系。為什么合約關(guān)系就是畸形的?我們來(lái)看看合約雙方的處境。
首先軟件團(tuán)隊(duì)方面承擔(dān)了過(guò)多的風(fēng)險(xiǎn):業(yè)務(wù)變化,改代碼!!商業(yè)抉擇轉(zhuǎn)換,改代碼!!憑啥你甲方的緣故非要我承擔(dān)額外的成本?你說(shuō)我冤不冤?冤!但是人家甲方也冤!!人家花了大把的銀子,拿到一堆不能用的軟件(你要是硬件人家還能轉(zhuǎn)手賣(mài)點(diǎn)錢(qián)),就像你要砍樹(shù)別人給你把鏟子,你要種樹(shù)人家給了你把鋸。擱你,你也不愿意。且不說(shuō)博弈,就算雙方都有心把事情做好,按合同來(lái),甲方不干;不按合同來(lái),乙方不干,最后變成“有心殺賊無(wú)力回天”,大家一起扯扯皮等二期算了。lose-lose,沒(méi)有贏家。
那么合作的關(guān)系是什么呢?合作的關(guān)系就好比你去subway買(mǎi)三明治,面包你自己選,要什么肉你來(lái)挑,蔬菜,cheese,醬汁你也自己看著辦。技術(shù)我來(lái),口味你選。技術(shù)失敗我負(fù)責(zé),口味不合適你負(fù)責(zé)。你做你的強(qiáng)項(xiàng)我來(lái)我的強(qiáng)項(xiàng),最終大家高高興興嘻嘻哈哈不吵不鬧,作出一頓可口午餐。這是時(shí)候,生產(chǎn)關(guān)系變了,我不是你的冷冰冰的供應(yīng)商,你也不是我邪惡的客戶(hù),我們是拴在一根繩子上的螞蚱。成功是我們的,失敗也是我們的。榮辱與共,攜手并肩。聽(tīng)著有點(diǎn)耳熟?沒(méi)錯(cuò),SaaS。敏捷宣言早就說(shuō)了,CoC啊。從供應(yīng)商變成服務(wù)商,從服務(wù)商變成戰(zhàn)略合作伙伴,這是在給軟件企業(yè)指出路,新的生產(chǎn)關(guān)系已經(jīng)盡在其中了。
如果看不清敏捷的這個(gè)根本革命點(diǎn),以為還是開(kāi)發(fā)方法的小打小鬧,那么敏捷根本實(shí)施不成。這話(huà)一般我不敢說(shuō)的,程序員自發(fā)實(shí)施敏捷,只在一種情況下可能成功:大企業(yè)的IT部門(mén)。再趕上個(gè)強(qiáng)力的IT領(lǐng)導(dǎo),自家人嘛,有什么不好談的。一來(lái)二去,就成功了(看看C3,說(shuō)白了不就是IT部門(mén)和業(yè)務(wù)部門(mén)?)但是,如果是做項(xiàng)目的公司,你營(yíng)銷(xiāo)手段不改變,敏捷就不可能成功。你的客戶(hù)跟你不是合作關(guān)系,你通過(guò)敏捷增加質(zhì)量(符合性質(zhì)量)的工作就不會(huì)被人可,那么就不能成為投資,只能是成本。當(dāng)成本增加到不可承擔(dān)的時(shí)候,敏捷就不了了之了。為什么好多人說(shuō)老板沒(méi)有響應(yīng)?舊的生產(chǎn)關(guān)系下敏捷根本就是負(fù)擔(dān)。
說(shuō)道這里,說(shuō)一下以敏捷聞名的ThoughtWorks。其實(shí)很多人都以為T(mén)hougtWorks只有方法論咨詢(xún),沒(méi)錯(cuò)我們是有方法論咨詢(xún),但是也有業(yè)務(wù)模式咨詢(xún),客戶(hù)業(yè)務(wù)模式不改變,他怎么能徹底敏捷?這點(diǎn)大家不可不查啊。
Yesterday I found a interesting? ruby library? ——? blinkenlights, which enables you to control the LEDs on your keyboard. I thouhgt it could be a cheap replacement of lava light, so I wrote a ruby script called 'Poor Man's Lava'
#!/usr/local/bin/ruby require 'rss/1.0' require 'rss/2.0' require 'open-uri' require 'rubygems' require 'blinkenlights'
SUCCESS = 'success'
def read_rss source='http://cruisecontrolrb.thoughtworks.com/projects/CruiseControl.rss' ? content = '' ? open(source) do |s| content = s.read end ? rss = RSS::Parser.parse content, false ? rss.items[0].title.include?(SUCCESS) ? all_ok : alarm end
def all_ok times = 50 ? BlinkenLights.open { |lights| times.times {lights.random} } end
def alarm times = 50, invertal = 0.0 ? BlinkenLights.open { |lights| times.times {lights.flash invertal} } end
while true ? read_rss ? sleep 5? end
make sure to have sufficient permissions to access the device, or you could simple run it as super user.
通常人們會(huì)將User Story和Use Case放在一起比較,雖然二者在形式上具有一定相似性,但是究其本質(zhì)來(lái)說(shuō),還是天淵之別的。這一點(diǎn),專(zhuān)業(yè)BA李默同學(xué)總結(jié)的格外準(zhǔn)確:“用戶(hù)故事是可見(jiàn)的商業(yè)價(jià)值,而不是功能描述”。想要更好的理解這句話(huà),需要了解什么是好的用戶(hù)故事。好的用戶(hù)故事,可用INVEST原則來(lái)概括:
I - Independent N - Negotiable V - Valuable E - Estimable S - Small T - Testable
我個(gè)人覺(jué)得,這個(gè)總結(jié)雖好,但不免分散注意。要我說(shuō),想把握好User Story,只用把握兩個(gè)就夠了Negotiable和Valuable。那么首先要確定什么是Negotiable的。User Story有一個(gè)流傳廣泛的書(shū)寫(xiě)形式:
As <role>, I'd like to <action>, so that <benifit/value/goal>.
為了更好的獲取story還有很多最佳實(shí)踐,比如personas, 比如business process modeling,其實(shí)這些全是糖衣炮彈,As, I'd like to都是噱頭,就是為了把用戶(hù)忽悠暈了,然后圖窮匕現(xiàn)直取商業(yè)價(jià)值和目標(biāo)。一旦商業(yè)價(jià)值確定下來(lái),role, action都是可以negotiable。比如李默之前在文章里舉的用戶(hù)登錄的例子,輸不輸用戶(hù)明密碼?可以商量嘛!是不是只有注冊(cè)用戶(hù)可以享受個(gè)性服務(wù)?可以商量嘛!關(guān)鍵是用戶(hù)想要什么,至于怎么實(shí)現(xiàn)這些到頭來(lái)都是可以商量的,都是Negotiable。只有客戶(hù)的商業(yè)價(jià)值是不能商量的,也沒(méi)的商量。價(jià)值沒(méi)有了,目標(biāo)不存在了,這個(gè)User Story也就沒(méi)用了,商量啥?重寫(xiě)一張就好了。
因此user story又有另外一個(gè)名稱(chēng),叫requirement placeholder。就是客戶(hù)價(jià)值的"立此存照"。至于具體需求,那么就到iteration plan meeting上是商量吧,看看當(dāng)時(shí)什么樣的形式(功能)才是最符合用戶(hù)需要。到此,其實(shí)大家可以看出來(lái)了,user story重點(diǎn)就不再How上,而是在Why上的。有了why,且可Negotiable,把握了精神,你就是按用例來(lái)寫(xiě)需求又有何妨涅?
有了valuable和negotiable的想法墊底,在看看基于user story的初步計(jì)劃制定——也就是有名的prioritization——就容易理解多了。用戶(hù)根據(jù)每張卡的價(jià)值,自行比較作出決定,大體場(chǎng)景就跟向神仙許愿一樣。
神仙:我可以滿(mǎn)足你一個(gè)愿望。 我:我要榮華富貴!!! 神仙:哦,榮華富貴,那么要不要愛(ài)情涅? 我:恩,這個(gè)...那我要忠貞的愛(ài)情好了!! 神仙:哦,忠貞的愛(ài)情,那么要不要健康平安呢? 我:呃.... repeat 無(wú)數(shù)次,最終我要了一件過(guò)冬的皮猴...
自從到了ThouhgtWorks,我訂閱的rss越來(lái)越少,很多程度上來(lái)說(shuō),有了自家的Planet TW,很多相看的blog可以一次性看全了,沒(méi)啥太大的必要去看其他的了。這不,在訂閱了Planet TW后的兩年中,我只增訂了一家:InfoQ。呵呵,想不到如今中文站也來(lái)了 http://www.infoq.com/cn/。不錯(cuò)不錯(cuò)。
pair programing是所有XP實(shí)踐中爭(zhēng)議最大的一個(gè),但竊以為確實(shí)XP實(shí)施的關(guān)鍵關(guān)鍵實(shí)踐之一,甚至于,我認(rèn)為很多XP實(shí)施的失敗都是由于沒(méi)有采用pair programming而造成的。 要了解pair為什么重要,就要了解pair的目的在何。當(dāng)然了,大多數(shù)人都知道pair的重點(diǎn)在于知識(shí)傳遞,知識(shí)共享,持續(xù)走查,降低代碼缺陷等等等等。這些都是pair的優(yōu)點(diǎn),不過(guò)最重要的一點(diǎn)卻常常被忽略——pair programing的最直接而又最根本的目的之一在于simple design。  上圖是著名的Ron Jefferies Model,可以看到XP最佳實(shí)踐被劃分成了一個(gè)一個(gè)的圓圈,而pair, TDD, refactor和simple design位于中心。這并不是說(shuō)這四個(gè)實(shí)踐就是xp的核心。jefferies model每一圈代表了xp實(shí)踐過(guò)程中的不同關(guān)注點(diǎn),最中心的是dev視角,其次是team視角,最外層是交付/管理視角。每圈上的最佳時(shí)間多少都有些緊耦合,放開(kāi)其他的不講,我們專(zhuān)門(mén)說(shuō)說(shuō)dev圈,pair programing, tdd, refactor和simple design。 這四個(gè)實(shí)踐里只有simple design最虛也最重要。有一個(gè)問(wèn)題已經(jīng)被問(wèn)過(guò)無(wú)數(shù)次了,“到底多simple的design才叫simple”。我對(duì)此也有一個(gè)近乎刻板的回答:team里所有人員都能夠理解的design就是simple的。一旦立了標(biāo)準(zhǔn),這四個(gè)實(shí)踐的主從關(guān)系就一下子清晰起來(lái)——simple design是這四個(gè)實(shí)踐的核心,其他三個(gè)實(shí)踐都是它服務(wù)的。 首先做出一個(gè)設(shè)計(jì),最簡(jiǎn)單的判斷標(biāo)準(zhǔn)就是是否可測(cè),一個(gè)不可測(cè)的設(shè)計(jì)基本上可以認(rèn)為無(wú)法實(shí)現(xiàn),于是TDD即是simple design的質(zhì)量保證又是simple design的直覺(jué)驗(yàn)證。 refactor是為了得到好的代碼,那么什么是好的代碼?simple design!!!這里有人不同意了,有人說(shuō)只是要易于修改和擴(kuò)展,可是擴(kuò)展和修改也要?jiǎng)e人看得懂才行啊...simple design是起碼的要求嘛。實(shí)際上,XP中的refactor就是朝著simple design的方向重構(gòu)過(guò)去的,也就是朝著所有人都能理解的代碼refactor過(guò)去的。插一句題外話(huà),為啥說(shuō)好的架構(gòu)的不是設(shè)計(jì)出來(lái)的呢?因?yàn)楹玫募軜?gòu)至少應(yīng)該是simple design的,而simple的概念有和人員相關(guān)...所以當(dāng)你極盡能事show off你的pattern知識(shí)之后,得到復(fù)雜設(shè)計(jì)根本就不可能是好的架構(gòu)。時(shí)刻緊記,架構(gòu)是妥協(xié)啊... 最后,pair programming是simple design的實(shí)際檢驗(yàn)!!!因?yàn)榧幢闶亲顝?fù)雜的設(shè)計(jì),只要是你自己想出來(lái)的,你都覺(jué)得它簡(jiǎn)單無(wú)比,里面充滿(mǎn)了直白且顯而易見(jiàn)的理由。可惜不幸的是,我們要的簡(jiǎn)單,是對(duì)team里所有人的簡(jiǎn)單。如果你的pair不能理解你的設(shè)計(jì),那么說(shuō)明你的設(shè)計(jì)復(fù)雜了;如果你們兩個(gè)人懂,但是swith pair的時(shí)候,換過(guò)來(lái)的人不懂,說(shuō)明你的設(shè)計(jì)復(fù)雜了。pair programming(以及他那容易讓人忽略的子實(shí)踐switching pair)就是檢驗(yàn)simple design的過(guò)程。pair programing + refactor就是時(shí)刻保證simple design防止過(guò)渡設(shè)計(jì)反攻倒算的過(guò)程。pair programming + refactor + tdd就是團(tuán)結(jié)在以Deming同學(xué)built quality in的質(zhì)量大旗下,堅(jiān)定地與過(guò)渡設(shè)計(jì)做斗爭(zhēng)的過(guò)程。據(jù)我觀察,至沒(méi)有使用pair programming的團(tuán)隊(duì)中,少一半simple design成了口號(hào),而這一半中,至少又有一半最終放棄了xp放棄了敏捷(俺以前帶過(guò)的團(tuán)隊(duì)就有這樣的...默哀一下)。深刻的教訓(xùn)啊,我們來(lái)高呼一下:"pair programming是檢驗(yàn)simple design的唯一標(biāo)準(zhǔn)!"。 最后說(shuō)一下pair programming經(jīng)濟(jì)學(xué),過(guò)多的假設(shè)我就不講了。單說(shuō)一點(diǎn),有哪一位上班的8小時(shí)從來(lái)不上msn/yahoo/qq等im?有哪一位上班從來(lái)不上論壇/不回貼/不發(fā)郵件?以我pair的經(jīng)驗(yàn)來(lái)看,pair programming的過(guò)程中,兩個(gè)人幾乎不會(huì)用im,幾乎不會(huì)逛論壇。你不好意思呀,畢竟不是你一個(gè)人的機(jī)器,畢竟是兩個(gè)人的時(shí)間,畢竟你也不愿意給同事一種懶散的印象吧?收回的這么浪費(fèi)的時(shí)間,至少頂?shù)眠^(guò)另外一個(gè)人的工作時(shí)間了吧?
想要理解敏捷軟件開(kāi)發(fā)為什么好,需要從軟件質(zhì)量講起。那么軟件的質(zhì)量是什么?這個(gè)問(wèn)題有很多中答案,我們不妨想看看傳統(tǒng)質(zhì)量理論對(duì)于質(zhì)量是如何理解的。教科書(shū)上說(shuō),在20世紀(jì)質(zhì)量管理的發(fā)展歷程經(jīng)歷了質(zhì)量檢驗(yàn)、統(tǒng)計(jì)質(zhì)量控制和全面質(zhì)量管理三個(gè)階段。其中,質(zhì)量理念也在不斷的演變。據(jù)說(shuō)有這么幾個(gè)階段:
符合性質(zhì)量 20世紀(jì)40年代,符合性質(zhì)量概念以符合現(xiàn)行標(biāo)準(zhǔn)的程度作為衡量依據(jù),“符合標(biāo)準(zhǔn)”就是合格的產(chǎn)品質(zhì)量,符合的程度反映了產(chǎn)品質(zhì)量的水平。比如說(shuō)我做一個(gè)杯子,沒(méi)什么特別的要求,也不是我神經(jīng)質(zhì)的藝術(shù)作品,就是普普通通的一個(gè)杯子,那么需要高矮長(zhǎng)短,大小胖瘦,等等一干質(zhì)量屬性,我做好了可以拿著質(zhì)量標(biāo)準(zhǔn)來(lái)對(duì)比,一眼就可以看出那里出了什么問(wèn)題。通過(guò)是否符合這些標(biāo)準(zhǔn)判斷產(chǎn)品具有相應(yīng)的質(zhì)量。 那么軟件的質(zhì)量理是不是符合性質(zhì)量呢?我個(gè)人覺(jué)得不屬于。雖然我們一樣可以拿出各種各樣的標(biāo)準(zhǔn),比如故障率,比如bug數(shù)等等。但是這些標(biāo)注都滿(mǎn)足確不一定是好的軟件,比如我寫(xiě)一個(gè)helloworld,雖然他可以沒(méi)有bug。但是卻發(fā)揮不了任何的作用。這樣的軟件就屬于“高質(zhì)量”的廢品。正如趙辛梅評(píng)價(jià)方鴻漸,“你不討厭,但是毫無(wú)用處。”,顯然毫無(wú)用處的軟件不會(huì)是真正高質(zhì)量的軟件。
適用性質(zhì)量 20世紀(jì)60年代,適用性質(zhì)量概念以適合顧客需要的程度作為衡量的依據(jù),從使用的角度定義產(chǎn)品質(zhì)量,認(rèn)為質(zhì)量就是產(chǎn)品的“適用性”。是“產(chǎn)品在使用時(shí)能夠成功滿(mǎn)足用戶(hù)需要的程度”。質(zhì)量涉及設(shè)計(jì)開(kāi)發(fā)、制造、銷(xiāo)售、服務(wù)等過(guò)程,形成了廣義的質(zhì)量概念。適用性質(zhì)量的例子也很多,比如我買(mǎi)了一件Givenchy西服(我還真買(mǎi)了一件),但是一時(shí)又沒(méi)有特別正是的場(chǎng)合(目前還真沒(méi)有什么正式的場(chǎng)合),于是我一天四頓牛排(其實(shí)只有一頓),于是就吃胖了,這件華麗的Givenchy就穿不上了。那么這件衣服從符合性質(zhì)量來(lái)說(shuō),是優(yōu)質(zhì)品,但是從適用性質(zhì)量來(lái)說(shuō),卻不是一個(gè)高質(zhì)量的產(chǎn)品——因?yàn)槲掖┎簧稀_€有一句話(huà),叫甲之熊掌乙之砒霜。也是適用性質(zhì)量的標(biāo)準(zhǔn)體現(xiàn)。 那么軟件的質(zhì)量是不是適用性質(zhì)量呢?我個(gè)人覺(jué)得,軟件的質(zhì)量至少是適用性質(zhì)量。軟件,尤其是定制軟件/企業(yè)軟件,就是量體裁衣。軟件的基本質(zhì)量就是要在用戶(hù)使用的過(guò)程中發(fā)揮價(jià)值,支撐客戶(hù)的業(yè)務(wù)發(fā)展。 書(shū)上說(shuō),從“符合性”到“適用性”,反映了人們?cè)趯?duì)質(zhì)量的認(rèn)識(shí)過(guò)程中,已經(jīng)開(kāi)始把顧客需求放在首要位置。但是它沒(méi)說(shuō)怎么才能做到把客戶(hù)需求放到首要位置。我看光靠文檔是堆不出來(lái)的,光考說(shuō)說(shuō)也是不行的。這個(gè)后面講,戴明同學(xué)比我講得好。
滿(mǎn)意性質(zhì)量 20世紀(jì)80年代,質(zhì)量管理進(jìn)入到TQM階段,將質(zhì)量定義為“一組固有特性滿(mǎn)足要求的程度”。它不僅包括符合標(biāo)準(zhǔn)的要求,而且以顧客及其他相關(guān)方滿(mǎn)意為衡量依據(jù),體現(xiàn)“以顧客為關(guān)注焦點(diǎn)”的原則。這個(gè)的最典型的例子是麥當(dāng)勞,他所有的店鋪從風(fēng)格到食物都保持在同一水平,使你無(wú)論在那里,都可以得到一定的購(gòu)物體驗(yàn)。也就構(gòu)成了對(duì)麥當(dāng)勞的滿(mǎn)意性質(zhì)量的驗(yàn)證。這個(gè)軟件上也是有例子的,內(nèi)舉不必親,ThoughtWorks大多數(shù)項(xiàng)目都可以達(dá)到“滿(mǎn)意性質(zhì)量”,呵呵誰(shuí)讓俺們是consultant涅。 我隱約覺(jué)得滿(mǎn)意性質(zhì)量應(yīng)該是一個(gè)過(guò)程的質(zhì)量,而不僅僅是軟件的質(zhì)量,但是目前沒(méi)有好的想法,暫且按下不表。
卓越質(zhì)量 ......下略100字。個(gè)人覺(jué)得大多數(shù)軟件還沒(méi)有達(dá)到適用性質(zhì)量,大多是過(guò)程也都沒(méi)有達(dá)到滿(mǎn)意性質(zhì)量,卓越質(zhì)量就先不說(shuō)了吧。
總之,我們大體的認(rèn)為軟件質(zhì)量主要是適用性質(zhì)量起碼是不會(huì)錯(cuò)的。那么怎么才能達(dá)到這個(gè)質(zhì)量標(biāo)準(zhǔn)涅?俺是做軟件的,質(zhì)量管理還是看看Deming同學(xué)怎么說(shuō)吧,不過(guò)他老人家的14點(diǎn)總是發(fā)生變化。我也只好斷章取義,說(shuō)說(shuō)一個(gè)敏捷開(kāi)發(fā)人員眼中的14原則:
1. 持之以恒地改進(jìn)產(chǎn)品和服務(wù) Create constancy of purpose for improvement of product and service
這個(gè)很明顯嘛,small release,快速發(fā)布,每次發(fā)布都是對(duì)產(chǎn)品的持續(xù)改進(jìn)。
2.采用新的觀念 Adopt the new philosophy
敏捷啊...
3.停止依靠大規(guī)模檢查去獲得質(zhì)量 Cease dependence on mass inspection
這個(gè)還有另一個(gè)說(shuō)法,build quality in。TDD,QA/BA全程參與,都是build quality in的好方法。
4.結(jié)束只以?xún)r(jià)格為基礎(chǔ)的采購(gòu)習(xí)慣 End the practice of awarding business on the basis of price tag alone
這個(gè)...貌似是說(shuō)請(qǐng)咨詢(xún)吧...
5.持之以恒地改進(jìn)生產(chǎn)和服務(wù)系統(tǒng) Improve constantly and forever the system of production and service
這個(gè)是敏捷過(guò)程的持續(xù)改進(jìn),對(duì)應(yīng)的實(shí)踐大家可能比較陌生——Restrospective!!!
6.實(shí)行崗位職能培訓(xùn) Institute training on the job
Pair Programming,Learning Lunch敏捷從來(lái)都不缺乏學(xué)習(xí)的機(jī)會(huì),就看你有沒(méi)有學(xué)習(xí)的動(dòng)力了。
7. 建立領(lǐng)導(dǎo)力企業(yè)管理 Institute leadership
敏捷團(tuán)隊(duì)的終極目標(biāo),自組織團(tuán)隊(duì),的管理是也。
8. 排除恐懼 Drive out fear
XP第一原則,勇氣,不要恐懼。
9. 打破部門(mén)之間的障礙 Break down barriers between staff areas
只有開(kāi)發(fā)團(tuán)隊(duì)的敏捷不是真正的敏捷,敏捷說(shuō)到底,是將軟件的供求關(guān)系從合約型轉(zhuǎn)為合作型,本來(lái)就要是大破障礙。而且障礙不打破,就很難將敏捷實(shí)施到底。這也是很多同學(xué)嘗試敏捷失敗的原因,僅僅以為敏捷是技術(shù)層面上的事情,其實(shí)不是。從這個(gè)角度來(lái)所,敏捷方法的確是深刻而震撼心靈的變革,有些人...呃...敏捷在十月...
10. 取消對(duì)員工的標(biāo)語(yǔ)訓(xùn)詞和告誡 Eliminate slogans, exhortations, and targets for the work force
恩,什么激情100天...封閉開(kāi)發(fā)...見(jiàn)鬼去吧...不過(guò)restrospective的結(jié)果是要寫(xiě)在白板上的,準(zhǔn)備時(shí)刻改進(jìn)。自我表?yè)P(yáng)和自我批評(píng),算不上訓(xùn)詞吧。
11.取消定額管理和目標(biāo)管理 Eliminate numerical quotas for the work force. Eliminate management by objectives
很多人都問(wèn)過(guò)我,pair programming了之后,技校怎么辦?嘿嘿,Deming同學(xué)已經(jīng)說(shuō)了,這樣的考核不要也罷。
12 消除打擊員工工作情感的考評(píng) Remove barriers that rob the hourly worker of his right to pride of workmanship. Remove barriers that rod people in management and in engineering of their right to pride of workmanship
敏捷團(tuán)隊(duì)的自我評(píng)價(jià)很簡(jiǎn)單,360度,由于你幾乎跟所有人都pair過(guò),如果所有人都不說(shuō)你好...這已經(jīng)是rp問(wèn)題了,就不是打擊這么簡(jiǎn)單了...
13 鼓勵(lì)學(xué)習(xí)和自我提高? Encourage education and self-improvement for everyone
同前,Pair Programming,Learning Lunch敏捷從來(lái)都不缺乏學(xué)習(xí)的機(jī)會(huì),就看你有沒(méi)有學(xué)習(xí)的動(dòng)力了。
14 采取行動(dòng)實(shí)現(xiàn)轉(zhuǎn)變 Take action to accomplish the transformation
每次restrospective之后必須定出方案,以實(shí)踐改進(jìn)。而諸位如果想實(shí)施敏捷又覺(jué)得難于說(shuō)服領(lǐng)帶,不妨拿Deming同學(xué)說(shuō)說(shuō)事,這位大老的殺傷力還是曼大的,尤其你老大是MBA的話(huà)
很長(zhǎng)時(shí)間以來(lái)我對(duì)rails框架本身沒(méi)什么興趣,因?yàn)槲覐膩?lái)都不是web fans,15歲那年我打印了一本HTML Reference準(zhǔn)備學(xué)習(xí)一下,感覺(jué)完全nonsense。很長(zhǎng)一段時(shí)間(大約有3年吧)里基本上看到Markup Language我就會(huì)大腦短路。但是我對(duì)rails背后的東西很感興趣,是什么讓rails如此有效率,一開(kāi)始我以為是Ruby DSL,但隨著做的rails項(xiàng)目越來(lái)越多,我發(fā)現(xiàn)rails的效率的根源來(lái)自它對(duì)delphi的繼承和發(fā)揚(yáng)。 1.? data centric object model Active Record delphi之所以成功,在于它看準(zhǔn)了大部分商用軟件都是數(shù)據(jù)庫(kù)核心的,并為之設(shè)計(jì)一套相應(yīng)的框架, delphi的核心就是圍繞數(shù)據(jù)庫(kù)進(jìn)行開(kāi)發(fā)(李維同學(xué)的borland傳奇里寫(xiě)道,當(dāng)時(shí)之所以起名字叫Delphi,就是因?yàn)橐猼aking to Oracle)。而rails也很好的在新時(shí)代(Web時(shí)代)把握了相似的核心點(diǎn)——content是web的核心,以及數(shù)據(jù)庫(kù)數(shù)據(jù)到content的映射是動(dòng)態(tài)web的核心。因此它沒(méi)有采用所謂的更加嚴(yán)肅的ORM技術(shù)。而是依然使用了delphi時(shí)代的active record(但是進(jìn)行了object封裝)。如下代碼示范了ruby和delphi在active record實(shí)現(xiàn)上的相似 Ruby class?Person?<?ActiveRecord::Base end
x8x?=?Person.new?:name?=>?'x8x' x8x.age?=?15 Delphi people?:?TADOTable;
 begin??? ???people.Table?=?'people'; ???people.InsertRecord('x8x');
???people.First; ???people.FieldByName('age')?:=?15; end 可以看出,Delphi的數(shù)據(jù)庫(kù)味道更濃一些,而ruby則更多使用了對(duì)象的視角(我記得在98年前后(我變成oo狂熱分子之后),在寫(xiě)delphi的時(shí)候我基本上不會(huì)直接使用Table對(duì)象了,而是做一些簡(jiǎn)單的封裝,用business object代替直接的數(shù)據(jù)庫(kù)對(duì)象。但是最后由于和delphi的ui組件結(jié)合的不好而放棄)。但是active record的pattern是一致的。 2. DB(resource)-aware UI component —— Action View Delphi另一個(gè)為人稱(chēng)道的地方是DB-Aware的UI組件,像TDBLabel, TDBMemo還有至今仍位人稱(chēng)道的TDBGrid,極大的簡(jiǎn)化了UI的開(kāi)發(fā)。不過(guò)說(shuō)到底,仍然是Delphi數(shù)據(jù)庫(kù)核心策略的延續(xù)。同樣,rails的view helper也是db核心的。text_field之類(lèi)的可以自動(dòng)感知active record的內(nèi)容和錯(cuò)誤。 <label>Name:</label>?<%=?text_field?'person',?'name'?%> 和 nameLabel?:?TDBLabel;
nameLabel.DataSource?=?peopleTable; nameLabel.Field?=?'name'; nameLabel.Label?=?'Name';
拋開(kāi)Desktop和web的差異,也可以算是大致相當(dāng)吧。 3. Simple Component Model —— Plan Object as Component Delphi是基于組件開(kāi)發(fā),并且是非常成功的一個(gè)組件模型。基本上有經(jīng)驗(yàn)的delphi程序員,都會(huì)在開(kāi)發(fā)過(guò)程中抽象幾個(gè)VCL component出來(lái)簡(jiǎn)化自己的開(kāi)發(fā)。一方面是DRY精神,另一方面Delphi簡(jiǎn)單的組件模型使得這樣做的代價(jià)非常的小。Delphi的組件基本上就是一個(gè)對(duì)象,重構(gòu)的過(guò)程中修修改改就成組件了。rails其實(shí)有類(lèi)似的機(jī)制,而且更簡(jiǎn)單直接,更符合web時(shí)代的胃口,一個(gè)對(duì)象外加一個(gè)helper就可以成為一個(gè)UI組件。與此同時(shí)rails還有另外一個(gè)天然的同盟——Ruby DSL。把以前Delphi需要用UI設(shè)計(jì)器的地方都用Ruby DSL代替了。這一點(diǎn)是我最近在用rails做曹老師華麗的RedSaga的時(shí)候推行DSL geek主義時(shí)發(fā)現(xiàn)的。比如現(xiàn)在我有這樣一個(gè)tiny DSL用以定義portlet: in controller @portlet?=?Portlet.new?do ????????????name?'administration' ????????????title?'Administration' ????????????tabs?do ????????????????Projects?:controller?=>'projects',?:action?=>?'list' ????????????end ????????end in view <%=?render_portlet?@portlet?%> 這種描述/configuration block風(fēng)格的dsl與delphi組件的初始化非常相似,或者可以說(shuō),只有語(yǔ)法上的差異而無(wú)思路上的差異(當(dāng)然delphi可以借助IDE而不是語(yǔ)言來(lái)指定這些,但是這個(gè)做法是沒(méi)有生產(chǎn)力的)。 with?Portlet?do ???Label?=? ???Name?=? . ???Tabs[0].Controller?=? ???Tabs[1].Action?=? end rails和delphi這種輕量的組件模型,使得構(gòu)建組件/復(fù)用組件級(jí)的代價(jià)極小。因此可以極大的提高開(kāi)發(fā)效率(我至今仍記得,01年前后接了一個(gè)Delphi私活,客戶(hù)要求Office XP菜單風(fēng)格,我找了一個(gè)XPMenu的控件,直接仍上去,自己的代碼一行沒(méi)改,菜單就Office XP了...)。 總之,Delphi的效率要素Rails大部分都學(xué)走了,最后簡(jiǎn)單總結(jié)一下rails在delphi基礎(chǔ)上的發(fā)揚(yáng): 1. 用ruby而不是object pascal。語(yǔ)法上更靈活簡(jiǎn)單 2. Object Model on top of ActiveRecord,比起Delphi可以跟好的使用OO開(kāi)發(fā)。 3. 組件模型更簡(jiǎn)單 4. CoC,這個(gè)就不說(shuō)了 5. expressive Ruby DSL 最后最后,說(shuō)一下Delphi for PHP,做得很華麗。但是UI部分我不是很喜歡。
I got a Sony PSP recently. After playing with it for a while, I
found out that, comparing to other handheld game console, PSP has a
fairly open platform. Some great guys had already customized a gcc
compiler for PSP, and also, a simple toolchain is provided by the
active community to support porting/development/debuging/hacking. So I
decided to port some of my favourite programming langauges to PSP. Of
course, the first one on my list is Ruby.
Cross compiling,
reading psp documents, reading ruby source code, hacking ruby source
code, cross compiling... after 3 days hard working, finally, I managed
to make the following ruby script(which is listed in the book
<Programming Ruby>) running on my PSP:
def say_goodnight(name)
"Good night, #{name}"
end
puts say_goodnight("PSP")
Bingo~~~! Ruby goes entertainment!!!!
btw : my ruby-psp patch could be found here:
https://rubyforge.org/tracker/index.php?func=detail&aid=8134&group_id=426&atid=1700
make sure you have psp-gcc and toolchain installed on you PC, and 3.03-OE/1.5 fireware on your PSP.
今天被老莊拉到JavaEye扯皮,扯來(lái)扯去還是lambda演算...本來(lái)應(yīng)承了老莊寫(xiě)lambda演算簡(jiǎn)介,不過(guò)看到磐石T1同學(xué)提到了Church number來(lái)勾引whl同學(xué)...于是我想還是寫(xiě)一些更有意思的東西吧。 每個(gè)Church number都是一個(gè)接受兩個(gè)參數(shù)的函數(shù),這兩個(gè)參數(shù)又都是函數(shù),第一個(gè)參數(shù)稱(chēng)為后繼函數(shù),第二個(gè)參數(shù)則叫做零點(diǎn)函數(shù)。依據(jù)這兩個(gè)函數(shù),我們可以定義Church number zero, one, two: (define zero? (lambda (successor?zero)?zero)) (define one (lambda (successor?zero)?(successor?zero))) (define two?? (lambda (successor?zero)?(successor?(successor?zero))))
可以看出,所謂one就是對(duì)零點(diǎn)函數(shù)應(yīng)用一次后繼函數(shù),而two則是對(duì)零點(diǎn)函數(shù)應(yīng)用后繼函數(shù)的結(jié)果再次應(yīng)用后繼函數(shù),依次類(lèi)推可以得到Church Number n。下面我們可以通過(guò)后繼函數(shù)increase和零點(diǎn)函數(shù)f(x) = 0來(lái)看看這些Church Number的計(jì)算結(jié)果: (define?(increase?x)?(+?x?1))
(zero increase 0) > 0 (one increase 0) >1 (two increase 0) >2
an approximate Java version:
public interface Function<T> { ??? T apply(Object... parameters); }
public interface ChurchNumber { ??? Integer apply(Function<Integer> successor, Function<Integer> zero); }
ChurchNumber zero = new ChurchNumber() { ?? public Integer apply(Function<Integer> successor,? Function<Integer> zero) { ????? return zero.apply(); ?? } };
ChurchNumber one = new ChurchNumber() { ?? public Integer apply(Function<Integer> successor, Function<Integer> zero) { ????? return successor.apply(zero); ?? } };
ChurchNumber two = new ChurchNumber() { ?? public Integer apply(Function<Integer> successor, Function<Integer> zero) { ? ? ? return successor.apply(successor.apply(zero)); ?? } };
Function increase = new Function<Integer>() { ?public Integer apply(Object... parameters) { ?? if (parameters[0] instanceof Function) { ????? return ((Function<Integer>) parameters[0]).apply() + 1; ?? } ?? return (Integer) parameters[0] + 1; ?} };
Function numberZero = new Function<Integer>() { ?? public Integer apply(Object... parameters) { return 0;} };
System.out.println(zero.apply(increase, numberZero)); >0 System.out.println(one.apply(increase, numberZero)); >1 System.out.println(two.apply(increase, numberZero)); >2
定義了Church number后,我們繼續(xù)定義Church number上的運(yùn)算,首先是增加1: (define?(inc?x)?(lambda (successor?zero) (successor (x successor zero))))
(define three (inc two)) (three increase 0) >3
an approximate Java version:
static ChurchNumber inc(final ChurchNumber churchNumber) { ?? return new ChurchNumber() { ????? public Integer apply(Function<Integer> successor, Function<Integer> zero) { ??? ???? return successor.apply(churchNumber.apply(successor, zero)); ??? ?? } ?? }; }
ChurchNumber three = inc(two); System.out.println(three.apply(increase, numberZero)); >3
然后是加法: (define?(add?x y)?(lambda (successor?zero)? (x successor (y successor zero))))
(define five (add three two)) (five increase 0) >5
an approximate Java version:
static ChurchNumber add(final ChurchNumber x, final ChurchNumber y) { ??? ??? return new ChurchNumber() { ??? ??? ??? public Integer apply(final Function<Integer> successor, ??? ??? ??? ??? ??? final Function<Integer> zero) { ??? ??? ??? ??? return x.apply(successor, new Function<Integer>() { ??? ??? ??? ??? ??? public Integer apply(Object... parameters) { ??? ??? ??? ??? ??? ??? return y.apply(successor, zero); ??? ??? ??? ??? ??? } ??? ??? ??? ??? }); ??? ??? ??? } ??? ??? }; }
ChurchNumber five = add(two, three); System.out.println(five.apply(increase, numberZero)); >5
最后是乘法: (define?(multiply?x?y) (lambda (successor?zero)? (x? (lambda (z) (y successor z)) zero)))
(define four (multiply two two)) (four increase 0) >4
an approximate Java version:
static ChurchNumber multiply(final ChurchNumber x, final ChurchNumber y) { ??? ??? return new ChurchNumber() { ??? ??? ??? public Integer apply(final Function<Integer> successor, ??? ??? ??? ??? ??? Function<Integer> zero) { ??? ??? ??? ??? return x.apply(new Function<Integer>() { ??? ??? ??? ??? ??? public Integer apply(final Object... parameters) { ??? ??? ??? ??? ??? ??? return y.apply(successor, new Function<Integer>() { ??? ??? ??? ??? ??? ??? ??? public Integer apply(Object... ignoredParameters) { ??? ??? ??? ??? ??? ??? ??? ??? if (parameters[0] instanceof Function) { ??? ??? ??? ??? ??? ??? ??? ??? ??? return ((Function<Integer>) parameters[0]).apply(); ??? ??? ??? ??? ??? ??? ??? ??? } ??? ??? ??? ??? ??? ??? ??? ??? return (Integer) parameters[0]; ??? ??? ??? ??? ??? ??? ??? } ??? ??? ??? ??? ??? ??? }); ??? ??? ??? ??? ??? } ??? ??? ??? ??? }, zero); ??? ??? ??? } ??? ??? }; }
ChurchNumber four = multiply(two, two); System.out.println(four.apply(increase, numberZero));
沒(méi)有減法和除法,Church當(dāng)年發(fā)明這套東西的時(shí)候就沒(méi)有。原因是非常明顯的...因此Church number只有后繼函數(shù),而沒(méi)有前驅(qū)函數(shù)。也就是說(shuō)Church number只能往前數(shù)...不能望后數(shù)...自然不可能作出減法和除法了。當(dāng)然擴(kuò)展一下也是非常容易的: (define?negative-one?(lambda?(successor?precursor?zero)?(precursor?zero))) (define?one?(lambda?(successor?precursor?zero)?(successor?zero)))
(define?(add?x?y)?(lambda?(successor?precursor?zero)?(x?successor?precursor?(?y?successor?precursor?zero)?)))
(define?(inc?x)?(+?x?1)) (define?(dec?x)?(-?x?1))
(define?zero?(add?one?negative-one))
(zero?inc?dec?0) >0
whl同學(xué)問(wèn)這樣能不能實(shí)現(xiàn)浮點(diǎn),答案是可以實(shí)現(xiàn)有限精度的浮點(diǎn)數(shù)....因?yàn)榘凑者@個(gè)思路發(fā)展下去,我們定義浮點(diǎn)的successor和precursor函數(shù)只能在有限的位數(shù)之內(nèi)...當(dāng)然有了one,zero再結(jié)合pair,模擬0/1存儲(chǔ)實(shí)現(xiàn)浮點(diǎn)也不是不可能的事情...
1. Never use Selenium FIT mode
Selenium分為兩種運(yùn)行模式,Driven Mode(現(xiàn)在叫Selenium Remote Control)和FIT Mode(現(xiàn)在叫Selenium Core)。
FIT Mode顧名思義,就是類(lèi)似FIT Testing Framework那種使用方式,主要用于QA等非技術(shù)人員編寫(xiě)Web應(yīng)用的功能測(cè)試。FIT Mode的Selenium測(cè)試使用HTML來(lái)組織測(cè)試用例。例如我要測(cè)試一個(gè)web應(yīng)用的登陸功能。我可能寫(xiě)出這樣的HTML 表格。
?1
<
table
>
?2
<
tr
>
?3
?
<
td
>
open
</
td
>
?4
????????
<
td
>
http://localhost:8080/login
</
td
>
?5
????????
<
td
></
td
>
?6
</
tr
>
?7
<
tr
>
?8
?
<
td
>
type
</
td
>
?9
????????
<
td
>
id=username
</
td
>
10
????????
<
td
>
someuser
</
td
>
11
</
tr
>
12
<
tr
>
13
?
<
td
>
type
</
td
>
14
????????
<
td
>
id=password
</
td
>
15
????????
<
td
>
password
</
td
>
16
</
tr
>
17
<
tr
>
18
?
<
td
>
click
</
td
>
19
????????
<
td
>
id=login_button
</
td
>
20
????????
<
td
></
td
>
21
</
tr
>
22
<
tr
>
23
?
<
td
>
assertTextPresent
</
td
>
24
????????
<
td
>
Welcome?to?xxxx
</
td
>
25
????????
<
td
></
td
>
26
</
tr
>
27
</
table
>
不同于FIT,Selenium內(nèi)置了一系列的命令,如上例中的open, type, click以及assertTextPresent,因此QA可以完全拋開(kāi)DEV獨(dú)立地編寫(xiě)測(cè)試(FIT需要DEV提供Behavior Fixture)。因此FIT Mode是相當(dāng)容易使用的,哪怕不會(huì)使用HTML的QA,也可以使用FrontPage畫(huà)出三列表格,依次填入數(shù)據(jù)。
然而對(duì)于大多數(shù)team而言——尤其是敏捷team,F(xiàn)IT Mode平易的外表下是令人恐懼的泥沼。大多數(shù)團(tuán)隊(duì)往往選擇使用Selenium作為功能測(cè)試和集成測(cè)試工具而不僅僅是QA測(cè)試工具,在不同的迭代間遇到功能流程或UI變化時(shí),必須要重構(gòu)Selenium測(cè)試,或者說(shuō),F(xiàn)unctional Test Migration。令人遺憾的是,HTML based的Selenium FIT Testing的重構(gòu)竟然令人難以置信的困難。我們可以使用include等Selenium FIT擴(kuò)展,使得它可以重用詳細(xì)的功能(Log in, Log out諸如此類(lèi))。即便如此,在一個(gè)真實(shí)的項(xiàng)目中,Selenium Test的數(shù)量往往在200-500之間(我目前所處的項(xiàng)目在改用Driven Mode前已達(dá)350+),對(duì)于這么大基數(shù)的Selenium測(cè)試,手工重構(gòu)幾乎是不可想象的,而目前尚沒(méi)有HTML代碼重構(gòu)工具。即便存在泛泛意義上的HTML重構(gòu)工具,對(duì)于Selenium測(cè)試重構(gòu)的有效性尚待商榷。而使用Driven Mode上述代碼可以寫(xiě)為:
1
public
?
void
?testShouldShowAWeclomeMessageAfterUserLoggedIn()?
{
2
????selenium.open(
"
http://localhost:8080/login
"
);
3
????selenium.type(
"
id=username
"
,
"
someuser
"
);
4
????selenium.type(
"
id=password
"
,?
"
password
"
);
5
????selenium.click(
"
id=login_button
"
);
6
????assertTrue(selenium.isTextPresent(
"
Welcome?to?xxxx
"
));
7
}
很自然,一個(gè)訓(xùn)練有素的程序員會(huì)重構(gòu)出如下代碼:
?1
public
?
void
?login(String?username,?String?password)?
{
?2
????selenium.open(
"
http://localhost:8080/login
"
);
?3
????selenium.type(
"
id=username
"
,username);
?4
????selenium.type(
"
id=password
"
,?password);
?5
????selenium.click(
"
id=login_button
"
);?
?6
}
?7
?8
public
?
void
?testShouldShowAWeclomeMessageAfterUserLoggedIn()?
{
?9
????login(
"
someuser
"
,?
"
password
"
);
10
????assertTrue(selenium.isTextPresent(
"
Welcome?to?xxxx
"
));
11
}
之后無(wú)論是pull up到公共基類(lèi)還是extact到Utils class都是很容易的事情。由于Java在代碼重構(gòu)上便利,Java Selenium Remote Control成為使用Selenium的最佳方式。在這一點(diǎn)上,縱使Ruby語(yǔ)法上比Java簡(jiǎn)單靈活得多,它仍不是編寫(xiě)Selenium測(cè)試的最佳載體(當(dāng)然一個(gè)經(jīng)過(guò)精心設(shè)計(jì)的ruby selenium dsl wrapper還是具有非凡的價(jià)值的,這個(gè)我們后面會(huì)涉及到)。
2. Using the name user, system, page instead of selenium
觀察上面提到的代碼,其中使用selenium來(lái)操縱web應(yīng)用的行為,這在Remote Control里是常見(jiàn)的做法,但是仍然不夠好,我們可以做一些小的變化以得到更好的測(cè)試:
?1
protected
?
void
?setup()?
{
?2
????selenium?
=
? ?
//
?intialize?selenium?instance
?3
????user?
=
?selenium;
?4
????currentPage?
=
?selenium;
?5
}
?6
?7
public
?
void
?login(String?username,?String?password)?
{
?8
????user.open(
"
http://localhost:8080/login
"
);
?9
????user.type(
"
id=username
"
,username);
10
????user.type(
"
id=password
"
,?password);
11
????user.click(
"
id=login_button
"
);?
12
}
13
14
public
?
void
?testShouldShowAWeclomeMessageAfterUserLoggedIn()?
{
15
????login(
"
some?guy
"
,?
"
password
"
);
16
????assertTrue(currentPage.isTextPresent(
"
Welcome?to?xxxx
"
));
17
}
基本上這只不過(guò)是"另一種寫(xiě)法"而已,但是它更好的表達(dá)了"用戶(hù)的行為",如login代碼所示。以及"系統(tǒng)的正確相應(yīng)",即currentPage.isTextPresent()。這種是典型的對(duì)編譯器無(wú)意義對(duì)人有意義的代碼,也就是普遍意義上好的代碼。
3. Creating a DSL base on your test codes
懂得HTML的QA可以在沒(méi)有DEV的幫助下使用Selenium FIT mode,然而卻不能在沒(méi)有DEV的幫助下使用Driven Mode。于是最自然也是最fashion的做法,就是在已有的test codes之上提供Testing DSL或者Scripting Language,讓FIT mode變得更加FIT。這方面內(nèi)容是一個(gè)更大的主題,以后再詳細(xì)展開(kāi)吧。
4. Hacking Selenium Object to support FIT command
Selenium FIT mode和RC mode下的命令有些許差異,比如FIT中的assertTextPresent,在RC中變成了isTextPresent。同樣還有FIT中最實(shí)用的命令clickAndWait,在RC中變成了click和waitForPageToLoad。在RC中使用FIT mode中的命令也非難事,找到com.thoughtworks.selenium.Selenium,添加方法:
public
?
void
?doCommand(String?commmand,?String ?parameters);
然后在com.thoughtworks.selenium.DefaultSelenium中添加實(shí)現(xiàn):
1
public
?
void
?doCommand(String?commmand,?String ?parameters)?
{
2
???String[]?paras?
=
?
new
?String[]
{
""
,
""
,
""
}
3
???
for
?(
int
?i?
=
?
0
;?i?
<
?parameters.length?
&&
?i?
<
?
3
;?i
++
)
4
??????paras[i]?
=
?parameters[i];
5
???commandProcessor.doCommand(command,?paras);
6
}
然后試驗(yàn)一下:
selenium.doCommand(
"
clickAndWait
"
);
在我們使用純RC mode之前曾經(jīng)用過(guò)一段中間方案,將rc code轉(zhuǎn)化為fit code來(lái)跑(因?yàn)閞c不支持https),由于不是真正的rc mode,像isTextPresent之類(lèi)的方法都沒(méi)有辦法使用,只能使用FIT mode command。因此如果因?yàn)橐恍┨厥獾脑?https, chrome起不來(lái),hta bug多等等),你沒(méi)有辦法使用RC mode,但是有希望得到RC可重構(gòu)的好處,那么這個(gè)tricky的技巧倒是不錯(cuò)的選擇。
5. Using chrome and IE hta lanucher to support https 6. Run test using different browser lanucher to test browser compatibility
這兩個(gè)都是和browser lanucher相關(guān)的,Selenium和JWebUnit最大的不同在于它使用真實(shí)的瀏覽器來(lái)跑測(cè)試,從而可以更加真實(shí)地考察系統(tǒng)在不同瀏覽器中的表現(xiàn)。因此使用不同的瀏覽器lanucher來(lái)運(yùn)行測(cè)試,可以更好測(cè)試應(yīng)用的瀏覽器兼容性,這對(duì)于web 2.0應(yīng)用而言是很有幫助的。此外,使用rc提供的試驗(yàn)性lanucher,chrome和hta可以解決跨domain測(cè)試和https的問(wèn)題。不過(guò)目前hta還是有很多bug的,推薦使用chrome。當(dāng)然,最希望的還是澳洲的同事可以早日在selenium里提供https支持。
摘要: 參加ThoughtWorks University的一個(gè)來(lái)月沒(méi)啥事情,閑了寫(xiě)寫(xiě)compiler玩。發(fā)現(xiàn)Lexer部分比較基礎(chǔ)也比較常用,有很多相似的東西,每次都要寫(xiě)一遍也太麻煩了,下面是我按著JSL寫(xiě)的一個(gè)common java-like lexer,對(duì)于大多數(shù)接近java語(yǔ)法的語(yǔ)言估計(jì)是夠用了。BTW:這個(gè)Lexer定義是TDD出來(lái),以通過(guò)測(cè)試為要?jiǎng)?wù),可能可讀性不太強(qiáng)。1.WhiteSpaceC... 閱讀全文
Watir doesn't work well with chinese characters. Try the following codes. ie.text_field(:name, 'some text field).set('某某') It will highlight the text field but put nothing in it. I read the Watir source codes, and found an interesting code segment: 1 for i in 0 .. value.length-1 2 sleep @ieController.typingspeed # typing speed 3 c = value[i,1] 4 #@ieController.log " adding c.chr " + c #.chr.to_s 5 @o.value = @o.value.to_s + c #c.chr 6 fire_key_events 7 end The above codes show how Watir simulates typing.If it doesn't work well with chinese characters, There must be something wrong with Ruby string. The first order of business is to figure out how Ruby string works for Chinese string. 1 chineseString = '某某' 2 puts chineseString.length 3 for i in 0..chineseString.length-1 4 puts chineseString[i, 1] 5 end result will be: 4
Does Ruby, which is now capturing all java programmers' love, use 8bit char instead of unicode? Holy fuck! I made a simple patch for the issue after I woke up from a short coma. 1 require 'Watir' 2 3 module Watir 4 module Cn 5 class IE <Watir::IE 6 def text_field(how , what=nil) 7 return TextField.new(self, how, what) 8 end 9 end 10 11 class TextField < Watir::TextField 12 def doKeyPress( value ) 13 begin 14 maxLength = @o.maxLength 15 if value.length > maxLength 16 value = suppliedValue[0 .. maxLength ] 17 @ieController.log " Supplied string is #{suppliedValue.length} chars, which exceeds the max length (#{maxLength}) of the field. Using value: #{value}" 18 end 19 rescue 20 # probably a text area - so it doesnt have a max Length 21 maxLength = -1 22 end 23 24 Cn.characters_in(value) {|c| 25 sleep @ieController.typingspeed 26 @o.value = @o.value.to_s + c 27 fire_key_events} 28 end 29 end 30 31 def Cn.characters_in(value) 32 index = 0 33 while index < value.length 34 len = value[index] > 128 ? 2 : 1 35 yield value[index, len] 36 index += len 37 end 38 end 39 end 40 end I submitted this patch to Watir tracing systsem,and zipped codes could be found here: http://rubyforge.org/tracker/index.php?func=detail&aid=3232&group_id=104&atid=489
昨天發(fā)現(xiàn)了Ruby Watir里的一個(gè)小問(wèn)題,沒(méi)有辦法在Text Field里輸入中文。雖然已經(jīng)hack了,但是還是不太爽,G.H.Hardy說(shuō): Beauty is the first
test: there is no permanent place in this world for ugly mathematics. 感動(dòng)了我好久。現(xiàn)在換個(gè)說(shuō)法: Beauty is the first
test: there is no permanent place in this world for ugly hack code. 這個(gè)問(wèn)題也不太難出理,ruby作為C的front interface在字符串處理上有很深的C的痕跡,嗯,10年前我還是個(gè)C程序員嘛,按照從前的做法區(qū)分ASCII碼。 1 def characters_in(value) 2 index = 0 3 while index < value.length 4 len = value[index] > 128 ? 2 : 1 5 yield value[index, len] 6 index += len 7 end 8 end 把TextField里的doKeyPress改一下: 1 characters_in(value) {|c| 2 sleep @ieController.typingspeed 3 @o.value = @o.value.to_s + c 4 fire_key_events} 搞定!但是還是很丑,直接把別人的code改了,contributing to eclipse里說(shuō)要contribute不要隨便change別人的代碼。好吧,好在ruby擴(kuò)展起來(lái)也不難: 1 require 'Watir' 2 3 module Watir 4 module Cn 5 class IE <Watir::IE 6 def text_field(how , what=nil) 7 return TextField.new(self, how, what) 8 end 9 end 10 11 class TextField < Watir::TextField 12 def doKeyPress( value ) 13 begin 14 maxLength = @o.maxLength 15 if value.length > maxLength 16 value = suppliedValue[0 .. maxLength ] 17 @ieController.log " Supplied string is #{suppliedValue.length} chars, which exceeds the max length (#{maxLength}) of the field. Using value: #{value}" 18 end 19 rescue 20 # probably a text area - so it doesnt have a max Length 21 maxLength = -1 22 end 23 24 Cn.characters_in(value) {|c| 25 sleep @ieController.typingspeed 26 @o.value = @o.value.to_s + c 27 fire_key_events} 28 end 29 end 30 31 def Cn.characters_in(value) 32 index = 0 33 while index < value.length 34 len = value[index] > 128 ? 2 : 1 35 yield value[index, len] 36 index += len 37 end 38 end 39 end 40 end 測(cè)試一下: require 'watir-cn'
ie = Watir::Cn::IE.start('http://www.google.com') ie.text_field(:name, 'q').set('Ruby Watir 功能測(cè)試' 成功。最后一步是貢獻(xiàn)社區(qū),直接登到rubyforge,找到Watir然后submit了兩個(gè)patch:一個(gè)直接修改watir庫(kù)的一個(gè)是獨(dú)立的watir-cn的。推薦大家使用第二個(gè)的patch。地址在: http://rubyforge.org/tracker/index.php?func=detail&aid=3232&group_id=104&atid=489
今天我和WPC被迫派給了一個(gè)很nonsense的活,客戶(hù)給了我們兩份名單,讓我們對(duì)照我們SSO中的用戶(hù)數(shù)據(jù)做一下數(shù)據(jù)維護(hù),如果有的用戶(hù)在SSO中沒(méi)有就加進(jìn)來(lái);要是SSO中有,看一下OA里有沒(méi)有,如果OA里沒(méi)有寫(xiě)一個(gè)列表讓OA的同志們?nèi)ゾS護(hù)數(shù)據(jù)。本來(lái)是一個(gè)很枯燥的活,好在WPC和我都是pragmatic programers,于是生活變得有樂(lè)趣多了。 解決這個(gè)問(wèn)題最直接的做法,就是login到SSO平臺(tái)上,然后一個(gè)用戶(hù)一個(gè)用戶(hù)的search,search完了再用OA Admin登陸查OA帳戶(hù)。我們是pragmatic programmer嘛,這么繁瑣的活動(dòng)自然寫(xiě)程序搞定它。自然浮現(xiàn)兩個(gè)選擇:Ruby Watri,還有就是產(chǎn)自俺們公司的Selenium Script。 上來(lái)先用Ruby Watri,這個(gè)東西好啊,簡(jiǎn)單啊。WPC找了一個(gè)以前寫(xiě)的example, 我照著改了一個(gè)用戶(hù)的search,然后擴(kuò)展:
1 # login in as sso admin 2 ie = Watir::IE.start(sso_login_url) 3 ie.text_field(:name,"username").set(sso_admin_user) 4 ie.text_field(:name,"password").set(sso_admin_pass) 5 ie.button(:value, "登錄").click 6 7 # search user 8 ie = Watir::IE.start(sso_search_url) 9 ie.text_field(:name, "userName).set('張三') 10 ie.button(:value, "查找").click 跑到command line run一把,ruby login.rb,然后一個(gè)古怪的事情出現(xiàn)了
ie.text_field(:name, "userName).set('張三') userName輸入框highlight了一下,然后沒(méi)有字...難道是編碼問(wèn)題?換了encoding重新save了一把,結(jié)果一樣。郁悶...于是我和WPC想法是...Ruby中文有問(wèn)題,不管了時(shí)間緊迫,換Selenium Test,自家的東西嘛。但是Selenium的Script是HTML-based的,寫(xiě)起來(lái)太麻煩。我們是pragmatic programmer嘛,這么繁瑣的活動(dòng)自然寫(xiě)程序搞定它。于是我來(lái)搞一個(gè)ScriptGenerator,WPC同志搞script template。一搞template WPC同志就比較興奮,大喊:velocity! velocity!哎,我機(jī)器上沒(méi)有velocity的library,于是我決定pragmatic一把,直接writer output。找了一個(gè)Selenume Script Demo,在每行前面加上aaaa,每行末尾加上bbb,然后ctrl + f,aaa->writer.write(" bbb->"); 改幾個(gè)",introduce parameter, extract method, compose method飛快地重構(gòu)之,一個(gè)hard code的generator引擎誕生了。WPC還在調(diào)template,我看了一下代碼,蠻ugly的,refactory之:
1 private static String scriptTemplate; 2 3 public static void readScriptTemplate(String templateName) { 4 BufferedReader reader = null; 5 try { 6 reader = new BufferedReader(new FileReader(templateName)); 7 String line = null; 8 StringBuffer template = new StringBuffer(); 9 while ((line = reader.readLine()) != null) 10 template.append(line).append("\n"); 11 scriptTemplate = template.toString(); 12 } catch (IOException e) { 13 14 } finally { 15 if (reader != null) 16 try { 17 reader.close(); 18 } catch (IOException e) { 19 } 20 } 21 } 22 23 public static void generatedScriptForUser(String path, String name) { 24 Writer writer = null; 25 try { 26 writer = new BufferedWriter(new FileWriter(path + "/" + name 27 + ".html")); 28 writer.write(scriptTemplate 29 .replaceAll("\\$\\$userName\\$\\$", name)); 30 } catch (IOException e) { 31 e.printStackTrace(); 32 } finally { 33 if (writer != null) 34 try { 35 writer.close(); 36 } catch (IOException e) { 37 } 38 } 39 40 } 一下子少了無(wú)數(shù)代碼,爽多了。然后wpc也搞好了template,按模版文件一generating,幾十個(gè)selenium test script就出現(xiàn)了。嗯,write program that write program,有夠pragmatic。 寫(xiě)了一個(gè)Test Suite,放到改一下Selenium Runner下跑一下又傻眼了。Selenium做的Functional Test,一般假定和被測(cè)的應(yīng)用在一個(gè)URL base里,因此這樣local“測(cè)”remoting就不太好,而且我們又是一個(gè)安全平臺(tái),URL base做安全基準(zhǔn)的。一下就所有測(cè)試就crackdown在這里了。郁悶啊... Selenium文檔,發(fā)現(xiàn)可以用driver來(lái)adpater local和remoting的環(huán)境,問(wèn)題是這個(gè)drvier要自己寫(xiě)...郁悶... WPC在firefox上裝了一個(gè)Selenium Recorder的plug in可以記錄web page actions,然后replay。他發(fā)現(xiàn)這個(gè)東西能繞過(guò)Selenium的限制,于是決定看看他怎么實(shí)現(xiàn)的,找到code一看...原來(lái)是把selenium runner hack了...用javascript把url base生生的給改了...WPC說(shuō)好啊,我們寫(xiě)一個(gè)Firefox Selenium Recorder Plugin的plug in吧,讓他從一個(gè)目錄里自動(dòng)load script...然后WPC開(kāi)始玩firefox plugin. 我決得我還是看看Ruby的中文支持吧,找來(lái)找去都沒(méi)有說(shuō)Ruby的中文有問(wèn)題的,后來(lái)發(fā)現(xiàn)一個(gè)老大測(cè)了一下Ruby的中文字符串,說(shuō)沒(méi)問(wèn)題。我忘了這個(gè)老大的URL了找到再補(bǔ)上。代碼上看起來(lái)似乎也沒(méi)什么問(wèn)題。于是我懷疑是Watri的問(wèn)題,看Watri的代碼,發(fā)現(xiàn)Watri的設(shè)計(jì)思路就是為了模擬人的錄入,然后找到這樣的代碼:
def doKeyPress(value)

for i in 0 .. value.length-1 sleep @ieController.typingspeed # typing speed c = value[i,1] #@ieController.log " adding c.chr " + c #.chr.to_s @o.value = @o.value.to_s + c #c.chr fire_key_events end

end 根據(jù)設(shè)定的延時(shí)模擬人敲擊鍵盤(pán)。每一個(gè)間隔用String slice來(lái)輸入。于是我試驗(yàn)了一下ruby的中文字符串切片:
1 value = "哈哈" 2 for i in 0..value.length-1 3 puts value[i,1] 4 end Ruby果然瞎菜了...value.length是4,每一個(gè)切片都是空...啊~~~~天啊,8bit char...C的時(shí)代啊。找到了問(wèn)題就好辦了,我權(quán)衡了fix watri unicode和直接hack它,最后我選擇直接hack它,方法簡(jiǎn)單:
1 if @ieController.typingspeed != -1 2 for i in 0 .. value.length-1 3 sleep @ieController.typingspeed # typing speed 4 c = value[i,1] 5 #@ieController.log " adding c.chr " + c #.chr.to_s 6 @o.value = @o.value.to_s + c #c.chr 7 fire_key_events 8 end 9 else 10 @o.value = value 11 fire_key_events 12 end 然后測(cè)試一下:
1 require 'Watir' 2 3 ie = Watir::IE.start("http://www.google.com") 4 ie.typingspeed = -1 5 ie.text_field(:name, "q").set("哈哈") 搞定。于是準(zhǔn)備改Ruby腳本,這個(gè)時(shí)候客戶(hù)下班了,我們決定明天繼續(xù),一共用時(shí)2小時(shí)... 最后說(shuō)一下需求,一共有多少數(shù)據(jù)呢?70條...如果pair錄入的話(huà),大約40-50分鐘吧 結(jié)論: 1.Pragmatic Programmer都是很懶的,重復(fù)5次的工作都回用代碼來(lái)寫(xiě)。 2.Pragmatic Programmer都是很有好奇心的,太多的重復(fù)性勞動(dòng)只能分散他們的注意力,不知道會(huì)搞出什么了,我估計(jì)我要沒(méi)有hack Watri,WPC已經(jīng)開(kāi)始寫(xiě)Firefox plugin了。 3.Pragmatic Programmer都是“古程序員”,寫(xiě)程序操縱計(jì)算機(jī)是很有樂(lè)趣的。 4.比一個(gè)Pragmatic Programmer更能折騰的是兩個(gè)Pragmatic Programmer...
|
|
隨筆:36
文章:0
評(píng)論:93
引用:0
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|
25 | 26 | 27 | 28 | 29 | 30 | 31 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 1 | 2 | 3 | 4 |
|
常用鏈接
留言簿(21)
隨筆分類(lèi)
隨筆檔案
搜索
最新評(píng)論

閱讀排行榜
評(píng)論排行榜
|
|