SlideShare a Scribd company logo
1 of 149
Download to read offline
前言:
               1.這是一場關於軟體單元測試的演講
 2. RSpec 是一套發揮 Ruby 程式語言特性的改良版 XUnit 測試框架
                  3. 本演講沒有 Ruby on Rails
           4. 適合聽眾:你有寫 Ruby 或你對測試有興趣




RSpec 讓你愛上寫測試
             ihower@gmail.com
            2011/3 at OSDC.TW
About Me
•   張文鈿 a.k.a. ihower

    •   http://ihower.tw

    •   http://twitter.com/ihower

•   Lead Software Developer at OptimisDev Taiwan

•   Ruby Developer since 2006

•   The Organizer of Ruby Taiwan Community

    •   http://ruby.tw

    •   http://rubyconf.tw
Agenda

• 什麼是測試
• RSpec
• RSpec::Mocks
• Some useful RSpec tools
• 怎樣寫好測試
什麼是測試?
RSpec 讓你愛上寫測試
Sorry, I’m not QA guy :/
測試 Overview
單元測試                     Verification
                   確認程式的執行結果有沒有對?
  Unit Test        Are we writing the code right?



   整合測試
Integration Test

  驗收測試                      Validation
                   檢查這軟體有滿足用戶需求嗎?
Acceptance Test    Are we writing the right software?
單元測試             測試個別的類別和函式結果正確
  Unit Test

   整合測試            測試多個類別之間的互動正確
Integration Test

  驗收測試              從用戶觀點測試整個軟體
Acceptance Test
單元測試
                   白箱測試
  Unit Test

   整合測試
Integration Test

  驗收測試
                   黑箱測試
Acceptance Test
單元測試
                   Developer 開發者
  Unit Test

   整合測試
Integration Test

  驗收測試
                    QA 測試人員
Acceptance Test
Verification
確認程式的執行結果有沒有對?
Are we writing the code right?
我們天天都在檢查程
 式的結果有沒有對
• 程式寫完後,手動執行看看結果對不
 對、打開瀏覽器看看結果對不對? (如果你是
 web developer 的話)


• 聽說有人只檢查 syntax 語法沒錯就
 commit 程式的?
手動測試很沒效率
特別是複雜或 dependency 較多的程式,你可能會偷
      懶沒測試所有的執行路徑
有三種路徑,你得執行(手動測
  試)三次才能完整檢查
  def correct_year_month(year, month)

    if month > 12
      if month % 12 == 0
         year += (month - 12) / 12
         month = 12
      else
         year += month / 12
         month = month % 12
     end
    end

    return [year, month]

  end
用介面手動測試?
用 Console 介面手動測?
自動化測試
讓程式去測試程式
1. Instant Feedback
      寫好測試後,很快就可以知道程式有沒有寫對

it "should return correct year and month" do

  correct_year_month(2011, 3).should == [2011, 3]
  correct_year_month(2011, 14).should == [2012,2]
  correct_year_month(2011, 24).should == [2012,12]

end
寫測試的時間
    小於
debug 除錯的時間
2. 回歸測試及重構

• 隨著程式越寫越多,新加的程式或修
 改,會不會搞爛現有的功能?

• 重構的時候,會不會造成破壞?
• 如果有之前寫好的測試程式,那們就可
 以幫助我們檢查。
3. 幫助你設計 API
• 寫測試的最佳時機點:先寫測試再實作
• TDD (Test-driven development)
 • Red-Green-Refactor 流程
• 從呼叫者的角度去看待程式,關注介
 面,協助設計出好用的 API。
4. 一種程式文件


• 不知道程式的 API 怎麼呼叫使用?
• 查測試程式怎麼寫的,就知道了。
小結

• 你的程式不 Trivial -> 寫測試節省開發時間
• 你的程式不是用過即丟 -> 回歸測試
• TDD -> 設計出可測試,更好的 API 介面
• API 怎麼用 -> 測試也是一種文件
怎麼寫測試?

xUnit Framework
xUnit Framework
      每個程式語言都有這樣的框架


• test case 依序執行各個 test :
 • Setup 建立初始狀態
 • Exercise 執行要測的程式
 • Verify (assertion)驗證狀態如預期
 • Teardown 結束清理資料
今天的案例

• 訂單 Order
 • 狀態 status
 • 依照 user 是否是 vip,計算總價   amount

 • 運送 ship!
Ruby 的 Test::Unit
class OrderTest < Test::Unit::TestCase

  def setup
    @order = Order.new                     Method 的命名是
  end                                          個麻煩
  def test_order_status_when_initialized
    assert_equal @order.status, "New"
  end

  def test_order_amount_when_initialized
    assert_equal @order.amount, 0
  end

end
今天的主題 RSpec 寫法
   describe Order do
     before do
       @order = Order.new
     end

     context "when initialized" do
       it "should have default status is New" do
         @order.status.should == "New"
       end

       it "should have default amount is 0" do
         @order.amount.should == 0
       end
     end
   end
今天的主題 RSpec 寫法
                 (行家版)


   describe Order do
     context "when initialized" do

       its(:status) { should == "New" }
       its(:amount) { should == 0 }

     end
   end
RSpec 第一印象

• 語法 Syntax 不同
 • 程式更好讀
 • 更像一種 spec 文件
RSpec
• RSpec 是一種 Ruby 的測試 DSL
 (Domain-specific language)


 •   Semantic Code:比 Test::Unit 更好讀,寫的人
     更容易描述測試目的

 •   Self-documenting:可執行的規格文件

• 非常多的 Ruby on Rails 專案採用 RSpec
 作為測試框架
RSpec (cont.)

• 不是全新的測試方法論
• 是一種改良版的 xUnit Framework
• 演進自 TDD,改稱 BDD (Behavior-driven
 development)
BDD 與 TDD

• TDD 用 test 思維,測試程式的結果
• BDD 用 spec 思維,描述程式應該有
 什麼行為
Live demo (偽)
red/green/refactor 流程
Syntax Matters!
     Why?
語言與思考
           http://esse_tsyo.blogspot.com/2010/09/blog-post_17.html
          http://www.nytimes.com/2010/08/29/magazine/29language-t.html




          • 語言是表達工具,但往往也是思考盲點
          • 語言會影響你的思考,讓你習慣性的用
               某種特定的方式去思考
                                                                   祕魯原住民的語言也相當有
                                           澳洲原住民的方向指示一律            特色。他們的文法嚴格要求
