2010-04-12 107 views
3

我參加了一個TDD編碼Dojo,我們嘗試在簡單問題上練習純TDD。然而,我發現單元測試中出現的代碼並不是最有效的。現在大多數情況下都沒問題,但是如果代碼使用量增加,那麼效率就成了問題。如何通過單元測試產生高效的代碼?

我喜歡代碼從單元測試中出現的方式,但是有可能通過進一步的測試使效率屬性出現嗎?

這裏是一個紅寶石的例子:素因子分解。我遵循純粹的TDD方法,使得測試一個接一個地驗證我的原始驗收測試(在底部註釋)。 如果我想讓generic prime factorization algorithms之一出現,我還能採取哪些步驟?爲了減少問題領域,我們假設我想要實現一個quadratic sieve ...現在在這種精確的情況下,我知道「最優算法,但在大多數情況下,客戶端只會添加一個要求, x」的時間,特定的環境。

require 'shoulda' 
require 'lib/prime' 

class MathTest < Test::Unit::TestCase 
    context "The math module" do 
    should "have a method to get primes" do 
     assert Math.respond_to? 'primes' 
    end 
    end 
    context "The primes method of Math" do 
    should "return [] for 0" do 
     assert_equal [], Math.primes(0) 
    end 
    should "return [1] for 1 " do 
     assert_equal [1], Math.primes(1) 
    end 
    should "return [1,2] for 2" do 
     assert_equal [1,2], Math.primes(2) 
    end 
    should "return [1,3] for 3" do 
     assert_equal [1,3], Math.primes(3) 
    end 
    should "return [1,2] for 4" do 
     assert_equal [1,2,2], Math.primes(4) 
    end 
    should "return [1,5] for 5" do 
     assert_equal [1,5], Math.primes(5) 
    end 
    should "return [1,2,3] for 6" do 
     assert_equal [1,2,3], Math.primes(6) 
    end  
    should "return [1,3] for 9" do 
     assert_equal [1,3,3], Math.primes(9) 
    end   
    should "return [1,2,5] for 10" do 
     assert_equal [1,2,5], Math.primes(10) 
    end     
    end 
# context "Functionnal Acceptance test 1" do 
# context "the prime factors of 14101980 are 1,2,2,3,5,61,3853"do  
#  should "return [1,2,3,5,61,3853] for ${14101980*14101980}" do 
#  assert_equal [1,2,2,3,5,61,3853], Math.primes(14101980*14101980) 
#  end 
# end 
# end 
end 

和天真的算法,我創造了這個方法

module Math 
    def self.primes(n) 
    if n==0 
     return [] 
    else 
     primes=[1] 
     for i in 2..n do 
     if n%i==0   
      while(n%i==0) 
      primes<<i 
      n=n/i 
      end 
     end 
     end  
     primes 
    end 
    end 
end 

編輯1從第一個答案來看,我想我並不清楚我的初始描述:性能測試是不是是我單元測試的一個標準部分,它是一個新驗收測試編寫回答特定要求從客戶端。

編輯2我知道如何測試執行時間,但它似乎從簡單的算法轉移到優化的算法是一個巨大的步驟。我的問題是如何使最優代碼emerge,換句話說:你如何分解從平凡代碼到最優代碼的遷移? 一些人提到這是一個問題的具體方法:我提供了一個示例問題,我不知道如何繼續。

+2

會很好,但無論設計正確的代碼還是有效的代碼,單元測試都不會顯示出現的屬性。一個給定的單元測試可以通過許多非常**不同的實現來滿足。 – Ray 2010-04-14 01:18:24

+0

如果你對Ruby感到滿意,那麼你根本不在意perf :-)如果你真的關心perf,那麼你一定知道它必須通過爲perf設計來實現,這意味着思考需要很長的時間並且很努力。這使得TDD成爲了性能的致命的敵人 - 因爲TDD旨在專注於「今天爲我做了些什麼」並縮短了交付時間。 – ZXX 2010-08-08 15:14:03

+0

是不是「爲perf設計」最終形式過早優化? :) 此外,我在例子中使用紅寶石,因爲它很容易閱讀。這只是爲了例子,而不是我的問題的對象。 – Jean 2010-08-08 17:57:10

回答

4
  • TDD正確性非迴歸並專注於單元測試
  • 仿形性能,這是一個功能測試問題。

