RubyKoans: 透過單元測試來學習 Ruby 語法

rubykoans.com/EdgeCase 所推出的 Ruby 學習工具。它的特別之處在於它是用單元測試填空題的方式來認識Ruby語法。要執行很簡單,下載之後,輸入 rake 就會開始跑測試了(如何安裝Ruby請參考這裡),例如以下的測試:


  def test_arrays_and_ranges
    assert_equal __, (1..5).class
    assert_not_equal Array, (1..5)
    assert_equal __, (1..5).to_a
    assert_equal __, (1...5).to_a
  end

其中 __ 的地方就是你要解答的部份,因為是空的,所以顯然測試不會通過(Red),它會提示你那一行出錯,然後實際的值是什麼:


~/koans] (master) .rvm-$ rake
/Users/ihower/.rvm/rubies/ruby-1.9.2-p290/bin/ruby path_to_enlightenment.rb
AboutArrays#test_arrays_and_ranges has damaged your karma.

The Master says:
  You have not yet reached enlightenment.
  You are progressing. Excellent. 20 completed.

The answers you seek...
  <"FILL ME IN"> expected but was  .

Please meditate on the following code:
  /Users/ihower/koans/about_arrays.rb:48:in `test_arrays_and_ranges'

mountains are merely mountains
your path thus far [...X______________________________________________] 20/274

當你填入答案之後,就會通過進到下一個測試。你可以看到總共有274題,要寫完也是要花點時間。這模式有趣吧,練完之後應該會對Ruby語法有扎實的認識。

BDD style unit testing video and slides@RubyKaigi 2011

This is my slides and video recording at RubyKaigi 2011 (with speaker’s note every page). It’s my first English presentation and the time is 30mins, so I only talk about the core value of BDD, basic BDD style syntax, why the syntax matters and some BDD caveats you should know and think.

I’m such nervous and may speak too quickly (only use 25 mins). I asked all attendees and found surprisingly more than half attendees do unit testing and RSpec, which means most attendees already knows unit testing and RSpec. Great! Now I’m afraid of my talk will be too easy :P Anyway, hope this talk can help you realize why we do BDD unit testing and not only because RSpec’s awesome syntax.

BTW, If you’re interested in more RSpec syntax, you can checkout my RSpec talk at OSDC.TW.

這是我在日本 RubyKaigi 2011 的演講錄影跟投影片(包括講稿),因為是第一次用英文演講,加上時間只有三十分鐘,所以我只講了BDD最重要的核心概念、最基本的 BDD 風格語法、為何 Syntax 會影響你思考以及一些你應該要知道及思考的 BDD 副作用。

因為用英文講太緊張了,所以一開始講的好像太快了,導致只花了25分鐘,還留了5分鐘沒用完。開講前還問了聽眾有多少人有做單元測試,意外發現超過一半以上舉手,而且大部分都是使用 RSpec。真是太棒了,讓我心底馬上os那我還需要講嗎? XD 無論如何,希望這演講還是可以讓你了解到 BDD 單元測試的核心價值,而不只是因為 RSpec 的 awesome 語法而已。

如果你有興趣看更多 RSpec,可以參考我之前在 OSDC.TW 的 RSpec 演講。

兩個 Ruby 建構 Array 和 Hash 的小技巧

Array

我想你可能寫過以下這樣的程式。其中 params[:a] 可以只有一個元素,也可以是陣列。但是為了接下來能夠處理,我們需要轉成陣列 array 變數:


array = (params[:a].is_a? Array)? params[:a] : [params[:a]]

這裡我們手動判斷了 params[:a] 是不是陣列,實在是有點 ugly。其實 Ruby 內建的 API 就可以支援下述寫法:


array = Array(params[:a])

無論 params[:a] 是陣列還是單一元素,Array(params[:a])會確保出來一定是陣列。

Hash

你有一個物件或是陣列,你想要轉成 Hash,最常見基本的作法會是先初始一個 hash,然後迭代設定它:


hash = {}
data.each { |d| hash[d.foo] =  d.bar }

高級一點的,也許會思考怎樣寫成一行,然後想到用 inject:


hash = data.inject({}) { |h,d| h[d.foo] =  d.bar; h }

不過,這裡我要介紹一種我的最愛:


hash = Hash[ data.map {|d| [d.foo, d.bar]} ]

Hash[]是一個Ruby內建的API可以把陣列轉成Hash,而且效能非常好,比前兩個方法都好。inject想當然是最慢的,我最不推薦使用。

有人跟我抱怨Hash[]有點 magic 可讀性不佳。可是啊,這是 Ruby “原生”的 Hash API,一點都不 magic。你不知道看不懂跟抱怨程式碼可讀性不佳,我個人認為是兩件事情哩。

BDD style Unit Testing@RubyKaigi 2011

RubyKaigi 是日本的 Ruby 年度大會。2009 年第一次去日本參加時,非常興奮能在 Ruby 發源地與全場四五百位 Rubyist 一起聽 Ruby 發明人 Matz 演講,現場歡聲雷動。當時就發願有機會也要上台分享。今年第二次投稿終於獲選了啦,我的題目是 BDD style Unit Testing,挑戰生平第一次英文演講。

<- Speaker 專用 Badge,爽!! It's awesome! 三天的議程在此,目前開放報名中。

如何處理不同版本的 Gem 執行檔,以 Rake 0.9.0 地雷為例

Update: Rake 0.9.1 回復了這個不相容變更,改成加上 Deprecation 警告:Global access to Rake DSL methods is deprecated. Please Include … Rake::DSL into classes and modules which use the Rake DSL methods.”。

前一陣子 Rake 0.9.0 發佈了,結果讓大家雞飛狗跳,因為他有個不向後相容的 API 更動。


## Version 0.9.0

* *Incompatible* *change*: Rake DSL commands ('task', 'file', etc.) are
no longer private methods in Object. If you need to call 'task :xzy' inside
your class, include Rake::DSL into the class. The DSL is still available at
the top level scope (via the top level object which extends Rake::DSL).

所以一旦你 gem install rake 升級到 0.9.0,那麼打 rake 就是用這個的版本,碰上你的專案不相容的話(例如 Rails 3.0.7 之前版本不相容),可以怎麼辦?

1. 移除 Rake 0.9.0 回到 0.8.7,但是常常會在不同環境下又不小心裝到,然後又爛掉了,是個不太可靠的方法 XD

2. 如果有用 Bundler 的話,你可以在 Gemfile 中指定 gem 'rake', '0.8.7',然後每次打 bundle exec rake 就是用 0.8.7 的版本了。BTW,如果指定了 0.8.7 還打 rake 的話,你會看到以下錯誤:


rake aborted!
You have already activated rake 0.9.0,
but your Gemfile requires rake 0.8.7.
Consider using bundle exec.

(See full trace by running task with --trace)

其實 Rails developer 應該都用 Bundler 了,而且也知道 bundler exec 的用途,但是直到這次事件前大家都沒習慣指定 rake 版本,因為 0.8.7 大家用了兩年都沒事啊,所以都習慣打 rake 而不是 bundle exec rake

2-1. Bundler 有個功能是 bundle install --binstubs,這會建立一個 bin 目錄包含所有 Gemfile 裡面用的執行檔。所以改打 bin/rake 即可。

2-2. 承上,嫌 bin/rake 還是太麻煩? 如果你有用 RVM 的話,在專案目錄下放個 .rvmrc 加上 export PATH="./bin:$PATH",這樣又回到只要輸入 rake 即可。( .rvmrc 的主要用途是指定此專案使用的 Ruby 版本,例如 rvm ree)

3. 試試 Rage,這是一個 script 檢查目錄下有沒有 Gemfile,有的話用 bundle exec rake,沒有的話用 rake

3-1. 類似的方法還有 Automating bundle exec

參考資料