與中文相較,英文的文法強        歐陸幾個主要語系把「性            是用絕對的「東南西北」。            他們在陳述一件事情,必須
迫使用「時態」,也會使聽        別」加諸在所有的名詞上,           實驗者準備了兩間佈置裝潢            用不同的動詞形態來區分事
的人知道這件事情是發生在        而且各語言都有所不同,這           一模一樣的房間讓受試者參            實(正看著)、推論(有線
過去,現在,或未來。          會使得各種母語的人對於不           觀,一間朝北,一間朝南。            索)、和臆測(用猜的),否則
                    同的物體有不同的情感連            絕大部份的受試者都表示他            會被當作是說謊。因此,如
                    結。例如,「椅子」在德文           們參觀了同一個房間兩次,            果你問一個 Matses 族的男
                    裡是男的,但是在西班牙文           只有 Guugu Yimithirr 母語   性他有幾個妻子,除非他的
                    裡是女的。                  的受試者表示他們「參觀了            妻子都在現場,否則他只能
                                           兩間長得一樣的房間」。他            用「過去式」回答:「上次
                                           的語系可以讓他們察覺是兩            我看到的時候是兩個」,即
                                           間不同的房間。                 使這個「上次」是僅僅 5分
Syntax Matters!
    用正確的語法描述程式應該要有什麼行為

def test_order_status_when_initialized
  assert_equal @order.status, "New"
end

# v.s.

it "should have default status is New" do
  @order.status.should == "New"
end

                                         可讀性大勝
Learn RSpec
• syntax
• syntax
• syntax
• more syntax
describe 和 context
 幫助你組織分類
要測的東西是什麼?
  describe Order do
    # ...
  end                     通常是一個
                           類別
  # 或是

  describe "A Order" do
    # ...
  end
可以 Nested 加入想要測試的
      方法是哪個
    describe Order do

      describe "#amount" do
          # ...
      end                        通常開頭用 # 表示
                                   instance method
                              dot 開頭表示 class method
    end
可以再 nested 加入不同情境
describe Order do

  describe "#amount" do
    context "when user is vip" do
     # ...
    end

   context "when user is not vip" do
      # ...
    end
  end

end
每個 it 就是一小段測試
 Assertions 又叫作 Expectations
加入 it
describe Order do

  describe "#amount" do
    context "when user is vip" do
      it "should discount five percent if total > 1000" do
        # ...
      end

        it "should discount ten percent if total > 10000" do
          # ...
        end
      end

   context "when user is not vip" do
      it "should discount three percent if total > 10000" do
        # ...
      end
    end
  end

end
should, should_not
所有物件都有這個方法來定義你的期望
describe Order do

  describe "#amount" do
    context "when user is vip" do

      it "should discount five percent if total >= 1000" do
        user = User.new( :is_vip => true )
        order = Order.new( :user => user, :total => 2000 )
        order.amount.should == 1900
      end

      it "should discount ten percent if total >= 10000" { ... }
    end
    context "when user is vip" { ... }
  end

end
輸出結果(red)
(狀態顯示為在寫 Order 主程式)
輸出結果(green)



        漂亮的程式文件
before 和 after
• 如同 xUnit 框架的 setup 和 teardown
• before(:each) 每段 it 之前執行
• before(:all) 整段 describe 執行一次
• after(:each)
• afte(:all)
describe Order do

  describe "#amount" do
    context "when user is vip" do

      before(:each) do
        @user = User.new( :is_vip => true )
        @order = Order.new( :user => @user )
      end

      it "should discount five percent if total >= 1000" do
        @order.total = 2000
        @order.amount.should == 1900
      end

      it "should discount ten percent if total >= 10000" do
        @order.total = 10000
        @order.amount.should == 9000
      end

    end
    context "when user is vip" { ... }
  end

end
pending
可以先列出來打算要寫的測試

describe Order do

  describe "#paid?" do
    it "should be false if status is new"

    it "should be true if status is paid or shipping" do
      pending
    end
  end

end
RSpec 讓你愛上寫測試
Syntax matters!
let(:name) { exp }
• Lazy,有需要才會運算,並且是 Memoized。
• 相較於 before(:each) 增加執行速度
• 不需用 instance variable 放 before
• 增加可讀性
• let! 則是非 lazy 版本
describe Order do

  describe "#amount" do
    context "when user is vip" do

      let(:user) { User.new( :is_vip => true ) }
      let(:order) { Order.new( :user => user ) }

      it "should discount five percent if total >= 1000" do
        order.total = 2000
        order.amount.should == 1900
      end

      it "should discount ten percent if total >= 10000" do
        order.total = 10000
        order.amount.should == 9000
      end

    end
    context "when user is vip" { ... }
  end

end
should 後面有各種 Matcher

     target.should be_true
     # targer.should == true

     target.should be_false
     # targer.should == false

     target.should be_nil
     # targer.should == nil
檢查型別、方法
target.should be_a_kind_of(Array)
# target.class.should == Array
target.should be_an_instance_of(Array)
# target.class.should == Array

target.should respond_to(:foo)
# target.repond_to?(:foo).should == true
檢查 Array、Hash
 target.should have_key(:foo)
 # target[:foo].should_not == nil

 target.should include(4)
 # target.include?(4).should == true

 target.should have(3).items
 # target.items.length == 3
任何 be_ 開頭
target.should be_empty
# target.empty?.should == true

target.should be_blank
# target.blank?.should == true

target.should be_admin
# target.admin?.should == true
別擔心...

• 一開始先學會用 should == 就萬能了
• 其他的 Matchers 可以之後再看 code
 學,學一招是一招

• 再進階一點你可以自己寫 Matcher,
 RSpec 甚至提供擴充的 DSL。
expect to
期望一段程式會改變某個值或丟出例外
改變值
describe Order do
  describe "#ship!" do

    context "with paid" do
      it "should update status to shipping" do
        expect {
          order.ship!
        }.to change { order.status }.from("new").to("shipping")
      end
    end

    context "without paid" { ... }
  end
end
其實跟這個版本一樣
 describe Order do
   describe "#ship!" do

     context "with paid" do
       it "should update status to shipping" do
         order.status.should == "new"
         order.ship!
         order.status.should == "shipping"
       end
     end

     context "without paid" { ... }
   end
 end
例外
describe Order do
  describe "#ship!" do

    context "with paid" do
      it "should raise NotPaidError" do
        expect {
           order.paid? = false
           order.ship!
         }.to raise_error(NotPaidError)
      end
    end

    context "with paid" { ... }

  end
