簡體   English   中英

如何測試 function 呼叫范圍是否在 python 內?

[英]How to test if a function calls range in python?

我是 Python 講師,我想給我的學生一個任務:編寫一個 function,使用 for 循環計算列表的平均值,范圍為 object。

我想對他們的 function 進行測試,看看它是否實際使用范圍 object。我該怎么做?

它應該是這樣的:

def avg(L):
    Pass

def test_range(avg):
    ...

如果avg包含range ,那么test_range應該返回True

我嘗試了利用func_code的解決方案,但顯然range沒有。

您可以使用 Python 的unittest.mock模塊從builtins模塊環繞range function,然后讓您的測試斷言環繞的range確實被調用了。

例如,使用 Python 的unittest框架編寫測試:

import builtins
import unittest
from unittest.mock import patch

# I don't know what L is supposed to be, and I know that there 
# are better ways to compute average of a list, but the code
# for calculating the average is not important for this question.
def avg(L):
    total = 0
    for index in range(len(L)):
        total += L[index]
    return total / len(L)

class TestAverage(unittest.TestCase):
    def test_avg(self):
        with patch("builtins.range", wraps=builtins.range) as wrapped_patch:
            expected = 47
            actual = avg([1,49,91])
            self.assertEqual(expected, actual)
        wrapped_patch.assert_called()

if __name__ == '__main__':
    unittest.main()
$ python -m unittest -v main.py
test_avg (main.TestAverage) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

它使用unittest.mock的針對 builtins.range builtins.rangepatch 。通常, patch用於測試以替換目標的行為和/或返回值,但在這種情況下,您可以傳遞wraps=builtins.range (這被傳遞給底層的Mock對象),這意味着“我只想監視調用,但不想修改它的行為”:

wraps :用於模擬 object 包裝的項目。 如果wraps不是None那么調用 Mock 會將調用傳遞給包裝的 object(返回真實結果)。

通過將其包裝在Mock object 中,您可以使用Mock的任何斷言函數來檢查對range的調用,例如assert_called檢查目標是否至少被調用一次。 您可以通過斷言調用range的次數來更具體:

self.assertTrue(wrapped_patch.call_count == 1)

如果根本不調用斷言,斷言就會失敗:

# Here, `range` wasn't used at all.
def avg(L):
    return sum(L) / len(L)

class TestAverage(unittest.TestCase):
    # same as the code above
$ python -m unittest -v main.py
test_avg (main.TestAverage) ... FAIL

======================================================================
FAIL: test_avg (main.TestAverage)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/path/to/main.py", line 15, in test_avg
    wrapped_patch.assert_called()
  File "/usr/local/Cellar/python@3.10/3.10.8/Frameworks/Python.framework/Versions/3.10/lib/python3.10/unittest/mock.py", line 888, in assert_called
    raise AssertionError(msg)
AssertionError: Expected 'range' to have been called.

使用patch時要注意的最重要的事情是要確切地知道在哪里打補丁 在這種情況下,您可以查看文檔或使用__module__來了解range的模塊:

>>> range
<class 'range'>
>>> range.__module__
'builtins'

但是測試有點幼稚,因為即使avg沒有真正使用range ,它仍然可以通過:

def avg(L):
    range(len(L))  # Called but really unused. Sneaky!
    return sum(L) / len(L)

class TestAverage(unittest.TestCase):
    # same as the code above
$ python -m unittest -v main.py
test_avg (main.TestAverage) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

一個稍微令人困惑的解決方法是“打破” range的測試,如果 function真的使用range ,那么它將不再起作用:

def avg(L):
    range(len(L))  # Called but really unused. Sneaky!
    return sum(L) / len(L)

class TestAverage(unittest.TestCase):
    def test_avg(self):
        # same as above

    def test_avg_is_really_using_range(self):
        L = [10,20,90]
        # Is it returning the correct result?
        self.assertEqual(avg(L), 40)

        # OK, but did it really use `range`?
        # Let's try breaking `range` so it always yields 0,
        # so we expect the return value to be *different*
        with patch("builtins.range", return_value=[0,0,0]):
            self.assertNotEqual(avg(L), 40)

因此,如果avg偷偷調用但沒有真正使用range ,那么test_avg_is_really_using_range現在會失敗,因為即使range被破壞, avg仍然會產生正確的值:

$ python -m unittest -v main.py
test_avg (main.TestAverage) ... ok
test_avg_really_using_range (main.TestAverage) ... FAIL

======================================================================
FAIL: test_avg_really_using_range (main.TestAverage)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/path/to/main.py", line 19, in test_avg_really_using_range
    self.assertNotEqual(avg(L), 40)
AssertionError: 40.0 == 40

最后,作為旁注,我在所有示例中都使用了assertEqual ,因為返回值的測試不是重點,但請務必閱讀斷言可能的浮點值的正確方法,例如。 如何對浮點輸出執行單元測試? - python

暫無
暫無

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

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