簡體   English   中英

如何對裝飾功能進行單元測試?

[英]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_numbersmult_numbers並不重要; 重要的是它們繼續按照定義工作:重新獲得所需操作的字符串化結果。

唯一剩下的測試,你需要寫一個驗證stringify完成打算做:返回裝飾功能作為一個字符串,你的結果test_stringify一樣。


您的問題似乎是您希望將展開的函數,裝飾器包裝函數視為單位。 但如果是這種情況,那么你就缺少一個單元測試:一個實際運行add_wrapper並測試其輸出的單元測試,而不僅僅是add_wrapper.__wrapped__ 如果你考慮將包裝函數作為單元測試或集成測試進行測試並不重要,但無論你怎么稱呼它,你都需要編寫它,因為正如你所指出的,僅測試未包裝的函數和裝飾師分開。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM