4

我最近遇到了一個簡單但討厭的錯誤。 我有一個列表,我想找到它中最小的成員。我使用Python的內置min()。 一切工作很好,直到在一些奇怪的情況下列表是空的(由於奇怪的用戶輸入,我不能預料)。我的應用程序崩潰了ValueError(順便說一句 - 沒有在官方文檔中記錄)。Python在覆蓋失敗時對異常進行預測試

我有非常廣泛的單元測試,我經常檢查覆蓋範圍,以避免這樣的驚喜。我也使用Pylint(所有東西都集成在PyDev中),我從不忽略警告,但是在我的用戶之前我沒有發現這個錯誤。

有沒有什麼我可以改變在我的方法避免這些類型的運行時錯誤? (這在編譯時會被Java/C#捕獲?)。

我正在尋找一些東西,而不是用大的嘗試來包裝我的代碼。我還可以做些什麼?有多少其他的Python函數構建隱藏這樣的令人討厭的驚喜?

+0

要澄清一點:我不認爲最小/最大應該返回None或0.在這種情況下,他們提出異常是有道理的。我只是擔心我只是通過我的sw發現了一條隱藏的路線,我並不知道也沒有測試過......而且代碼與用戶遠離:這是一個自然語言旅行請求我提取所有數據。在某些時候,我試圖從文本中找到100公里半徑範圍內最大的城市,但是用戶進入了澳大利亞的一個機場,並在其旁邊有0個城市......我的不好,當然...... – 2010-04-15 20:59:59

+0

潛在答案:如果我沒有將它傳遞給空列表導致它引發異常,請改進覆蓋率以顯示此行的部分覆蓋範圍!這樣它會在覆蓋報告中彈出,我會寫一個測試來覆蓋它。這是可行的嗎? – 2010-04-16 06:07:27

回答

5

即使在Java/C#中,RuntimeError也沒有被選中,編譯器也不會檢測到這種異常(這就是爲什麼它們被稱爲RuntimeError而不是CompileError)的原因。

在python中,某些異常(如KeyboardInterrupt)特別毛茸茸,因爲它可以在程序中的任意點上實際引發。

我正在尋找一些東西,而不是用大的嘗試來包裝我的代碼 - 除了。

任何事情,但請。讓異常得到用戶並停止程序,而不是讓錯誤靜靜地傳遞(Python的禪)是更好的。

與Java不同,Python不需要捕獲所有異常,因爲要求捕獲所有異常使得程序員很容易忽略異常(通過編寫空白異常處理程序)。

只是放鬆,讓錯誤停止;讓用戶向你報告,這樣你就可以修復它。另一種選擇是你在四十二小時內進入一個調試器,因爲由於空白的強制性異常處理程序,客戶的數據正在被破壞。

所以,你應該改變你的方法是認爲異常是壞的;他們並不漂亮,但他們比替代品更好。

+0

我同意100%,這是我使用的方法。但 - 我會喜歡有一個覆蓋工具,讓我運行我的單元測試,然後告訴我,我從來沒有經歷過我的代碼的某些路徑(例如這個例外)。 – 2010-08-13 12:40:40

0

我不知道你的問題的直接答案;如果pylint警告這種可能性,我也會喜歡它。由於空列表在各種情況下都會造成問題,我的一般做法是在使用之前查看真相清單;例如:

val = min(vals) if vals else 0 

在許多情況下,這是「免費」的,因爲你經常需要檢查None反正。它還可以對特殊情況下的空列表進行性能優化,以避免即開始新的線程,進程或數據庫事務來處理零個項目。

+1

沒有downvoting,但如果列表是[[1,0,3,2,5]'?您現在沒有辦法(在您的構造中)區分有效的最小值和由空列表引起的值。 – ChristopheD 2010-04-15 18:44:44

+0

@ChristopheD:這就是主意;經常,尤其是在這裏用* min *,空或者甚至沒有列表不是真的有問題。在這種情況下,經常會有一些值,比如零。我只是在編寫代碼時儘量瞭解這一點,就像我在處理None時一樣。不幸的是,「更加警惕」僅僅是迄今爲止,所以我承認這不是對這個問題的真實答案。 – DNS 2010-04-15 18:53:50

+1

有關於將哨兵添加到最小/最大http://bugs.python.org/issue7153的討論,但由於各種原因他們被拒絕。 – 2010-04-15 19:03:50

7

這裏的問題是格式錯誤的外部輸入使程序崩潰。解決方案是徹底地在代碼邊界單元測試可能的輸入場景。你說你的單元測試是'廣泛的',但你顯然沒有測試過這種可能性。代碼覆蓋率是一個有用的工具,但重要的是要記住覆蓋代碼是而不是,就像徹底測試它一樣。徹底的測試是涵蓋使用場景以及代碼行的組合。

我使用的方法是信任內部呼叫者,但從不信任外部呼叫者或輸入。所以我明確不要單元測試的空列表情況下超出第一個函數接收外部輸入的任何代碼。但是那個輸入函數應該是窮舉覆蓋。

在這種情況下,我認爲圖書館的例外是合理的行爲 - 要​​求min是空的列表是沒有意義的。例如,圖書館無法爲您合理設置一個值爲0的值,因爲您可能正在處理負數。

我認爲空列表應該永遠不會到達請求min的代碼 - 它應該在輸入時被識別出來,並且在那裏引發異常,或者將其設置爲0(如果這對您有用)或者其他任何其他這是對你有用。

+0

關於處理自然語言的有趣之處在於,你無法在任何地方盡力覆蓋潛在的投入。這是我必須面對的現實。我對圖書館的行爲沒有任何問題(除了沒有記錄的事實!)。 – 2010-08-13 12:36:45

1

您也可以使用隨機測試:

#!/usr/bin/env python 
import random 
from peckcheck import TestCase, an_int, main 

def a_seq(generator): 
    return lambda size: [generator(size) 
         for _ in xrange(random.randrange(size))] 

class TestMin(TestCase): 
    def testInputNoThrow(self, x=a_seq(an_int)): 
     min(x) 

if __name__=="__main__": 
    main() 

要安裝peckcheck,類型:

$ pip install http://github.com/downloads/zed/peckcheck/peckcheck-0.1.v2.6.tar.gz 

或者只是grub的peckcheck.py

+0

我真的不是這個粉絲,因爲你的測試依賴於隨機性。您可以通過提供種子來使用僞隨機性:http://stackoverflow.com/questions/9023660/how-to-generate-a-repeatable-random-number-sequence – seanp2k 2014-06-09 21:28:56

+0

@ seanp2k:您錯過了隨機測試的要點。如果你想要一個確定性測試(*除了隨機測試*),那麼你也可以硬編碼測試的輸入。 – jfs 2014-06-10 11:33:15