end
其實跟這個版本一樣
 describe Order do
   describe "#ship!" do

     context "with paid" do
       it "should raise NotPaidError" do
         lambda {
            order.paid? = false
            order.ship!
          }.should_raise(NotPaidError)
       end
     end

     context "with paid" { ... }

   end
 end
漂亮的程式文件
Syntax matters!
    快走火入魔啦!!
使用 subject 可省略
    receiver
describe Order do

  subject { Order.new(:status => "New") }

  it { should be_valid } # subject.valid?.should == true

end
its 可省略 receiver 的
     方法呼叫
describe Order do

  subject { Order.new(:status => "New") }

  its(:status) { should == "New"} # subject.status.should == "New"

end
一些別名方法
        哪個念起來順就用哪個

describe Order do # describe 和 context 其實是 alias

  subject { Order.new(:status => "New") }

  # it, specify, example 其實都一樣
  it { should be_valid }
  specify { should be_valid }
  example { should be_valid }

end
Syntax matters!
RSpec is awesome because of its syntax.
一些慣例
• 一個 rb 檔案配一個同名的 _spec.rb 檔案
 • 容易找
 • watchr 等工具容易設定
 • editor 有支援快速鍵
• describe “#title” 是 instance method
• describe “.title” 是 class method
輸出格式

• rspec filename.rb 預設不產生文件
• rspec filename.rb -fs 輸出 specdoc 文件
• rspec filename.rb -fh 輸出 html 文件
HTML format
鼓勵寫出高 spec coverage 程式,因為可以當做一種文件
RSpec Mocks
用假的物件替換真正的物件,作為測試之用
物件導向是
         物件和物件之間的互動


                 Target
                 Object



Collaborator   Collaborator   Collaborator
  Object         Object         Object
當你在寫目標類別的測試和實作
  時,這些 Collaborator...

 • 無法控制回傳值的外部系統          (例如第三方 web service)


 • 建構正確的回傳值很麻煩      (例如得準備很多假資料)


 • 可能很慢,拖慢測試速度      (例如耗時的運算)


 • 有難以預測的回傳值   (例如亂數方法)


 • 還沒開始實作
        (特別是採用 TDD 流程)
使用假物件

• 可以隔離 Collaborator 的 Dependencies
 • 讓你專心在目標類別上
• 只要 Collaborator 提供的介面不變,修
 改實作不會影響這邊的測試。
Stub
回傳設定好的回傳值
@user = stub("user")
@user.stub(:name).and_return("ihower")
@user.name # "ihower"

@customer = stub("customer").as_null_object
@customer.foobar # nil

@client = stub("client")
@client.stub_chain(:foo, :bar, :baz).and_return("blah")
@client.foo.bar.baz # "blah"
# 小心 The Law of Demeter
用來測試最後的狀態

 describe "#receiver_name" do
   it "should be user name" do
     user = stub(:user)
     user.stub(:name).and_return("ihower")

     order = Order.new(:user => user)
     order.receiver_name.should == "ihower"
   end
 end
Mock
       如果沒被呼叫到,就算測試失敗



@gateway = mock("ezcat")

# Message Expectations
@gateway.should_receive(:deliver).with(@user).and_return(true)
@gateway.deliver
用來測試行為的發生
describe "#ship!" do

  before do
    @user = stub("user").as_null_object
    @gateway = mock("ezcat")
    @order = Order.new( :user => @user, :gateway => @gateway )
  end

  context "with paid" do
    it "should call ezship API" do
      @gateway.should_receive(:deliver).with(@user).and_return(true)
      @order.ship!
    end
end
行為沒發生...
Partial mocking and
        stubbing

• 如果 Collaborator 類別已經有了,我們可
 以 reuse它

• 只在有需要的時候 stub 或 mock 特定方法
stub 和 should_receive 可以直接
    用在任意物件及類別上

   user = stub(:user)
   User.stub(:find).and_return("ihower")

   gateway = Gateway.new
   gateway.should_receive(:deliver).with(user).and_return(true)
一般測試流程 vs.
     Mock 測試流程

Given 給定條件     Given 給定條件
When 當事情發生     Expect 預期會發生什麼
Then 則結果要是如何   When 當事情發生
一般測試是檢查物件最後的狀態

 測狀態如預期


                 Target
                 Object



Collaborator   Collaborator   Collaborator
  Object         Object         Object
Mocks 可以讓我們測試物件之間的行為



                   Target
測這個行為的發生           Object



  Collaborator   Collaborator   Collaborator
    Object         Object         Object
Mocks Aren’t Stubs
                      (2004/Java)




http://martinfowler.com/articles/mocksArentStubs.html
Classical Testing v.s.
   Mockist Testing

• 你先決定你要完成的範圍,然後實作這
 個範圍的所有物件。

• 只針對一個類別做測試及實作,其他都
 用假的。
哀號聲浪也不少...
• 你可能需要 stub 很多東西
• stub 的回傳值可能難以建構
• 與 Collaborator 的行為太耦合,改介面就要
 跟著改一堆測試。

• 承上,即使忘了跟著改實作,單元測試還是
 會過... 例如在動態語言中,可能 Collaborator 後來改了方法名稱,但是
 這邊的測試仍然會過...
如何趨吉避凶?
• 採用 TDD,會讓最後設計出來的 API 趨向:
 • 不需要 stub 太多
 • 回傳值簡單
 • 類別之間的耦合降低
• 擁有更高層級的整合測試,使用真的物件來
 做測試。
我的推論
 採用 TDD 並搭配
  Integration Test


會比較多的 stub 和
   mock


但是會設計出比較好
的 API 讓副作用較少
我的推論
  愛用 stub 和 mock


但是沒有用 TDD,也沒有
   Integration Test


API 設計的不好,stub
的缺點就顯現出來的
不然,請小心:

• 用 Mocks 來完全隔離內部 Internal dependencies
• 用 Mocks 來取代還沒實作的物件
版本一:

這個 Order 完全隔離 Item
  describe Order do
    before do
      @order = Order.new
    end

    describe "#<<" do
      it "should push item into order.items" do
        Item = stub("Item")
        item = stub("item", :name => "foobar")
        Item.stub(:new).and_return(item)

        @order << "foobar"
        @order.items.last.name.should == "foobar"
      end
    end

  end
測試通過,即使還沒
  實作 Item
  class Order

    attr_accessor :items

    def initialize
      self.items = []
    end

    def <<(item_name)
      self.items << Item.new(item_name)
    end

  end
版本二:

用真的 Item 物件
 (拿掉剛才的所有 stub code 即可)

describe Order do
  before do
    @order = Order.new
  end

  describe "#<<" do
    it "should push item into order.items" do
      @order << "foobar"
      @order.items.last.name.should == "foobar"
    end
  end

