<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    測試熱潮現(xiàn)在傳播到了 Ruby 編程社區(qū),并且愈演愈熱。在過去一年里,測試領域中最為矚目的創(chuàng)新應屬 RSpec 的引入和快速發(fā)展,這是一種行為驅動測試工具。通過本文了解 RSpec 如何改變?nèi)藗兯伎紲y試的方式。

    在過去十年中,軟件開發(fā)人員對測試的熱情日漸低迷。同一時期出現(xiàn)的動態(tài)語言并沒有提供編譯程序來捕捉最基本的錯誤,這使得測試變得更加重要。隨著測試社區(qū)的成長,開發(fā)人員開始注意到,除了捕獲 bug 等最基本的優(yōu)點外,測試還具有以下優(yōu)勢:

    • 測試能夠改進您的設計。進行測試的每個目標對象必須具備至少兩個客戶機:生產(chǎn)代碼和測試用例。這些客戶機強制您對代碼進行解耦。測試還鼓勵開發(fā)人員使用更小、更簡單的方法。
    • 測試減少了不必要的代碼。在編寫測試用例時,您養(yǎng)成了很好的測試習慣,即只編寫運行測試用例所需的最少代碼。您抵制住了對功能進行編碼的誘惑,因為您目前還不需要它。
    • 推動了測試優(yōu)先開發(fā)。您編寫的每個測試用例會確定一個小問題。使用代碼解決這個問題非常有用并且可以推動開發(fā)。當我進行測試驅動開發(fā)時,時間過得飛快。
    • 測試提供了更多的自主權。在使用測試用例捕獲可能的錯誤時,您會發(fā)現(xiàn)自己非常愿意對代碼進行改進。

    測試驅動的開發(fā)和 RSpec

    有關測試的優(yōu)點無需贅述,我將向您介紹一個簡單的使用 RSpec 的測試驅動開發(fā)示例。RSpec 工具是一個 Ruby 軟件包,可以用它構建有關您的軟件的規(guī)范。該規(guī)范實際上是一個描述系統(tǒng)行為的測試。使用 RSpec 的開發(fā)流程如下:

    • 編寫一個測試。該測試描述系統(tǒng)中某個較小元素的行為。
    • 運行測試。由于尚沒有為系統(tǒng)中的相應部分構建代碼,測試失敗。這一重要步驟將測試您的測試用例,檢驗測試用例是否在應當失敗的時候失敗。
    • 編寫足夠的代碼,使測試通過。
    • 運行測試,檢驗測試是否成功。

    實質上,RSpec 開發(fā)人員所做的工作就是將失敗的測試用例調試為成功的測試用例。這是一個主動的過程。本文中,我將介紹 RSpec 的基本用法。

    首先,假設您已安裝了 Ruby 和 gems。您還需要安裝 RSpec。輸入下面的內(nèi)容:

    gem install rspec





    使用示例

    接下來,我將逐步構建一個狀態(tài)機。我將遵循 TDD 規(guī)則。首先編寫自己的測試用例,并且直到測試用例需要時才編寫代碼。Rake 的創(chuàng)建者 Jim Weirich 認為這有助于角色扮演。在編寫實際的生產(chǎn)代碼時,您希望充當一回 jerk 開發(fā)人員的角色,只完成最少量的工作來使測試通過。在編寫測試時,您則扮演測試人員的角色,試圖為開發(fā)人員提供一些有益的幫助。

    以下的示例展示了如何構建一個狀態(tài)機。如果您以前從未接觸過狀態(tài)機,請查閱 參考資料。狀態(tài)機具有多種狀態(tài)。每種狀態(tài)支持可以轉換狀態(tài)機狀態(tài)的事件。測試驅動開發(fā)入門的關鍵就是從零入手,盡量少地使用假設條件。針對測試進行程序設計。

    使用清單 1 的內(nèi)容創(chuàng)建名為 machine_spec.rb 的文件。該文件就是您的規(guī)范。您還不了解 machine.rb 文件的作用,目前先創(chuàng)建一個空文件。


    清單 1. 最初的 machine_spec.rb 文件
      require 'machine'
                

    接下來,需要運行測試。始終通過輸入 spec machine_spec.rb 運行測試。清單 2 展示了預料之中的測試失敗:


    清單 2. 運行空的規(guī)范
    ~/rspec batate$ spec machine_spec.rb
                /opt/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require':
                no such file to load -- machine (LoadError)
                from /opt/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
                from ./state_machine_spec.rb:1
                from ...
                

    在測試驅動開發(fā)中,您需要進行增量開發(fā),因此在進行下一次開發(fā)前,需要先解決此次測試出現(xiàn)的問題。現(xiàn)在,我將扮演 jerk 開發(fā)人員的角色,即只完成滿足應用程序運行所需的最少工作量。我將創(chuàng)建一個名為 machine.rb 的空文件,使測試通過。我現(xiàn)在可以以逸待勞,測試通過而我?guī)缀鯖]做任何事情。

    繼續(xù)角色扮演。我現(xiàn)在扮演一個煩躁的測試人員,促使 jerk 開發(fā)人員做些實際的工作。我將編碼以下規(guī)范,需要使用 Machine 類,如清單 3 所示:


    清單 3. 初始規(guī)范
    require 'machine'
                describe Machine do
                before :each do
                @machine = Machine
                end
                end
                

    該規(guī)范描述了目前尚不存在的 Machine 類。describe 方法提供了 RSpec 描述,您將傳入測試類的名稱和包含實際規(guī)范的代碼塊。通常,測試用例需要執(zhí)行一定數(shù)量的設置工作。在 RSpec 中,將由 before 方法完成這些設置工作。您向 before 方法傳遞一個可選的標志和一個代碼塊。代碼塊中包含設置工作。標志確定 RSpec 執(zhí)行代碼塊的頻率。默認的標志為 :each,表示 RSpec 將在每次測試之前調用 set up 代碼塊。您也可以指定 :all,表示 RSpec 在執(zhí)行所有測試之前只調用一次 before 代碼塊。您應該始終使用 :each,使各個測試彼此獨立。

    輸入 spec 運行測試,如清單 4 所示:


    清單 4. 存在性測試失敗
    ~/rspec batate$ spec machine_spec.rb
                ./machine_spec.rb:3: uninitialized constant Machine (NameError)
                

    現(xiàn)在,煩躁的測試人員要促使 jerk 開發(fā)人員做點什么了 — jerk 開發(fā)人員現(xiàn)在需要創(chuàng)建某個類。對我來說,就是修復測試出現(xiàn)的錯誤。在 machine.rb 中,我輸入最少量的代碼,如清單 5 所示:


    清單 5. 創(chuàng)建初始 Machine 類
    class Machine
                end

    保存文件,然后運行測試。毫無疑問,清單 6 顯示的測試報告沒有出現(xiàn)錯誤:


    清單 6. 測試 Machine 是否存在
    ~/rspec batate$ spec machine_spec.rb
                Finished in 5.0e-06 seconds
                0 examples, 0 failures
                





    編寫行為

    現(xiàn)在,我可以開始實現(xiàn)更多的行為。我知道,所有狀態(tài)機必須在某些初始狀態(tài)下啟動。目前我還不是很清楚如何設計這個行為,因此我先編寫一個非常簡單的測試,首先假設 state 方法會返回 :initial 標志。我對 machine_spec.rb 進行修改并運行測試,如清單 7 所示:


    清單 7. 實現(xiàn)初始狀態(tài)并運行測試
    require 'machine'
                describe Machine do
                before :each do
                @machine = Machine.new
                end
                it "should initially have a state of :initial" do
                @machine.state.should == :initial
                end
                end
                ~/rspec batate$ spec machine_spec.rb
                F
                1)
                NoMethodError in 'Machine should initially have a state of :initial'
                undefined method `state' for #<Machine:0x10c7f8c>
                ./machine_spec.rb:9:
                Finished in 0.005577 seconds
                1 example, 1 failure
                

    注意這條規(guī)則: it "should initially have a state of :initial" do @machine.state.should == :initial end。首先注意到這條規(guī)則讀起來像是一個英文句子。刪除標點,將得到 it should initially have a state of initial。然后會注意到這條規(guī)則并不像是典型的面向對象代碼。它確實不是。您現(xiàn)在有一個方法,稱為 it。該方法具有一個使用引號括起來的字符串參數(shù)和一個代碼塊。字符串應該描述測試需求。最后,doend 之間的代碼塊包含測試用例的代碼。

    可以看到,測試進度劃分得很細。這些微小的步驟產(chǎn)生的收益卻很大。它們使我能夠改進測試密度,提供時間供我思考期望的行為以及實現(xiàn)行為所需的 API。這些步驟還能使我在開發(fā)期間跟蹤代碼覆蓋情況,從而構建更加豐富的規(guī)范。

    這種風格的測試具有雙重作用:測試實現(xiàn)并在測試的同時構建需求設計文檔。稍后,我將通過測試用例構建一個需求列表。

    我使用最簡單的方式修復了測試,返回 :initial,如清單 8 所示:


    清單 8. 指定初始狀態(tài)
    class Machine
                def state
                :initial
                end
                end
                

    當查看實現(xiàn)時,您可能會放聲大笑或感覺受到了愚弄。對于測試驅動開發(fā),您必須稍微改變一下思考方式。您的目標并不是編寫最終的生產(chǎn)代碼,至少現(xiàn)在不是。您的目標是使測試通過。當掌握以這種方式工作時,您可能會發(fā)現(xiàn)新的實現(xiàn),并且編寫的代碼要遠遠少于采用 TDD 時編寫的代碼。

    下一步是運行代碼,查看它是否通過測試:


    清單 9. 運行初始狀態(tài)測試
    ~/rspec batate$ spec machine_spec.rb
                .
                Finished in 0.005364 seconds
                1 example, 0 failures

    花些時間思考一下這個通過測試的迭代。如果查看代碼的話,您可能會覺得氣餒。因為并沒有取得什么進展。如果查看整個迭代,將看到更多內(nèi)容:您捕獲了一個重要需求并編寫測試用例實現(xiàn)需求。作為一名程序員,我的第一個行為測試幫助我明確了開發(fā)過程。因為實現(xiàn)細節(jié)隨著測試的進行越來越清晰。

    現(xiàn)在,我可以實現(xiàn)一個更健壯的狀態(tài)實現(xiàn)。具體來講,我需要處理狀態(tài)機的多個狀態(tài)。我需要創(chuàng)建一個新的規(guī)則獲取有效狀態(tài)列表。像以前一樣,我將運行測試并查看是否通過。


    清單 10. 實現(xiàn)有效狀態(tài)規(guī)范
     it "should remember a list of valid states" do
                @machine.states = [:shopping, :checking_out]
                @machine.states.should = [:shopping, :checking_out]
                end
                run test(note: failing first verifies test)
                ~/rspec batate$ spec machine_spec.rb
                .F
                1)
                NoMethodError in 'Machine should remember a list of valid states'
                undefined method `states=' for #<Machine:0x10c7154>
                ./machine_spec.rb:13:
                Finished in 0.005923 seconds
                2 examples, 1 failure

    在清單 10 中,出現(xiàn)了一個 RSpec 形式的斷言。該斷言從 should 方法開始,然后添加了一些比較關系。should 方法對應用程序進行某種觀察。工作中的應用程序應該以某種方式運行。should 方法很好地捕獲了這種需求。在本例中,我的狀態(tài)機應該記憶兩種不同的狀態(tài)。

    現(xiàn)在,應該添加一個實例變量來實際記憶狀態(tài)。像以往一樣,我在修改代碼后運行測試用例,并觀察測試是否成功。


    清單 11. 創(chuàng)建一個屬性以記憶狀態(tài)
    class Machine
                attr_accessor :states
                def state
                :initial
                end
                end
                ~/rspec batate$ spec machine_spec.rb
                ..
                Finished in 0.00606 seconds
                2 examples, 0 failures





    驅動重構

    此時,我并不想決定將 :initial 狀態(tài)稱為狀態(tài)機的第一個狀態(tài)。相反,我更希望第一個狀態(tài)是狀態(tài)數(shù)組中的第一個元素。我對狀態(tài)機的理解在不斷演變。這種現(xiàn)象并不少見。測試驅動開發(fā)經(jīng)常迫使我重新考慮之前的假設。由于我已經(jīng)通過測試用例捕獲了早期需求,我可以輕松地對代碼進行重構。在本例中,重構就是對代碼進行調整,使其更好地工作。

    修改第一個測試,使其如清單 12 所示,并運行測試:


    清單 12. 初始狀態(tài)應該為指定的第一個狀態(tài)
    it "should initially have a state of the first state" do
                @machine.states = [:shopping, :checking_out]
                @machine.state.should == :shopping
                end
                ~/rspec batate$ spec machine_spec.rb
                F.
                1)
                'Machine should initially have a state of the first state' FAILED
                expected :shopping, got :initial (using ==)
                ./machine_spec.rb:10:
                Finished in 0.005846 seconds
                2 examples, 1 failure

    可以這樣說,測試用例起到作用了,因為它運行失敗,因此我現(xiàn)在需要修改代碼以使其工作。顯而易見,我的任務就是使測試通過。我喜歡這種測試目的,因為我的測試用例正在驅動我進行設計。我將把初始狀態(tài)傳遞給 new 方法。我將對實現(xiàn)稍作修改,以符合修改后的規(guī)范,如清單 13 所示。


    清單 13. 指定初始狀態(tài)
    start to fix it
                class Machine
                attr_accessor :states
                attr_reader :state
                def initialize(states)
                @states = states
                @state = @states[0]
                end
                end
                ~/rspec batate$ spec machine_spec.rb
                1)
                ArgumentError in 'Machine should initially have a state of the first state'
                wrong number of arguments (0 for 1)
                ./machine_spec.rb:5:in `initialize'
                ./machine_spec.rb:5:in `new'
                ./machine_spec.rb:5:
                2)
                ArgumentError in 'Machine should remember a list of valid states'
                wrong number of arguments (0 for 1)
                ./machine_spec.rb:5:in `initialize'
                ./machine_spec.rb:5:in `new'
                ./machine_spec.rb:5:
                Finished in 0.006391 seconds
                2 examples, 2 failures

    現(xiàn)在,測試出現(xiàn)了一些錯誤。我找到了實現(xiàn)中的一些 bug。測試用例不再使用正確的接口,因為我沒有把初始狀態(tài)傳遞給狀態(tài)機。可以看到,測試用例已經(jīng)起到了保護作用。我進行了較大的更改,測試就發(fā)現(xiàn)了 bug。我們需要對測試進行重構以匹配新的接口,將初始狀態(tài)列表傳遞給 new 方法。在這里我并沒有重復初始化代碼,而是將其放置在 before 方法中,如清單 14 所示:


    清單 14. 在 “before” 中初始化狀態(tài)機
    require 'machine'
                describe Machine do
                before :each do
                @machine = Machine.new([:shopping, :checking_out])
                end
                it "should initially have a state of the first state" do
                @machine.state.should == :shopping
                end
                it "should remember a list of valid states" do
                @machine.states.should == [:shopping, :checking_out]
                end
                end
                ~/rspec batate$ spec machine_spec.rb
                ..
                Finished in 0.005542 seconds
                2 examples, 0 failures

    狀態(tài)機開始逐漸成型。代碼仍然有一些問題,但是正在向良好的方向演化。我將開始對狀態(tài)機進行一些轉換。這些轉換將促使代碼實際記憶當前狀態(tài)。

    測試用例促使我全面地思考 API 的設計。我需要知道如何表示事件和轉換。首先,我將使用一個散列表表示轉換,而沒有使用成熟的面向對象實現(xiàn)。隨后,測試需求可能會要求我修改假設條件,但是目前,我仍然保持這種簡單性。清單 15 顯示了修改后的代碼:


    清單 15. 添加事件和轉換
    remember events... change before conditions
                require 'machine'
                describe Machine do
                before :each do
                @machine = Machine.new([:shopping, :checking_out])
                @machine.events = {:checkout =>
                {:from => :shopping, :to => :checking_out}}
                end
                it "should initially have a state of the first state" do
                @machine.state.should == :shopping
                end
                it "should remember a list of valid states" do
                @machine.states.should == [:shopping, :checking_out]
                end
                it "should remember a list of events with transitions" do
                @machine.events.should == {:checkout =>
                {:from => :shopping, :to => :checking_out}}
                end
                end
                ~/rspec batate$ spec machine_spec.rb
                FFF
                1)
                NoMethodError in 'Machine should initially have a state of the first state'
                undefined method `events=' for #<Machine:0x10c6f38>
                ./machine_spec.rb:6:
                2)
                NoMethodError in 'Machine should remember a list of valid states'
                undefined method `events=' for #z7lt;Machine:0x10c5afc>
                ./machine_spec.rb:6:
                3)
                NoMethodError in 'Machine should remember a list of events with transitions'
                undefined method `events=' for #<Machine:0x10c4a58>
                ./machine_spec.rb:6:
                Finished in 0.006597 seconds
                3 examples, 3 failures

    由于新的測試代碼位于 before 中,將我的三個測試分解開來。盡管如此,清單 16 中展示的測試非常容易修復。我將添加另一個訪問程序:


    清單 16. 記憶事件
    class Machine
                attr_accessor :states, :events
                attr_reader :state
                def initialize(states)
                @states = states
                @state = @states[0]
                end
                end
                ~/rspec batate$ spec machine_spec.rb
                ...
                Finished in 0.00652 seconds
                3 examples, 0 failures
                test

    測試全部通過。我得到了一個能正常運行的狀態(tài)機。接下來的幾個測試將使它更加完善。





    接近真實的應用程序

    目前為止,我所做的不過是觸發(fā)了一次狀態(tài)轉換,但是我已經(jīng)做好了所有基礎工作。我得到了一組需求。我還構建了一組測試。我的代碼可以為狀態(tài)機提供使用的數(shù)據(jù)。此時,管理單個狀態(tài)機轉換僅表示一次簡單的轉換,因此我將添加如清單 17 所示的測試:


    清單 17. 構建狀態(tài)機的狀態(tài)轉換
    it "should transition to :checking_out upon #trigger(:checkout) event " do
                @machine.trigger(:checkout)
                @machine.state.should == :checking_out
                end
                ~/rspec batate$ spec machine_spec.rb
                ...F
                1)
                NoMethodError in 'Machine should transition to :checking_out upon
                #trigger(:checkout) event '
                undefined method `trigger' for #<Machine:0x10c4d00>
                ./machine_spec.rb:24:
                Finished in 0.006153 seconds
                4 examples, 1 failure

    我需要抵制快速構建大量功能的誘惑。我應該只編寫少量代碼,只要使測試通過即可。清單 18 展示的迭代將表示 API 和需求。這就足夠了:


    清單 18. 定義 trigger 方法
    def trigger(event)
                @state = :checking_out
                end
                ~/rspec batate$ spec machine_spec.rb
                ....
                Finished in 0.005959 seconds
                4 examples, 0 failures

    這里出現(xiàn)了一個有趣的邊注。在編寫代碼時,我兩次都弄錯了這個簡單的方法。第一次我返回了 :checkout;第二次我將狀態(tài)設置為 :checkout 而不是 :checking_out。在測試中使用較小的步驟可以為我節(jié)省大量時間,因為測試用例為我捕獲的這些錯誤在將來的開發(fā)中很難捕獲到。本文的最后一個步驟是實際執(zhí)行一次狀態(tài)機轉換。在第一個示例中,我并不關心實際的機器狀態(tài)是什么樣子的。我僅僅是根據(jù)事件進行盲目轉換,而不考慮狀態(tài)。

    兩節(jié)點的狀態(tài)機無法執(zhí)行這個操作,我需要在第三個節(jié)點中構建。我沒有使用已有的 before 方法,只是在新狀態(tài)中添加另外的狀態(tài)。我將在測試用例中進行兩次轉換,以確保狀態(tài)機能夠正確地執(zhí)行轉換,如清單 19 所示:


    清單 19. 實現(xiàn)第一次轉換
    it "should transition to :success upon #trigger(:accept_card)" do
                @machine.events = {
                :checkout => {:from => :shopping, :to => :checking_out},
                :accept_card => {:from => :checking_out, :to => :success}
                }
                @machine.trigger(:checkout)
                @machine.state.should == :checking_out
                @machine.trigger(:accept_card)
                @machine.state.should == :success
                end
                ~/rspec batate$ spec machine_spec.rb
                ....F
                1)
                'Machine should transition to :success upon #trigger(:accept_card)' FAILED
                expected :success, got :checking_out (using ==)
                ./machine_spec.rb:37:
                Finished in 0.007564 seconds
                5 examples, 1 failure

    這個測試將使用 :checkout:accept_card 事件建立新的狀態(tài)機。在處理簽出時,我選擇使用兩個事件而不是一個,這樣可以防止發(fā)生雙命令。簽出代碼可以確保狀態(tài)機在簽出之前處于 shopping 狀態(tài)。第一次簽出首先將狀態(tài)機從 shopping 轉換為 checking_out。測試用例通過觸發(fā) checkoutaccept_card 事件實現(xiàn)兩個轉換,并在調用事件之后檢驗事件狀態(tài)是否正確。與預期一樣,測試用例失敗 — 我并沒有編寫處理多個轉換的觸發(fā)器方法。代碼修正包含一行非常重要的代碼。清單 20 展示了狀態(tài)機的核心:


    清單 20. 狀態(tài)機的核心
    def trigger(event)
                @state = events[event][:to]
                end
                ~/rspec batate$ spec machine_spec.rb
                .....
                Finished in 0.006511 seconds
                5 examples, 0 failures

    測試可以運行。這些粗糙的代碼第一次演變?yōu)檎嬲梢苑Q之為狀態(tài)機的東西。但是這還遠遠不夠。目前,狀態(tài)機缺乏嚴密性。不管狀態(tài)如何,狀態(tài)機都會觸發(fā)事件。例如,當處于 shopping 狀態(tài)時,觸發(fā) :accept_card 并不會轉換為 :success 狀態(tài)。您只能夠從 :checking_out 狀態(tài)觸發(fā) :accept_card。在編程術語中,trigger 方法的范圍應針對事件。我將編寫一個測試來解決問題,然后修復 bug。我將編寫一個負測試(negative test),即斷言一個不應該出現(xiàn)的行為,如清單 21 所示:


    清單 21: 負測試
    it "should not transition from :shopping to :success upon :accept_card" do
                @machine.events = {
                :checkout => {:from => :shopping, :to => :checking_out},
                :accept_card => {:from => :checking_out, :to => :success}
                }
                @machine.trigger(:accept_card)
                @machine.state.should_not == :success
                end
                rspec batate$ spec machine_spec.rb
                .....F
                1)
                'Machine should not transition from :shopping to :success upon :accept_card' FAILED
                expected not == :success, got :success
                ./machine_spec.rb:47:
                Finished in 0.006582 seconds
                6 examples, 1 failure

    現(xiàn)在可以再次運行測試,其中一個測試如預期一樣運行失敗。修復代碼同樣只有一行,如清單 22 所示:


    清單 22. 修復 trigger 中的范圍問題
    def trigger(event)
                @state = events[event][:to] if state == events[event][:from]
                end
                rspec batate$ spec machine_spec.rb
                ......
                Finished in 0.006873 seconds
                6 examples, 0 failures





    組合代碼

    現(xiàn)在,我具有一個可簡單運行的狀態(tài)機。無論從哪方面來說,它都不是一個完美的程序。它還具有下面這些問題:

    • 狀態(tài)散列實際上不具備任何功能。我應該根據(jù)狀態(tài)對事件及其轉換進行驗證,或者將所有狀態(tài)集中起來。后續(xù)需求很可能會要求這樣做。
    • 某個既定事件只能存在于一個狀態(tài)中。這種限制并不合理。例如,submitcancel 事件可能需要處于多個狀態(tài)。
    • 代碼并不具備明顯的面向對象特征。為使配置保持簡單,我將大量數(shù)據(jù)置入散列中。后續(xù)的迭代會進一步驅動設計,使其朝面向對象設計方向發(fā)展。

    但是,您還可以看到,這個狀態(tài)機已經(jīng)能夠滿足一些需求了。我還具備一個描述系統(tǒng)行為的文檔,這是進行一系列測試的好起點。每個測試用例都支持系統(tǒng)的一個基本需求。事實上,通過運行 spec machine_spec.rb --format specdoc,您可以查看由系統(tǒng)規(guī)范組成的基本報告,如清單 23 所示:


    清單 23. 查看規(guī)范
    spec machine_spec.rb --format specdoc
                Machine
                - should initially have a state of the first state
                - should remember a list of valid states
                - should remember a list of events with transitions
                - should transition to :checking_out upon #trigger(:checkout) event
                - should transition to :success upon #trigger(:accept_card)
                - should not transition from :shopping to :success upon :accept_card
                Finished in 0.006868 seconds

    測試驅動方法并不適合所有人,但是越來越多的人開始使用這種技術,使用它構建具有靈活性和適應性的高質量代碼,并且根據(jù)測試從頭構建代碼。當然,您也可以通過其他框架(如 test_unit)獲得相同的優(yōu)點。RSpec 還提供了優(yōu)秀的實現(xiàn)方法。這種新測試框架的一大亮點就是代碼的表示。新手尤其可以從這種行為驅動的測試方法中受益。請嘗試使用該框架并告訴我您的感受。

    posted on 2007-10-23 19:01 lzj520 閱讀(321) 評論(0)  編輯  收藏 所屬分類: RORagile
    主站蜘蛛池模板: 亚洲制服丝袜在线播放| 亚洲线精品一区二区三区影音先锋| 亚洲AV无码一区二区二三区软件 | 国产无遮挡又黄又爽免费网站| 浮力影院第一页小视频国产在线观看免费| 亚洲最大在线观看| 亚洲网站在线免费观看| 亚洲国产模特在线播放| 国产免费女女脚奴视频网| 亚洲免费二区三区| 成年性羞羞视频免费观看无限| 亚洲xxxx18| 国产伦精品一区二区三区免费下载| 精品亚洲成a人在线观看| 免费人成在线观看网站品爱网日本| 黄人成a动漫片免费网站| 亚洲男人的天堂一区二区| 久青草视频在线观看免费| 亚洲AV永久无码精品成人| 精品成在人线AV无码免费看| 亚洲国产精品白丝在线观看| 99久久综合国产精品免费| WWW亚洲色大成网络.COM| 国产成人麻豆亚洲综合无码精品| 成全视频免费观看在线看| 亚洲美免无码中文字幕在线| 免费无码肉片在线观看| 理论秋霞在线看免费| 亚洲精品午夜无码专区| 99久久精品日本一区二区免费| 亚洲日韩精品无码AV海量| 亚洲精品一级无码鲁丝片| 99久久精品免费视频| 亚洲精品乱码久久久久久蜜桃图片| 亚洲国产高清在线一区二区三区| 少妇人妻偷人精品免费视频| 亚洲综合色一区二区三区| 亚洲午夜日韩高清一区| 最近2019免费中文字幕6| 国产亚洲综合精品一区二区三区| 亚洲av之男人的天堂网站|