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

參考資料

Rails 3.1 RC 發佈: 重點導覽

Update(2011/8/31): 正式釋出! Rails 3.1.0 has been released!

來整理一篇 Rails 3.1 重點,花了點時間看過一遍 Changelogs (TL;DR;)。

新的預設 jQuery

jQuery: New Default: 官方的說明

jQuery 取代 prototype.js 成為新的預設 JavaScript 函式庫。另外,RJS 也從核心中被移出成為 Plugin。還記得當年令人興奮的 RJS 功能,可以很方便的寫簡單的 Ajax 效果,如今又回到直接寫 JavaScript 才是王道。

Assets pipeline

RailsConf 2011, David Heinemeier Hansson: 今年 DHH 在 RailsConf 的演講,重點就在介紹 Assets pipeline。

靜態檔案成為一級公民了,Rails 3.1 透過 Sprockets 打包和壓縮靜態檔案,也支援編譯 SassCoffeeScript。細節請參考我書的 Assets 一章

將 CoffeeScript 納入預設有不少爭議,畢竟用 CoffeeScript 實在又多了學習負擔,不是這麼多人喜歡。不過它其實只是 Gemfile 預設載入的一行而已,如果不用拿掉即可。

HTTP Streaming

HTTP Streaming 功能是 Rails 3.1 的一個實驗性突破,可以在 render 樣板的同時,就同時開始下載資料來節省時間。原理是 HTTP 1.1 協定中的 Chunked transfer encoding 傳輸機制。我推薦可以看看 Facebook 的 BigPipe 應用其實就是這個技術。

這功能預設是關閉的,而且也不是打開設定就馬上可以享受到。因為 render 的流程不同了,本來可以先 render template 再套 layout 的,用 streaming 的話是改從 Layout 先開始,由上往下一行一吐出結果就丟給瀏覽器,因此需要搭配注意的細節不少,像是要用 Ruby 1.9、網頁伺服器要支援、template 中不能讀取 layout 中的變數、使用 provide helper 取代 content_for、Controller 的操作必須盡可能的改成 Lazy method (也就是真正耗時的動作得實際發生在 View 中,例如 SQL query,不然時間卡在 Action 裡沒進到 View 就沒辦法提早傳結果出去啦。好加在 ActiveRecord 的 Query 都已經是 Lazy method 了),最後所有你用到的 Rack Middleware 層都必須支援 HTTP Streaming,萬一其中有 Middleware 需要等 response 完整的 body 內容才能處理,那麼就會卡住而失去 Streaming 提早送資料給瀏覽器的意義。

Migration

Reversible migration:新的 change 方法可以不需要分開寫 up 跟 down 了,Rails 會自動對應出 down 的操作,猜不出來的時候才會要你補寫 down 方法。

還有,Migrations 內統一改用 Instance methods 而不是 class methods 了,寫 def up 和 def down 即可,不需要 def self.up 和 def self.down。

Rack::Cache

加入了 Rack-cache middleware ,搭配 #expires_in, #fresh_when, #stale with :public => true 等方法,並使用你設定的 Caching store 來存放資料 (例如memcached)。

注意到因為有了 Rack::Cache,原本 Controller 的 Page caching 被移除不需要了。

採用 Rack-caching 真正的重點倒不是在於增加效能,而是在於它讓你無痛就開始寫符合 HTTP caching 標準協定的程式,讓你之後有流量需求時,可以很彈性地轉換到真正的 HTTP caching 伺服器上,例如 Varnish。詳情請參考Rack-cache FAQ

Mountable Engines

Engine 被改的更為獨立,可以擁有獨立的 routes 和 namespace。請參考 API 文件,或是這篇簡介 Mountable engines in Rails 3.1 beta: getting started

ActiveRecord/ActiveModel 相關

  • 採用 SQL Prepared statements 了,增加複雜 SQL query 的效能。
  • Mass assignment 可以設定 attr_proected 和 attr_accessible 因應不同角色而有不同限制,挺不錯實用的功能。詳見 API 文件
  • Custom ActiveRecord Attribute Serialization: 支援不同的 Serialize 方式
  • ActiveRecord Identity Map:支援 Identity Map,相同的資料庫資料也一定會是同一個物件,可以節省資源增加一致性。不過因為行為有些改變,所以預設是關的,詳見
    API 文件
  • ActiveModel::SecurePassword,可以存密碼, 不過我覺得沒啥實用,大家都裝認證相關的 Gem 來用
  • 新的 update_column 方法,這會完全忽略 validations 和 callbacks。本來的 update_attribute 雖然會忽略 validations,但是會跑 callbacks。
  • ActionPack 相關

    • Template Inheritance: Controller 繼承也可以讓 Template 也繼承
    • force_ssl: 內建了 Rack::SSL middleware 可以限制整站必須是 SSL 存取。或是在 Controller 中用 force_ssl 這個方法限定部分 Actions 要用 SSL (之前大家是裝 ssl_requirement 這個 plugin。
    • ActionController::ParamsWrapper: 這功能讓 POST 可以直接傳 JSON 和 XML 資料並自動轉成 params hash 讓你使用,可以很方便寫出一致的 JSON/XML API 介面:吐資料和接收資料都用 JSON/XML。這功能預設是關的。

    ActionView 相關

    • form_for 的 :method 參數可以不必放 :html 裡面:也就是 form_for(@post, :html => { :method => :delete }) 變成 form_for(@post, :method => :delete)
    • 如果 form 裡面有用 file_field 檔案上傳,Rails 會自動幫 form_for 加上 :multipart => true 參數,這樣就不怕忘記啦,超棒。
    • 新增 j() helper 是 escape_javascript() 的別名
    • 新增 data 參數方便設定 HTML5 data-* 屬性:
      tag("div", :data => {:name => 'Stephen', :city_state => %w(Chicago IL)}) 會輸出 <div data-name="Stephen" data-city-state="["Chicago","IL"]" />

    其他

    • 在 rails console 中,改成預設會顯示 SQL query log。
    • CSS Sprites: DHH 去年 RailsConf 講過要有,很抱歉這次 3.1 沒有喔!! 這張票還沒兌現。

    Rails 3.2 ?

    展望未來,我想 Rails 有兩大開發重點方向:

    支援 Rich clients 的 API 介面

    @DHH 的 37Signals 用了 backbone.js 來做 Basecamp mobile 版。而 @wycats (Rails3 架構師) 加入了 SproutCore 團隊,今年他在 RailsConf 的演講 Building Rails Apps for the Rich Client 就在研究他們如何在 Rails 上設計 API 搭配 SproutCore。

    果然,這一篇 Rails core team 成員的
    A New World of Resources 透漏了他們打算從乏人問津的 ActiveResource 下手。看來設計出一套給 Rich clients (例如 JavaScript MVC 框架) 的 API Convention 慣例,會是下一個大目標。

    Middleware 大改造

    看完 RailsConf 2011: Aaron Patterson 的演講,你會發現要做 HTTP streaming 真的很難,所有 Rack Middleware 都要重新看過,因此 @tenderlove 提出了新的 Rack 介面計畫想要改寫。另外就是他發現 Rails 2 到 Rails 3 的 RPS 效能變差的主因是太多 Middleware 造成 call stack 太深了。Rails 2.3 有 51 deep、Rails 3.0 有 60 deep,到了 3.1 達到 67 deep。這也是他想要改進的地方。增加效能、節省記憶體使用量是 Rails 3.2 的一大目標。