当前位置: 首页 > 工具软件 > rspec-mocks > 使用案例 >

RSpec簡介

郎德馨
2023-12-01

測試 Testing

Developer testing isn’t primarily about verifying code. It’s about making great code. If you can’t test something, it might be your testing skills failing you but it’s probably your code code’s design. Testable code is almost always better code. - Chad Fowler

關於寫測試,很多人的第一印象可能是:

  • 寫測試很無聊
  • 測試很難寫
  • 寫測試不好玩
  • 我們沒有時間寫測試

時程緊迫預算吃緊,哪來的時間做自動化測試呢?這個想法是短視的,寫測試其實有以下好處:

  1. 確認你寫的程式的正確,結果如你所預期。一旦寫好測試程式,很容易就可以檢查程式有沒有寫對。
  2. 之後新加功能或改寫重構時,不會影響搞爛其他程式。這又叫作「回歸測試」,你不需要手動再去測其他部分的測試,你可以用之前寫好的測試程式。
  3. 可以採用*TDD*開發方式,先寫測試再實作。這是寫測試的最佳時機點,實作的目的就是為了通過測試。從使用*API*的呼叫者的角度去看待程式,可以關注在介面而設計出更好用的*API*。
  4. 測試就是一種程式規格,程式的規格就是滿足測試條件。這也是為什麼*RSpec*稱為*Spec*的原因。不知道*API*怎麼呼叫使用時,可以透過讀測試程式知道怎麼使用。

其中光是第一個好處,就值得你學習如何寫測試,來加速你的開發,怎麼說呢?回想你平常是怎麼確認你寫的程式正確的呢? 是不是在命令列中實際執行看看,或是打開瀏覽器看看結果,每次修改,就重新手動重新整理看看。這些步驟其實可以透過用自動化測試取代,大大節省手工測試的時間。也其實是一種投資,如果是簡單的程式,也許你手動執行一次就寫對了,但是如果是複雜的程式,往往第一次不會寫對,你會浪費很多時間在檢查到底你寫的程式的正確性,而寫測試就可以大大的節省這些時間。更不用說你明天,下個禮拜或下個月需要再確認其他程式有沒有副作用影響的時候,你有一組測試程式可以大大節省手動檢查的時間。

在這一章,我們將使用RSpec來取代Rails預設的Test::Unit來做為我們測試的工具。RSpec是一套改良版的xUnit測試框架,讓我們先來比較看看:

幾乎每種語言都有一套基於xUnit測試框架的測試工具,讓你可以測試軟體中的基本元件,也就是類別和方法。簡單來說,每個單元測試的流程是:設定測試資料、然後執行要測試的方法、最後檢查結果是否正確。

這是一個Test::Unit範例:

class OrderTest < Test::Unit::TestCase
  def setup
    @order = Order.new
  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

光看程式有沒有覺得非常容易了解跟閱讀,也更像是一種規格Spec文件,讓我們繼續介紹下去如何使用吧。

RSpec簡介

RSpec是一套Ruby的測試DSL(Domain-specific language)框架,它的程式比Test::Unit更好讀,寫的人更容易描述測試目的,可以說是一種可執行的規格文件。也 非常多的Ruby on Rails專案採用RSpec作為測試框架。它又稱為一種BDD(Behavior-driven development)測試框架,相較於TDDtest思維,測試程式的結果。BDD強調的是用spec思維,描述程式應該有什麼行為。

安裝RSpecRSpec-Rails

Gemfile中加入:

group :test, :development do
  gem "rspec", "~> 2.0"
  gem "rspec-rails", "~> 2.0"
end

安裝:

rails generate rspec:install

如何執行測試:

bundle exec rake spec

rake spec會先執行一次rake db:test:prepare建立測試資料庫。

測試單一檔案,例如:

bundle exec rspec spec/models/user_spec.rb

語法介紹

在示範怎麼在Rails中寫單元測試前,讓我們先介紹一些基本的RSpec用法:

describecontext

describecontext幫助你組織分類,都是可以任意套疊的:

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

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

通常最外層是我們想要測試的類別,然後下一層是哪一個方法,然後是不同的情境。

itshould

每個it就是一小段測試,在裡面我們會用should來設定期望,例如:

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

除了should,也有相反地should_not可以用。

beforeafter

如同xUnit框架的setupteardown

  • before(:each) 每段it之前執行
  • before(:all) 整段describe前只執行一次
  • after(:each) 每段it之後執行
  • after(:all) 整段describe後只執行一次

範例如下:

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

Matcher

上述的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可以之後邊看邊學,學一招是一招。再進階一點你可以自己寫MatcherRSpec有提供擴充的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 raise NotPaidError" do
       expect {
          order.paid? = false
          order.ship!
        }.to raise_error(NotPaidError)
      end
    end  
 
    context "with paid" { ... }
  
  end
end

RSpec Mocks

用假的物件替換真正的物件,作為測試之用。主要用途有:

  • 無法控制回傳值的外部系統 (例如第三方的網路服務)
  • 建構正確的回傳值很麻煩 (例如得準備很多假資料)
  • 可能很慢,拖慢測試速度 (例如耗時的運算)
  • 有難以預測的回傳值 (例如亂數方法)
  • 還沒開始實作 (特別是採用*TDD*流程)

如何使用Mocks超出本書範圍。

Rails中的測試

Rails中,RSpec分成數種不同測試,分別是Model測試、Controller測試、View測試、Helper測試、Route測試。

安裝

編輯Gemfile

group :test, :development do
  gem "rspec"
  gem "rspec-rails"
end

輸入bundle安裝,接著輸入:

rails g rspec:install

這樣就會建立出spec目錄來放測試程式,本來的test目錄就不用了。

Model 測試

裝了rspec-rails之後,rails g model時也會順道建立對應的Spec檔案。這裡我們來寫點Event model的測試吧,延續RESTful 與表單設計操作 Resources 狀態一節所示範的方法為例,新增spec/models/event_spec.rb如下:

require 'spec_helper'

describe Event do

  before do
    @event = Event.new( :name => "foobar" )
  end

  describe ".closed?" do
    it "should return true if status is CLOSED" do
      @event.status = "CLOSED"
      @event.closed?.should be_true
    end

    it "should return false if status is not CLOSED" do
      @event.status = "OPEN"
      @event.closed?.should be_false
    end
  end

  describe ".open?" do
    it "should return true if status is OPEN" do
      @event.status = "OPEN"
      @event.open?.should be_true
    end

    it "should return false if status is not OPEN" do
      @event.status = "CLOSED"
      @event.open?.should be_false
    end
  end

  describe "open!" do
    it "should set status to OPEN" do
      @event.open!
      @event.status.should == "OPEN"
    end
  end

  describe "close!" do
    it "should set status to CLOSED" do
      @event.close!
      @event.status.should == "CLOSED"
    end
  end
end

要怎麼執行測試呢?輸入bundle exec rake spec就會根據目前的開發資料庫Schema建一個測試用資料庫,然後執行所有spec目錄下的_spec.rb檔案結尾的測試。

如果測試資料庫已經建好了,例如執行過rake spec或是bundle exec rake db:test:prepare之後,你也可以單獨執行測試bundle exec rspec spec/models/event_spec.rb

 类似资料: