[英]How to unit-test decorated functions?
我最近嘗試過對單元測試最佳實踐進行大量訓練。 其中大部分內容都非常有意義,但有一些內容經常被忽視和/或解釋得很糟糕:一個單元測試如何裝飾功能?
我們假設我有這個代碼:
def stringify(func):
@wraps(func)
def wrapper(*args):
return str(func(*args))
return wrapper
class A(object):
@stringify
def add_numbers(self, a, b):
"""
Returns the sum of `a` and `b` as a string.
"""
return a + b
我顯然可以寫下面的測試:
def test_stringify():
@stringify
def func(x):
return x
assert func(42) == "42"
def test_A_add_numbers():
instance = MagicMock(spec=A)
result = A.add_numbers.__wrapped__(instance, 3, 7)
assert result == 10
這給了我100%的覆蓋率:我知道用stringify()
修飾的任何函數都將其結果作為字符串獲取,並且我知道未修飾的A.add_numbers()
函數返回其參數的總和。 因此,通過傳遞性, A.add_numbers()
的修飾版本必須以字符串形式返回其參數的總和。 一切都好看!
但是我對此並不完全滿意:我的測試,正如我寫的那樣,如果我要使用另一個裝飾器(那就做其他事情,比如將結果乘以2而不是轉換為str
),我的測試仍然可以通過。 我的函數A.add_numbers
不再正確,但測試仍然會通過。 不太棒。
我可以測試A.add_numbers()
的裝飾版本但是我會因為我的裝飾器已經過單元測試而A.add_numbers()
。
感覺我在這里遺漏了一些東西。 單元測試裝飾功能的好策略是什么?
我最后將裝飾師分成兩部分。 所以不要:
def stringify(func):
@wraps(func)
def wrapper(*args):
return str(func(*args))
return wrapper
我有:
def to_string(value):
return str(value)
def stringify(func):
@wraps(func)
def wrapper(*args):
return to_string(func(*args))
return wrapper
這允許我稍后在測試裝飾函數時簡單地模擬出to_string
。
顯然在這個簡單的例子中它可能看起來有點過分,但是當在裝飾器上使用時實際上做了復雜或昂貴的事情(比如打開與DB的連接,或者其他),能夠模擬出來是一件非常好的事情。
測試代碼的公共接口 。 如果你只希望人們調用裝飾函數,那么你應該測試它。 如果裝飾器也是公共的,那么也要測試它(就像你使用test_stringify()
)。 除非人們直接調用它們,否則不要測試包裝版本。
單元測試的主要好處之一是允許重構具有一定程度的信心,即重構代碼繼續像以前一樣工作。 假設你已經開始了
def add_numbers(a, b):
return str(a + b)
def mult_numbers(a, b):
return str(a * b)
你會有一些測試
def test_add_numbers():
assert add_numbers(3, 5) == "8"
def test_mult_numbers():
assert mult_numbers(3, 5) == "15"
現在,您決定使用stringify
裝飾器重構每個函數的公共部分(將輸出包裝在一個字符串中)。
def stringify(func):
@wraps(func)
def wrapper(*args):
return str(func(*args))
return wrapper
@stringify
def add_numbers(a, b):
return a + b
@stringify
def mult_numbers(a, b):
return a * b
您會注意到在重構之后您的原始測試仍然有效。 你如何實現add_numbers
和mult_numbers
並不重要; 重要的是它們繼續按照定義工作:重新獲得所需操作的字符串化結果。
唯一剩下的測試,你需要寫一個驗證stringify
完成它打算做:返回裝飾功能作為一個字符串,你的結果test_stringify
一樣。
您的問題似乎是您希望將展開的函數,裝飾器和包裝函數視為單位。 但如果是這種情況,那么你就缺少一個單元測試:一個實際運行add_wrapper
並測試其輸出的單元測試,而不僅僅是add_wrapper.__wrapped__
。 如果你考慮將包裝函數作為單元測試或集成測試進行測試並不重要,但無論你怎么稱呼它,你都需要編寫它,因為正如你所指出的,僅測試未包裝的函數和裝飾師分開。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.