【问题标题】:Python Unit Test for Try/Except BlockTry/Except 块的 Python 单元测试
【发布时间】:2020-01-18 13:56:33
【问题描述】:

我有以下单元测试功能:

#!/usr/bin/env python3
# https://docs.python.org/3/library/ipaddress.html
# https://docs.python.org/3.4/library/unittest.html

import ipaddress
import unittest

from unittest.mock import patch
from unittest import TestCase

def validate_IP():
    """Prompt user for IPv4 address, then validate."""

    while True:
        try:
            return ipaddress.IPv4Address(input('Enter a valid IPv4 address: '))
        except ValueError:
            print('Bad value, try again.')


class validate_IP_Test(unittest.TestCase):

    @patch('builtins.input', return_value='192.168.1.1')
    def test_validate_IP_01(self, input):
        self.assertIsInstance(validate_IP(), ipaddress.IPv4Address)

    @patch('builtins.input', return_value='10.0.0.1')
    def test_validate_IP_02(self, input):
        self.assertIsInstance(validate_IP(), ipaddress.IPv4Address)

    @patch('builtins.input', return_value='Derp!')
    def test_validate_IP_03(self, input):
        self.assertRaises(ValueError, msg=none)


if __name__ == '__main__':
    unittest.main()

该函数使用 Python3 中的 ipaddress 模块来验证用户输入,即检查用户输入是否为实际的 IPv4 address。我的前两个测试按预期工作。不过,我不清楚如何使用 Python3 的 unittest 模块对函数的异常部分测试无效输入,如第三次测试。

当输入无效输入时,测试应该识别出异常被抛出并通过测试。作为参考,这是我输入无效地址时解释器的相关输出:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File 
raise AddressValueError("Expected 4 octets in %r" % ip_str)
ipaddress.AddressValueError: Expected 4 octets in 'derp'`

【问题讨论】:

  • 无效输入的可见行为是请求了新输入。它是通过捕获异常来完成的,这是一个实现细节,您应该能够在不破坏测试的情况下进行更改。

标签: python unit-testing ip-address try-except


【解决方案1】:

您可以使用assertRaises 方法作为上下文管理器:

@patch('builtins.input', return_value='Derp!')
def test_validate_IP_03(self, input):
    with self.assertRaises(ValueError):
        validate_IP()

但是,您的 validate_IP 函数会在无限循环中捕获异常本身,因此上述测试实际上不会通过。如果您打算能够在调用之外捕获异常,则可以使其不重试,并在输出错误消息后重新引发异常:

def validate_IP():
    try:
        return ipaddress.IPv4Address(input('Enter a valid IPv4 address: '))
    except ValueError:
        print('Bad IPv4 address.')
        raise

【讨论】:

  • 您的第一个示例是我尝试运行的一个测试,但是,正如您所提到的,我陷入了无限循环。你的第二个例子符合我的意图。谢谢。
【解决方案2】:

异常未被识别,因为您捕获了它,您不需要捕获它或重新引发它。要重新加注,只需在您的示例中在 catch 块内调用 raise 而不使用参数:

 def validate_IP():
    """Prompt user for IPv4 address, then validate."""

    while True:
        try:
            return ipaddress.IPv4Address(input('Enter a valid IPv4 address: '))
        except ValueError:
            print('Bad value, try again.')
            raise

这会让你跳出while,让它变得毫无意义。我会将其移至另一种方法或功能,以便您可以单独测试 IPv4Address 提升行为。

另一个问题是在函数内部调用input,对于测试来说非常烦人。我会选择def validate_ip(ip):,更容易测试。

问候,

【讨论】:

    【解决方案3】:

    无效输入的可见行为是请求了新输入。它是通过捕获异常来完成的,这是一个测试最好不要关心的实现细节,这样您就可以在不破坏测试的情况下更改该实现细节。您问题中的回溯是IPv4Address 会做什么,而不是validate_IP() 会做什么。

    此外,当地址无效时,您期望 AddressValueError,捕获 ValueError 可能会隐藏其他意外异常并使调试更加困难,因此您最好捕获更具体的异常:

    def validate_IP():
        """Prompt user for IPv4 address, then validate."""
    
        while True:
            try:
                return ipaddress.IPv4Address(input('Enter a valid IPv4 address: '))
            except ipaddress.AddressValueError:
                print('Bad value, try again.')
    

    您可能希望测试说明的一个故事是:如果输入流包含无效 IP,然后是有效 IP,我们会丢弃前者并返回后者。

    @patch('builtins.input', side_effect=('Derp!', '10.0.0.1'))
    def test_invalid_IP(self, input):
        self.assertEqual(validate_IP(), ipaddress.IPv4Address('10.0.0.1'))
    

    顺便说一句,我建议您重新考虑名称,我认为 validate_IP 并没有真正说明函数在做什么,并且测试名称几乎没有意义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-26
      • 1970-01-01
      • 1970-01-01
      • 2021-10-26
      • 1970-01-01
      • 2019-09-01
      相关资源
      最近更新 更多