Skip to content

好用的 pytest 之 raises

📅 7/21/2024

範例

我們先看一段寫得很草率的 function,這 function 用於計算除法並且會回傳商數和餘數,且他會做一些基本輸入檢查;可惜的是他目前不想支援浮點數運算

python
class UnexpectError(Exception):
    def __init__(self, code, *args):
        self.code = code
        super().__init__(*args)


def divide(x, y):
    if type(x) is float or type(y) is float:
        raise UnexpectError(1001, '目前尚未支援浮點數運算')

    if isinstance(x, bool) or isinstance(y, bool):
        raise UnexpectError(1002, '請勿輸入布林值')

    x, y = int(x), int(y)

    if y == 0:
        raise ZeroDivisionError('除數不可以為 0')

    return x // y, x % y

接著下面是他的單元測試

python
@pytest.mark.parametrize(
    ('x', 'y', 'quotient', 'remainder'),
    [
        (4, 2, 2, 0),
        (5, 3, 1, 2),
        (1, 1, 1, 0),
        (3, 5, 0, 3),
        ('4', '2', 2, 0),
        ('3', '5', 0, 3),
        (3, '5', 0, 3),
    ],
)
def test_divide(x, y, quotient, remainder):
    q, r = divide(x, y)
    assert q == quotient, r == remainder

好像有些程式碼沒有測到

上面範例的 divide function 單元測試毫無意外的測試過了,也就代表著 x, y = int(x), int(y)return x // y, x % y 都沒問題, 不過 divide 還包含了 if 判斷的內容,這些內容在上面的單元測試就測不到了,但是故意寫符合 if 判斷的測項又會因為促發錯誤導致測試過不了; 也因為如此,目前的 coverage 只有 62%。

讓它出現錯誤吧

在這總情況下 pytest 有個好用的工具 pytest.raises,用於預期你的測試項會出現哪寫錯誤, 例如我下面範例就預期會出現 UnexpectErrorZeroDivisionError, 而我自定義的 UnexpectError 會有個屬性 code 代表錯誤代碼,我們可以在更進一步檢查是否會拿到我們預期的代碼

python
@pytest.mark.parametrize(
    ('x', 'y', 'error', 'code'),
    [
        (3, 0.3, UnexpectError, 1001),
        (0.01, 2, UnexpectError, 1001),
        (True, False, UnexpectError, 1002),
        (12, 0, ZeroDivisionError, None)
    ],
)
def test_divide(x, y, error, code):
    with pytest.raises(error) as e:
        divide(x, y)
    if code:
        assert e.value.code == code