我也用來參加每週的TDD編碼Dojo,我們嘗試了一些實驗,看看它是否可以用於算法目的(找到更好的算法,找到沒有明顯的算法)或者內置的性能限制。

當道場使用TDD,我們儘量遵守規則之下

  • 寫打破現有的代碼最簡單的測試(或支付的啤酒,如果它不破碼)
  • 編寫最簡單代碼,使測試添加新的測試
之前加入測試
  • 也重構測試之前通過
  • 重構代碼(使用代碼味道)

    根據這些規則,我們比第一眼就可以看到更多的實驗空間。我們可以調整最簡單的定義並添加代碼異常來考慮效率(基本上來說:如果我們想到幾種實現某些東西的簡單方法,它們更喜歡效率最高的方法,並且如果我們知道一些更高效但仍然簡單的方法 - 算法比我們的代碼中使用的是一種氣味)。

    總的來說,結果是TDD本身不適合預測整體代碼的性能,並且從開始就實現高效的代碼,即使使用TDD和重構,我們成功實現了對代碼的更好洞察,並且可以增強它以獲得更好的可讀性避免了一些明顯的性能瓶頸。試圖在測試級別的代碼中插入性能約束通常是災難性的(我們得到的代碼和測試太複雜,經常代碼破壞或者更改太複雜)。

    其中一個原因是TDD我們通常使用非常小的測試集(最簡單的測試失敗)。另一方面,真正的數據集會出現更多的性能問題,並且與上述規則相吻合。性能測試,即使在正式的單元測試中,也是更相似的功能測試。通常的優化策略包括添加緩存,或者考慮到實際數據分佈的某些屬性,或者當某些小的優點特性對性能有很大的負面影響時,關聯用戶故事中的更改。所有這些都不能真正在TDD中內置,但更有可能在分析代碼時發現。

    我相信表演目標基本上是一個功能測試的問題。

  • +0

    非常感謝您的貢獻,這絕對是我正在尋找的答案對於。 – Jean 2010-04-18 20:15:01

    +0

    我決定選擇你的答案作爲我的問題的最佳答案。不過,我建議閱讀這篇文章的讀者也準備好Gishu的答案,因爲它增加了這個答案,請參閱 – Jean 2010-04-18 20:36:05

    3

    不,你不應該嘗試。單元測試測試的正確性,而不是效率 - 迫使他們測試效率是一種過早優化的一種形式。

    +0

    注意我提到**進一步**測試。 你如何定義正確性?我認爲它是:「通過客戶的要求」(驗收測試)。 如果客戶明確要求功能運行得更快(這是我最近在工作中遇到的情況)會怎麼樣? 我有關於如何編寫一個檢查代碼在時間範圍內運行的測試的想法,即使是自動化測試也是如此。 但是我將編寫的代碼使測試通過不會出現,我將不得不預期某種形式的代碼實際上比另一種更快...... – Jean 2010-04-12 21:37:29

    1

    在您的單元測試代碼中,您可以添加測量目標代碼已用時間的代碼。僞代碼將如下所示:

    start_time = date_time_now(); 
    Math.primes(1000); 
    stop_time = date_time_now(); 
    assert stop_time-start_time < target_execution_time; 
    

    某些單位文本框架可能已經有可用的時間,您可以參考。這使得額外的樣板代碼無需測量時間。

    此外,elapsed_time只是要使用的「效率」指標的一個示例。其他要測試的指標包括cpu_time,吞吐量,傳輸的輸入/輸出字節等。

    +0

    雖然我可以測量執行時間,但這不會使高效的代碼出現。它會創建一個失敗的測試,但並不表明「快速」代碼應該是什麼樣子。再次,你將如何使二次篩的代碼出現?它不能簡單地來自時間測量。 – Jean 2010-04-13 05:48:56

    0

    進行效率測試。您給出了這樣的要求:「對於給定的環境,該功能的運行時間少於」x「時間。」 編寫一個測試執行時間的測試。如果你通過了測試,那麼如果失敗了,就不需要進一步的代碼增強,通過分析和微觀選擇或算法增強來加快速度。

    我不得不同意BlueRaja性能測試不應該是單元測試的標準部分,儘管如果重視性能,它可以幫助保持它在桌面上。

    2

    單元測試通常檢查零散的功能正確性。

    你當然可以添加時序約束到一個單元測試,但你可能很難有時間與技術無關的方式表達出來(例如,O(N LN N)。

    而且,僅僅因爲你可以寫一個堅持要在一定時間內交付結果的單元測試並不意味着該單元的編碼器必然能夠找到一個達到這種效果的算法(當然,他可能無法想到如何正確實現功能,

    如果你這樣做,我會建議隔離功能測試和性能測試 然後至少你可以告訴你代碼在你deci之前是否工作表現確實很糟糕。

    +0

    查看包含的示例,我有功能測試正常。但是「驗收測試」在我的筆記本電腦上運行了超過半個小時,使用100%的CPU。作爲客戶端,我希望該功能的運行速度至少提高10%。 我的問題是,當面對效率要求時,使高效算法出現。你如何將效率要求分解爲單個步驟? 也許這是不可能的,我當然不知道如何去做,但我不知道這一切是什麼,所以我問:) – Jean 2010-04-12 21:47:25

    +0

    你有兩個問題:1)你可以單元測試期望的性能表現(答案基本上是肯定的)2)你能設計一個單元來通過你有代碼的測試嗎(答案是,取決於問題和你的技能水平,而與單元測試無關)。實現某種效果(功能或性能)的一個標準技巧是將其分解成更小的可能步驟,每個步驟都實現了所需效果的一部分(「分而治之」)。在完成基本建議之後,您需要具體問題的方法來實現它。 – 2010-04-13 02:33:03

    +0

    其實我只有一個問題,它指的是使用單元測試時出現的代碼質量。代碼結構應該顯示爲應用一系列測試的結果。從平凡的算法遷移到二次篩選我提到並不是微不足道的任務,它看起來像一個巨大的步驟,但我看不到中間步驟是什麼。我非常確定分而治之對此無濟於事。這就是爲什麼我給出了一個具體問題:從一個微不足道的素數分解器遷移到一個非平凡的分解器。 – Jean 2010-04-13 05:51:57

    0

    我起初雖然這不起作用,你需要一個比TDD更大的飛躍,而且我會說至少你的測試會在你重寫你的代碼時幫助你。

    但是,您應該嘗試讓我們知道。雖然我還沒有做到,但我認爲你的下一個測試應該是的一次性能測試。這顯然是這個時候的要求,所以爲什麼不以同樣的方式繼續下去。

    我認爲你可以編寫一個能夠在任何平臺上可靠運行的基礎。你需要一些基礎設施,以幫助你,但它可以看起來像:

    TEST: should be faster than O(n^2) 
    setup: baseline_time_for_10 = time_of(f(10)) 
    100: assert time_of(f(100)) < baseline_time_for_10^2  
    etc. 
    

    我一直想做到這一點,但還沒有一個項目有合適的機會。讓我們知道怎麼回事。

    +0

    就是這樣的事情:我試過但我找不到「下一步」。我認爲gishu和kriss幾乎是釘了它(現在我在選擇其中一個答案,因爲他們都以有趣的方式提供... ...)之間撕裂...) – Jean 2010-04-18 20:31:02

    3

    TDD不能幫你推導算法 - 如果那是你的問題。這是其中一個不適合TDD優勢的利基領域(利基:與成千上萬框架/庫中的企業軟件相比)。

    沒有阻止你繼續使用TDD - 你可能會寫一個性能測試,但是你的目標規格是什麼?如果有可能減半你的規格呢?這些問題不能在不分析代碼的情況下以正確的方式進行回答。去測試驅動不會回答這些問題;最多它會給你一個安全網,以檢測你是否破壞了現有的代碼。

    例如你可以用TDD來實現排序,但是你找到一個新算法或者到達一個像quicksort這樣的現有高效算法的機會是黯淡的。除非你知道算法設計的原則,並有意識地通過它的方式。

    更新:支持證據。 http://www.markhneedham.com/blog/2009/12/10/tdd-big-leaps-and-small-steps/ 還有一些 - 但他們就像討論reddit。自以爲是的不受支持的免費爲所有人。不發佈這些。

    +0

    感謝您的回答。像往常一樣,我的主要問題是溝通問題,畢竟我認識到存在一個問題,就是無法在上面寫出正確的文字。你顯然在那裏幫了忙。 – Jean 2010-04-18 20:29:06