end
需要寫真的 Item
       讓測試通過
class Item               describe Item do
                           it "should be created by name" do
  attr_accessor :name        item = Item.new("bar")
                             item.name.should == "bar"
  def initialize(name)     end
    self.name = name     end
  end

end
差在哪? 假設我們後來
 修改了 Item 的實作
   class Item

     attr_accessor :name

     def initialize(name)
       self.name = name.upcase
     end

   end
真的版本會有多個 Failure :/
 Mocks 版本則不受影響 :)
但是如果 Item
 改介面呢?
 class Item

   attr_accessor :title

   def initialize(name)
     self.title = name.upcase
   end

 end
即使我們忘了改 Order,
Mocks 版的測試還是照過
       不誤 :(
兩害相權取其輕


• 寧願看到重複原因的 Failure?
• 還是連 Failure 都捕捉不到,失去測試的意義?
So, 有限度使用
• 造真物件不難的時候,造假的時間拿去造真
 的。有需要再用 Partial Stubbing/Mocking。
 (相較於 Java,在 Ruby 裡造物件很快)


• 只在 Collaborator 不好控制的時候,Stub 它
• 只在 Collaborator 還沒實作的時候,Mock 它
• Collaborator 的狀態不好檢查或它的實作可能會
 改變,這時候才改成測試行為。
造假舉例:Logger
 已知有一個 Logger 其 log 方法運作正常

# 測狀態
logger.log("Report generated")
File.read("log.log").should =~ /Report generated/
# 如果 Logger 換別種實作就死了

# 測行為
logger.should_receive(:log).with(/Report generated/)
logger.log("Report generated")
造假舉例:
   MVC 中的Controller 測試
# 測狀態
it "should be created successful" do
  post :create, :name => "ihower"
  response.should be_success
  User.last.name.should == "ihower" # 會真的存進資料庫
end

# 測行為
it "should be created successful" do
  Order.should_receive(:create).with(:name => "ihower").and_return(true)
  post :create, :name => "ihower"
  response.should be_success
end
More Message
Expectations Syntax
數數
target.should_receive(:ping).once
target.should_receive(:ping).twice
target.should_receive(:ping).exactly(3).times
target.should_receive(:ping).at_most(3).times
target.should_receive(:ping).at_least(3).times
彈性的參數
target.should_receive(:transfer).with( 50, instance_of(String) )
target.should_receive(:transfer).with( anything(), 50 )

target.should_receive(:transfer).with( any_arge() )
target.should_receive(:transfer).with( no_arge() )

target.should_receive(:transfer).with( hash_including( :foo => 1, :bar => 2) )
target.should_receive(:transfer).with( hash_not_including( :foo => 1, :bar => 2) )
target.should_receive(:transfer).with( hash_not_including( :foo => 1, :bar => 2) )
Mocking 例外

target.should_receive(:transfer).and_raise(TransferError)

target.should_receive(:transfer).and_throw(:transfer_error)
Mocks 小結

• 多一種測試的方法:除了已經會的測試
 狀態,多了可以測試行為的方法

• 有需要用到來讓測試更好寫,就用。不
 需要用,就不要用。
Some useful RSpec
        tools
• watchr
• rcov
• rspec-rails
 • factory_girl 造測試資料更方便
 • shoulda 提供更多 rails 專屬 matcher
• capybara 提供 browser simulators
Continuous Testing

• 使用 watchr 或 autotest
• 程式一修改完存檔,自動跑對應的測試
 • 節省時間,立即回饋
RSpec 讓你愛上寫測試
rcov
測試涵蓋度
哪些程式沒有測試到?
Coverage 只是手段,
     不是目的!
Capybara
                     模擬瀏覽器行為


describe "the signup process", :type => :request do
  it "signs me in" do
    within("#session") do
      fill_in 'Login', :with => 'user@example.com'
      fill_in 'Password', :with => 'password'
    end
      click_link 'Sign in'
    end
end
驗收測試的 Syntax
feature "signing up" do
  background do
    User.make(:email => 'user@example.com', :password => 'caplin')
  end

  scenario "signing in with correct credentials" do
    within("#session") do
      fill_in 'Login', :with => 'user@example.com'
      fill_in 'Password', :with => 'caplin'
    end
    click_link 'Sign in'
  end
end
怎樣寫好測試?
單元測試壞味道
• 只是一個 bug,卻出來一堆 failures,不
 知如何 trace 起。

• 改了程式實作,結果一堆測試要跟著改
• 測試都有涵蓋到,但是還是沒捕捉到明
 顯錯誤 (剛才的 Mocks 例子)
原則一:Isolation
• 一個 it 裡面只有一種測試目的,最好就
  只有一個 Expectation

• 盡量讓一個單元測試不被別的週邊因素
  影響

• one failure one problem
  這樣才容易 trace 問題所在
錯誤示範
describe "#amount" do
  it "should discount" do
    user.vip = true
    order.amount.should == 900
    user.vip = false
    order.amount.should == 1000
  end
end
善用 context
describe "#amount" do

  context "when user is vip" do
    it "should discount ten percent" do
      user.vip = true
      order.amount.should == 900
    end
  end

  context "when user is not vip" do
    it "should discount five percent" do
      user.vip = false
      order.amount.should == 1000
    end
  end

end
原則二:
盡量用較穩定的介面來進行測試

 • 不要測 Private methods
 • 抽象化可能會改變的實作,使用較穩定
  的介面來進行測試。
例一:

Private methods
 class Order

   def amount
     if @user.vip?
       self.caculate_amount_for_vip
     else
       self.caculate_amount_for_non_vip
     end
   end

   private

   def caculate_amount_for_vip
     # ...
   end

   def caculate_amount_for_non_vip
     # ...
   end

 end
錯誤示範

it "should discount for vip" do
  @order.send(:caculate_amount_for_vip).should == 900
end

it "should discount for non vip" do
  @order.send(:caculate_amount_for_vip).should == 1000
end
不要測 Private methods
•   測試 public 方法就可以同樣涵蓋到測試
    private/protect methods 了
•   修改任何 private 方法,不需要重寫測試

    •   變更 public 方法的實作,只要不改介面,
        就不需要重寫測試

•   可以控制你的 Public methods 介面,如果只
    是類別內部使用,請放到 Private/Protected
例二:

             測試特定的實作
                           HTTParty 是一個第三方套件


describe User do

  describe '.search' do
    it 'searches Google for the given query' do
      HTTParty.should_receive(:get).with('http://www.google.com',
                                          :query => { :q => 'foo' } ).and_return([])
      User.search query
    end
  end

end
                                        換別套 HTTP 套
                                        件,就得改測試
透過抽象化介面
          來測試
describe User do

  describe '.search' do
    it 'searches for the given query' do
      User.searcher = Searcher
      Searcher.should_receive(:search).with('foo').and_return([])

      User.search query
    end
  end

end
對應的實作
class User < ActiveRecord::Base

  class_attribute :searcher

  def self.search(query)
    searcher.search query
  end

end
                                    修改實作不需
class Searcher
                                    要修改測試
  def self.search(keyword, options={})
    HTTParty.get(keyword, options)
  end

end
哪些要測試?
TATFT
test all the f**king time
測試你最擔心出錯的部分,
 不要等待完美的測試。
不要因為測試無法捕捉所有bug,
     就不撰寫測試。
因為測試的確可以抓到大多數 bug。
每當接獲 bug 回報,
請先撰寫一個單元測試來揭發。
如何鼓勵寫測試? 提高
   coverage?
code review
pair programming
最後
寫測試的確不是萬能,不能保證程式的品質,
  也不能保證你的專案不會 EPIC FAIL。
RSpec 讓你愛上寫測試
別期待有時間補寫測試,現在就開始

邊開發邊寫 Unit Test,
  從做中學。
Reference:
•   The RSpec Book
•   The Rails 3 Way
•   Foundation Rails 2
•   xUnit Test Patterns
•   http://pure-rspec-rubynation.heroku.com/
•   http://jamesmead.org/talks/2007-07-09-introduction-to-mock-objects-in-ruby-
    at-lrug/
•   http://martinfowler.com/articles/mocksArentStubs.html
•   http://blog.rubybestpractices.com/posts/gregory/034-issue-5-testing-
    antipatterns.html
•   http://blog.carbonfive.com/2011/02/11/better-mocking-in-ruby/
其他程式語言 BDD

• JavaScript https://github.com/pivotal/jasmine
• PHP https://github.com/Behat/Behat
• Java
 • http://jdave.org/
 • http://jbehave.org/
Thanks.
google “ihower” 可以找到投影片

More Related Content

What's hot

Javascript this keyword
Javascript this keywordJavascript this keyword
Javascript this keywordPham Huy Tung
 
PHP para Adultos: Clean Code e Object Calisthenics
PHP para Adultos: Clean Code e Object CalisthenicsPHP para Adultos: Clean Code e Object Calisthenics
PHP para Adultos: Clean Code e Object CalisthenicsGuilherme Blanco
 
JavaScript - Chapter 8 - Objects
 JavaScript - Chapter 8 - Objects JavaScript - Chapter 8 - Objects
JavaScript - Chapter 8 - ObjectsWebStackAcademy
 
Observables in Angular
Observables in AngularObservables in Angular
Observables in AngularKnoldus Inc.
 
Mock Server Using WireMock
Mock Server Using WireMockMock Server Using WireMock
Mock Server Using WireMockGlobant
 
[PHP 也有 Day #64] PHP 升級指南
[PHP 也有 Day #64] PHP 升級指南[PHP 也有 Day #64] PHP 升級指南
[PHP 也有 Day #64] PHP 升級指南Shengyou Fan
 
Mockito a simple, intuitive mocking framework
Mockito   a simple, intuitive mocking frameworkMockito   a simple, intuitive mocking framework
Mockito a simple, intuitive mocking frameworkPhat VU
 
JavaScript - Chapter 4 - Types and Statements
 JavaScript - Chapter 4 - Types and Statements JavaScript - Chapter 4 - Types and Statements
JavaScript - Chapter 4 - Types and StatementsWebStackAcademy
 
Py.test
Py.testPy.test
Py.testsoasme
 
Fundamental JavaScript [UTC, March 2014]
Fundamental JavaScript [UTC, March 2014]Fundamental JavaScript [UTC, March 2014]
Fundamental JavaScript [UTC, March 2014]Aaron Gustafson
 
JavaScript - Chapter 7 - Advanced Functions
 JavaScript - Chapter 7 - Advanced Functions JavaScript - Chapter 7 - Advanced Functions
JavaScript - Chapter 7 - Advanced FunctionsWebStackAcademy
 
Effective testing with pytest
Effective testing with pytestEffective testing with pytest
Effective testing with pytestHector Canto
 
Creating Domain Specific Languages in Python
Creating Domain Specific Languages in PythonCreating Domain Specific Languages in Python
Creating Domain Specific Languages in PythonSiddhi
 
PHP - Introduction to Object Oriented Programming with PHP
PHP -  Introduction to  Object Oriented Programming with PHPPHP -  Introduction to  Object Oriented Programming with PHP
PHP - Introduction to Object Oriented Programming with PHPVibrant Technologies & Computers
 
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...Edureka!
 
서버 개발자가 바라 본 Functional Reactive Programming with RxJava - SpringCamp2015
서버 개발자가 바라 본 Functional Reactive Programming with RxJava - SpringCamp2015서버 개발자가 바라 본 Functional Reactive Programming with RxJava - SpringCamp2015
서버 개발자가 바라 본 Functional Reactive Programming with RxJava - SpringCamp2015NAVER / MusicPlatform
 

What's hot (20)

Javascript this keyword
Javascript this keywordJavascript this keyword
Javascript this keyword
 
PHP para Adultos: Clean Code e Object Calisthenics
PHP para Adultos: Clean Code e Object CalisthenicsPHP para Adultos: Clean Code e Object Calisthenics
PHP para Adultos: Clean Code e Object Calisthenics
 
JavaScript - Chapter 8 - Objects
 JavaScript - Chapter 8 - Objects JavaScript - Chapter 8 - Objects
JavaScript - Chapter 8 - Objects
 
Observables in Angular
Observables in AngularObservables in Angular
Observables in Angular
 
Mock Server Using WireMock
Mock Server Using WireMockMock Server Using WireMock
Mock Server Using WireMock
 
Php variables
Php variablesPhp variables
Php variables
 
[PHP 也有 Day #64] PHP 升級指南
[PHP 也有 Day #64] PHP 升級指南[PHP 也有 Day #64] PHP 升級指南
[PHP 也有 Day #64] PHP 升級指南
 
Mockito a simple, intuitive mocking framework
Mockito   a simple, intuitive mocking frameworkMockito   a simple, intuitive mocking framework
Mockito a simple, intuitive mocking framework
 
JavaScript - Chapter 4 - Types and Statements
 JavaScript - Chapter 4 - Types and Statements JavaScript - Chapter 4 - Types and Statements
JavaScript - Chapter 4 - Types and Statements
 
Py.test
Py.testPy.test
Py.test
 
Fundamental JavaScript [UTC, March 2014]
Fundamental JavaScript [UTC, March 2014]Fundamental JavaScript [UTC, March 2014]
Fundamental JavaScript [UTC, March 2014]
 
Php functions
Php functionsPhp functions
Php functions
 
JavaScript - Chapter 7 - Advanced Functions
 JavaScript - Chapter 7 - Advanced Functions JavaScript - Chapter 7 - Advanced Functions
JavaScript - Chapter 7 - Advanced Functions
 
Effective testing with pytest
Effective testing with pytestEffective testing with pytest
Effective testing with pytest
 
JavaScript Promises
JavaScript PromisesJavaScript Promises
JavaScript Promises
 
Creating Domain Specific Languages in Python
Creating Domain Specific Languages in PythonCreating Domain Specific Languages in Python
Creating Domain Specific Languages in Python
 
PHP - Introduction to Object Oriented Programming with PHP
PHP -  Introduction to  Object Oriented Programming with PHPPHP -  Introduction to  Object Oriented Programming with PHP
PHP - Introduction to Object Oriented Programming with PHP
 
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
Node.js Tutorial for Beginners | Node.js Web Application Tutorial | Node.js T...
 
서버 개발자가 바라 본 Functional Reactive Programming with RxJava - SpringCamp2015
서버 개발자가 바라 본 Functional Reactive Programming with RxJava - SpringCamp2015서버 개발자가 바라 본 Functional Reactive Programming with RxJava - SpringCamp2015
서버 개발자가 바라 본 Functional Reactive Programming with RxJava - SpringCamp2015
 
Es6 to es5
Es6 to es5Es6 to es5
Es6 to es5
 

Viewers also liked

最近の単体テスト
最近の単体テスト最近の単体テスト
最近の単体テストKen Morishita
 
BDD style Unit Testing
BDD style Unit TestingBDD style Unit Testing
BDD style Unit TestingWen-Tien Chang
 
那些 Functional Programming 教我的事
那些 Functional Programming 教我的事那些 Functional Programming 教我的事
那些 Functional Programming 教我的事Wen-Tien Chang
 
An introduction to the linux kernel and device drivers (NTU CSIE 2016.03)
An introduction to the linux kernel and device drivers (NTU CSIE 2016.03)An introduction to the linux kernel and device drivers (NTU CSIE 2016.03)
An introduction to the linux kernel and device drivers (NTU CSIE 2016.03)William Liang
 
作業系統與硬體元件的驅動軟體開發法則 (Operating Systems and Software Design Principles for Har...
作業系統與硬體元件的驅動軟體開發法則 (Operating Systems and Software Design Principles for Har...作業系統與硬體元件的驅動軟體開發法則 (Operating Systems and Software Design Principles for Har...
作業系統與硬體元件的驅動軟體開發法則 (Operating Systems and Software Design Principles for Har...William Liang
 
Android device driver structure introduction
Android device driver structure introductionAndroid device driver structure introduction
Android device driver structure introductionWilliam Liang
 
認試軟體測試的世界 & TDD/BDD 入門
認試軟體測試的世界 & TDD/BDD 入門認試軟體測試的世界 & TDD/BDD 入門
認試軟體測試的世界 & TDD/BDD 入門wantingj
 

Viewers also liked (7)

最近の単体テスト
最近の単体テスト最近の単体テスト
最近の単体テスト
 
BDD style Unit Testing
BDD style Unit TestingBDD style Unit Testing
BDD style Unit Testing
 
那些 Functional Programming 教我的事
那些 Functional Programming 教我的事那些 Functional Programming 教我的事
那些 Functional Programming 教我的事
 
An introduction to the linux kernel and device drivers (NTU CSIE 2016.03)
An introduction to the linux kernel and device drivers (NTU CSIE 2016.03)An introduction to the linux kernel and device drivers (NTU CSIE 2016.03)
An introduction to the linux kernel and device drivers (NTU CSIE 2016.03)
 
作業系統與硬體元件的驅動軟體開發法則 (Operating Systems and Software Design Principles for Har...
作業系統與硬體元件的驅動軟體開發法則 (Operating Systems and Software Design Principles for Har...作業系統與硬體元件的驅動軟體開發法則 (Operating Systems and Software Design Principles for Har...
作業系統與硬體元件的驅動軟體開發法則 (Operating Systems and Software Design Principles for Har...
 
Android device driver structure introduction
Android device driver structure introductionAndroid device driver structure introduction
Android device driver structure introduction
 
認試軟體測試的世界 & TDD/BDD 入門
認試軟體測試的世界 & TDD/BDD 入門認試軟體測試的世界 & TDD/BDD 入門
認試軟體測試的世界 & TDD/BDD 入門
 

Similar to RSpec 讓你愛上寫測試

Ruby 使用手冊 (Part 1)
Ruby 使用手冊 (Part 1)Ruby 使用手冊 (Part 1)
Ruby 使用手冊 (Part 1)Drake Huang
 
The Evolution of Async Programming (GZ TechParty C#)
The Evolution of Async Programming (GZ TechParty C#)The Evolution of Async Programming (GZ TechParty C#)
The Evolution of Async Programming (GZ TechParty C#)jeffz
 
實踐大學教案20140329
實踐大學教案20140329實踐大學教案20140329
實踐大學教案20140329Mu-Fan Teng
 
Java DSL与动态代码生成技术的应用 (上集:DSL部分)
Java DSL与动态代码生成技术的应用 (上集:DSL部分)Java DSL与动态代码生成技术的应用 (上集:DSL部分)
Java DSL与动态代码生成技术的应用 (上集:DSL部分)悦 温
 
02.python基础
02.python基础02.python基础
02.python基础modou li
 
Learning notes ruby
Learning notes rubyLearning notes ruby
Learning notes rubyRoger Xia
 
使用Dsl改善软件设计
使用Dsl改善软件设计使用Dsl改善软件设计
使用Dsl改善软件设计mingjin
 
Compiler for Dummy 一點都不深入的了解 Compiler, Interpreter 和 VM
Compiler for Dummy 一點都不深入的了解 Compiler, Interpreter 和 VMCompiler for Dummy 一點都不深入的了解 Compiler, Interpreter 和 VM
Compiler for Dummy 一點都不深入的了解 Compiler, Interpreter 和 VMLi Hsuan Hung
 
Learning python in the motion picture industry by will zhou
Learning python in the motion picture industry   by will zhouLearning python in the motion picture industry   by will zhou
Learning python in the motion picture industry by will zhouWill Zhou
 
Ecmascript
EcmascriptEcmascript
Ecmascriptjay li
 
模块一-Go语言特性.pdf
模块一-Go语言特性.pdf模块一-Go语言特性.pdf
模块一-Go语言特性.pdfczzz1
 
PHP 語法基礎與物件導向
PHP 語法基礎與物件導向PHP 語法基礎與物件導向
PHP 語法基礎與物件導向Shengyou Fan
 
Java SE 7 技術手冊投影片第 07 章 - 介面與多型
Java SE 7 技術手冊投影片第 07 章 - 介面與多型Java SE 7 技術手冊投影片第 07 章 - 介面與多型
Java SE 7 技術手冊投影片第 07 章 - 介面與多型Justin Lin
 
看似比較簡單的推坑教學 C語言從崩潰到崩潰Ex(二)
看似比較簡單的推坑教學 C語言從崩潰到崩潰Ex(二)看似比較簡單的推坑教學 C語言從崩潰到崩潰Ex(二)
看似比較簡單的推坑教學 C語言從崩潰到崩潰Ex(二)永立 連
 
Bash入门基础篇
Bash入门基础篇Bash入门基础篇
Bash入门基础篇Zhiyao Pan
 
Maintainable PHP Source Code
Maintainable PHP Source CodeMaintainable PHP Source Code
Maintainable PHP Source CodeBo-Yi Wu
 

Similar to RSpec 讓你愛上寫測試 (20)

Ruby basic
Ruby basicRuby basic
Ruby basic
 
Ruby 使用手冊 (Part 1)
Ruby 使用手冊 (Part 1)Ruby 使用手冊 (Part 1)
Ruby 使用手冊 (Part 1)
 
The Evolution of Async Programming (GZ TechParty C#)
The Evolution of Async Programming (GZ TechParty C#)The Evolution of Async Programming (GZ TechParty C#)
The Evolution of Async Programming (GZ TechParty C#)
 
實踐大學教案20140329
實踐大學教案20140329實踐大學教案20140329
實踐大學教案20140329
 
Java DSL与动态代码生成技术的应用 (上集:DSL部分)
Java DSL与动态代码生成技术的应用 (上集:DSL部分)Java DSL与动态代码生成技术的应用 (上集:DSL部分)
Java DSL与动态代码生成技术的应用 (上集:DSL部分)
 
Redis
RedisRedis
Redis
 
02.python基础
02.python基础02.python基础
02.python基础
 
Learning notes ruby
Learning notes rubyLearning notes ruby
Learning notes ruby
 
使用Dsl改善软件设计
使用Dsl改善软件设计使用Dsl改善软件设计
使用Dsl改善软件设计
 
Compiler for Dummy 一點都不深入的了解 Compiler, Interpreter 和 VM
Compiler for Dummy 一點都不深入的了解 Compiler, Interpreter 和 VMCompiler for Dummy 一點都不深入的了解 Compiler, Interpreter 和 VM
Compiler for Dummy 一點都不深入的了解 Compiler, Interpreter 和 VM
 
Learning python in the motion picture industry by will zhou
Learning python in the motion picture industry   by will zhouLearning python in the motion picture industry   by will zhou
Learning python in the motion picture industry by will zhou
 
Ecmascript
EcmascriptEcmascript
Ecmascript
 
模块一-Go语言特性.pdf
模块一-Go语言特性.pdf模块一-Go语言特性.pdf
模块一-Go语言特性.pdf
 
PHP 語法基礎與物件導向
PHP 語法基礎與物件導向PHP 語法基礎與物件導向
PHP 語法基礎與物件導向
 
Java SE 7 技術手冊投影片第 07 章 - 介面與多型
Java SE 7 技術手冊投影片第 07 章 - 介面與多型Java SE 7 技術手冊投影片第 07 章 - 介面與多型
Java SE 7 技術手冊投影片第 07 章 - 介面與多型
 
getPDF.aspx
getPDF.aspxgetPDF.aspx
getPDF.aspx
 
getPDF.aspx
getPDF.aspxgetPDF.aspx
getPDF.aspx
 
看似比較簡單的推坑教學 C語言從崩潰到崩潰Ex(二)
看似比較簡單的推坑教學 C語言從崩潰到崩潰Ex(二)看似比較簡單的推坑教學 C語言從崩潰到崩潰Ex(二)
看似比較簡單的推坑教學 C語言從崩潰到崩潰Ex(二)
 
Bash入门基础篇
Bash入门基础篇Bash入门基础篇
Bash入门基础篇
 
Maintainable PHP Source Code
Maintainable PHP Source CodeMaintainable PHP Source Code
Maintainable PHP Source Code
 

More from Wen-Tien Chang

⼤語⾔模型 LLM 應⽤開發入⾨
⼤語⾔模型 LLM 應⽤開發入⾨⼤語⾔模型 LLM 應⽤開發入⾨
⼤語⾔模型 LLM 應⽤開發入⾨Wen-Tien Chang
 
A brief introduction to Machine Learning
A brief introduction to Machine LearningA brief introduction to Machine Learning
A brief introduction to Machine LearningWen-Tien Chang
 
淺談 Startup 公司的軟體開發流程 v2
淺談 Startup 公司的軟體開發流程 v2淺談 Startup 公司的軟體開發流程 v2
淺談 Startup 公司的軟體開發流程 v2Wen-Tien Chang
 
ALPHAhackathon: How to collaborate
ALPHAhackathon: How to collaborateALPHAhackathon: How to collaborate
ALPHAhackathon: How to collaborateWen-Tien Chang
 
Git 版本控制系統 -- 從微觀到宏觀
Git 版本控制系統 -- 從微觀到宏觀Git 版本控制系統 -- 從微觀到宏觀
Git 版本控制系統 -- 從微觀到宏觀Wen-Tien Chang
 
Exception Handling: Designing Robust Software in Ruby (with presentation note)
Exception Handling: Designing Robust Software in Ruby (with presentation note)Exception Handling: Designing Robust Software in Ruby (with presentation note)
Exception Handling: Designing Robust Software in Ruby (with presentation note)Wen-Tien Chang
 
Exception Handling: Designing Robust Software in Ruby
Exception Handling: Designing Robust Software in RubyException Handling: Designing Robust Software in Ruby
Exception Handling: Designing Robust Software in RubyWen-Tien Chang
 
從 Classes 到 Objects: 那些 OOP 教我的事
從 Classes 到 Objects: 那些 OOP 教我的事從 Classes 到 Objects: 那些 OOP 教我的事
從 Classes 到 Objects: 那些 OOP 教我的事Wen-Tien Chang
 
Yet another introduction to Git - from the bottom up
Yet another introduction to Git - from the bottom upYet another introduction to Git - from the bottom up
Yet another introduction to Git - from the bottom upWen-Tien Chang
 
A brief introduction to Vagrant – 原來 VirtualBox 可以這樣玩
A brief introduction to Vagrant – 原來 VirtualBox 可以這樣玩A brief introduction to Vagrant – 原來 VirtualBox 可以這樣玩
A brief introduction to Vagrant – 原來 VirtualBox 可以這樣玩Wen-Tien Chang
 
Ruby 程式語言綜覽簡介
Ruby 程式語言綜覽簡介Ruby 程式語言綜覽簡介
Ruby 程式語言綜覽簡介Wen-Tien Chang
 
A brief introduction to SPDY - 邁向 HTTP/2.0
A brief introduction to SPDY - 邁向 HTTP/2.0A brief introduction to SPDY - 邁向 HTTP/2.0
A brief introduction to SPDY - 邁向 HTTP/2.0Wen-Tien Chang
 
RubyConf Taiwan 2012 Opening & Closing
RubyConf Taiwan 2012 Opening & ClosingRubyConf Taiwan 2012 Opening & Closing
RubyConf Taiwan 2012 Opening & ClosingWen-Tien Chang
 
從 Scrum 到 Kanban: 為什麼 Scrum 不適合 Lean Startup
從 Scrum 到 Kanban: 為什麼 Scrum 不適合 Lean Startup從 Scrum 到 Kanban: 為什麼 Scrum 不適合 Lean Startup
從 Scrum 到 Kanban: 為什麼 Scrum 不適合 Lean StartupWen-Tien Chang
 
RubyConf Taiwan 2011 Opening & Closing
RubyConf Taiwan 2011 Opening & ClosingRubyConf Taiwan 2011 Opening & Closing
RubyConf Taiwan 2011 Opening & ClosingWen-Tien Chang
 
Service-Oriented Design and Implement with Rails3
Service-Oriented Design and Implement with Rails3Service-Oriented Design and Implement with Rails3
Service-Oriented Design and Implement with Rails3Wen-Tien Chang
 

More from Wen-Tien Chang (20)

⼤語⾔模型 LLM 應⽤開發入⾨
⼤語⾔模型 LLM 應⽤開發入⾨⼤語⾔模型 LLM 應⽤開發入⾨
⼤語⾔模型 LLM 應⽤開發入⾨
 
A brief introduction to Machine Learning
A brief introduction to Machine LearningA brief introduction to Machine Learning
A brief introduction to Machine Learning
 
淺談 Startup 公司的軟體開發流程 v2
淺談 Startup 公司的軟體開發流程 v2淺談 Startup 公司的軟體開發流程 v2
淺談 Startup 公司的軟體開發流程 v2
 
ALPHAhackathon: How to collaborate
ALPHAhackathon: How to collaborateALPHAhackathon: How to collaborate
ALPHAhackathon: How to collaborate
 
Git 版本控制系統 -- 從微觀到宏觀
Git 版本控制系統 -- 從微觀到宏觀Git 版本控制系統 -- 從微觀到宏觀
Git 版本控制系統 -- 從微觀到宏觀
 
Exception Handling: Designing Robust Software in Ruby (with presentation note)
Exception Handling: Designing Robust Software in Ruby (with presentation note)Exception Handling: Designing Robust Software in Ruby (with presentation note)
Exception Handling: Designing Robust Software in Ruby (with presentation note)
 
Exception Handling: Designing Robust Software in Ruby
Exception Handling: Designing Robust Software in RubyException Handling: Designing Robust Software in Ruby
Exception Handling: Designing Robust Software in Ruby
 
從 Classes 到 Objects: 那些 OOP 教我的事
從 Classes 到 Objects: 那些 OOP 教我的事從 Classes 到 Objects: 那些 OOP 教我的事
從 Classes 到 Objects: 那些 OOP 教我的事
 
Yet another introduction to Git - from the bottom up
Yet another introduction to Git - from the bottom upYet another introduction to Git - from the bottom up
Yet another introduction to Git - from the bottom up
 
A brief introduction to Vagrant – 原來 VirtualBox 可以這樣玩
A brief introduction to Vagrant – 原來 VirtualBox 可以這樣玩A brief introduction to Vagrant – 原來 VirtualBox 可以這樣玩
A brief introduction to Vagrant – 原來 VirtualBox 可以這樣玩
 
Ruby 程式語言綜覽簡介
Ruby 程式語言綜覽簡介Ruby 程式語言綜覽簡介
Ruby 程式語言綜覽簡介
 
A brief introduction to SPDY - 邁向 HTTP/2.0
A brief introduction to SPDY - 邁向 HTTP/2.0A brief introduction to SPDY - 邁向 HTTP/2.0
A brief introduction to SPDY - 邁向 HTTP/2.0
 
RubyConf Taiwan 2012 Opening & Closing
RubyConf Taiwan 2012 Opening & ClosingRubyConf Taiwan 2012 Opening & Closing
RubyConf Taiwan 2012 Opening & Closing
 
從 Scrum 到 Kanban: 為什麼 Scrum 不適合 Lean Startup
從 Scrum 到 Kanban: 為什麼 Scrum 不適合 Lean Startup從 Scrum 到 Kanban: 為什麼 Scrum 不適合 Lean Startup
從 Scrum 到 Kanban: 為什麼 Scrum 不適合 Lean Startup
 
Git Tutorial 教學
Git Tutorial 教學Git Tutorial 教學
Git Tutorial 教學
 
RubyConf Taiwan 2011 Opening & Closing
RubyConf Taiwan 2011 Opening & ClosingRubyConf Taiwan 2011 Opening & Closing
RubyConf Taiwan 2011 Opening & Closing
 
Git and Github
Git and GithubGit and Github
Git and Github
 
Service-Oriented Design and Implement with Rails3
Service-Oriented Design and Implement with Rails3Service-Oriented Design and Implement with Rails3
Service-Oriented Design and Implement with Rails3
 
Rails3 changesets
Rails3 changesetsRails3 changesets
Rails3 changesets
 
遇見 Ruby on Rails
遇見 Ruby on Rails遇見 Ruby on Rails
遇見 Ruby on Rails
 

RSpec 讓你愛上寫測試