2013-03-08 83 views
9

這裏是龍。你已被警告。Python運行之前覆蓋默認的類型()元類

我正在考慮創建一個新庫,它將嘗試幫助編寫更好的測試套件。
爲了做到這一點,功能之一是驗證正在使用的任何不是測試運行器和system under test的對象都有一個測試對象(模擬對象,存根,僞造或虛擬對象)。如果測試者需要活動對象並因此減少測試隔離,則必須明確指定。

我看到要做到這一點的唯一方法是覆蓋默認元類的內建函數type()
新的默認元類將檢查測試雙註冊表字典以查看它是否已被替換爲測試double或者是否指定了活動對象。

當然,這是不可能通過Python本身:

>>> TypeError: can't set attributes of built-in/extension type 'type' 

有沒有辦法用Python的元類查找干預前的測試套件運行(也可能是Python的)?
也許使用字節碼操作?但究竟如何?

+0

這甚至是可取的嗎?你說「每個對象」。你知道整數和字符串是對象嗎? Python中的每個值都是一個對象。不清楚的是,你可以自動分離有趣的對象進行審查。爲什麼每個物體都要加倍呢?你怎麼知道被測系統是什麼? – 2013-03-08 11:51:27

+0

@NedBatchelder我知道這個問題會來。當你正在進行單元測試時,你需要確保你只測試一個**單元**。必須嘲笑該單位的相關性才能做到這一點。當然,整數和字符串不會被嘲笑,但是每個與它們有關的方法(例如str.split)都會被嘲笑。簡而言之,在可能的情況下會有例外和合理的雙打。開發者將指定被測試的系統是什麼。 – 2013-03-08 12:11:06

+4

我認爲嘲笑str.split是矯枉過正的。嘲笑某事的原因是因爲你想將自己與它隔離開來,因爲你不相信它,或者它是不可預測的,或者它太慢了。 Str.split不屬於任何這些類別。 – 2013-03-08 13:05:44

回答

9

以下是不可取的,而且你會打很多的問題和cornercases實現你的想法,但關於Python 3.1及以後,你可以通過重寫掛接到定製類的創建過程中,__build_class__內置掛鉤:

import builtins 


_orig_build_class = builtins.__build_class__ 


class SomeMockingMeta(type): 
    # whatever 


def my_build_class(func, name, *bases, **kwargs): 
    if not any(isinstance(b, type) for b in bases): 
     # a 'regular' class, not a metaclass 
     if 'metaclass' in kwargs: 
      if not isinstance(kwargs['metaclass'], type): 
       # the metaclass is a callable, but not a class 
       orig_meta = kwargs.pop('metaclass') 
       class HookedMeta(SomeMockingMeta): 
        def __new__(meta, name, bases, attrs): 
         return orig_meta(name, bases, attrs) 
       kwargs['metaclass'] = HookedMeta 
      else: 
       # There already is a metaclass, insert ours and hope for the best 
       class SubclassedMeta(SomeMockingMeta, kwargs['metaclass']): 
        pass 
       kwargs['metaclass'] = SubclassedMeta 
     else: 
      kwargs['metaclass'] = SomeMockingMeta 

    return _orig_build_class(func, name, *bases, **kwargs) 


builtins.__build_class__ = my_build_class 

這僅限於自定義類只有,但確實給你一個全能的鉤。

對於3.1之前的Python版本,您可以忘記掛鉤創建類。如果沒有定義元類,則C build_class function直接使用C型type()值,但它從不從__builtin__模塊查找它,因此無法覆蓋它。

+0

你的意思是我會有很多的實現問題,或者我會有真正的代碼分裂? – 2013-03-13 17:52:07

+2

@ the_drow:我的意思是,當你走這條路時,你會發現許多拐角案例和其他問題,所以這將是一個實施挑戰。我同意奈德的觀點,認爲嘲笑應該是明確的,而不是像你所建議的那樣是隱含的和自動化的。但至少你現在知道了槍支儲物櫃的位置,我只希望你不要在腳下自己射擊。 :-) – 2013-03-13 17:55:03

+0

@Martin你有沒有使用過Django?曾經嘗試過爲它編寫單元測試嗎?嘲笑數據庫API是一場噩夢。如果你有一個工具可以自動爲你做,並且在你觸摸數據庫時拋出一個異常,如果你沒有嘲笑你自己需要的位,那麼你實際上有一個真正簡單的方法來編寫實際檢查一個單元的單元測試正確。 – 2013-03-13 18:20:55

2

我喜歡你的想法,但我認爲你會稍微離開課程。如果代碼調用庫函數而不是類,該怎麼辦?你的假類型()永遠不會被調用,你永遠不會被建議你沒有嘲笑這個庫函數。在Django和任何實際的代碼庫中都有很多實用功能。

我建議您以Python源代碼的形式編寫您需要的解釋程序級支持。或者你可能會發現將這樣一個鉤子添加到PyPy的代碼庫更容易,該代碼庫是用Python自己編寫的,而不是用Python的C源代碼搞亂。

我剛剛意識到,Python解釋器包含一套全面的工具,可以使任何一段Python代碼都可以逐步執行任何其他代碼段,檢查每段函數調用的內容,甚至是如果需要的話,每條Python線都將被執行。

sys.setprofile應該足以滿足您的需求。有了它,你可以安裝一個鉤子(一個回調函數),它將被通知目標程序正在進行的每個函數調用。你不能用它來改變目標程序的行爲,但你可以收集它的統計數據,包括你的「模擬覆蓋率」指標。

關於Profilers的Python文檔介紹了一些基於sys.setprofile構建的模塊。你可以研究他們的來源,看看如何有效地使用它。

如果結果不夠好,仍然存在sys.settrace,這是一種嚴格的方法,允許您遍歷目標程序的每一行,檢查其變量並修改其執行。標準模塊bdb.py構建於sys.settrace之上,並實現了一組標準調試工具(斷點,步入,跳過等),由命令行調試器pdb.py以及其他圖形調試器使用。

有了這兩個鉤子,你應該沒問題。

+0

問題是測試套件應該能夠在許多python版本中運行,而不僅僅是PyPy。我其實考慮過這個。我也不想碰C代碼。我真的沒有答案給你。這正是我問的原因。你能想出一種方法來確保功能也被嘲笑嗎? – 2013-03-17 01:32:43

+1

你說得對。閱讀我更新的答案,我想我找到了「正確」的解決方案。 – Tobia 2013-03-17 02:18:14

+0

雖然我們在這裏正確的方向,settrace將阻止調試,這是你可能想在單元測試時要做的事情。如何使用路徑掛鉤(http://docs.python.org/2/library/sys.html#sys.path_hooks)並實現我自己的finder,它將導入我的可調用對象,如果該對象不在我的嘲弄對象註冊?它會起作用嗎? – 2013-03-17 04:52:31