PyTest 测试套件在 Python 中测试另一个线程的结果时断言应该失败时通过?
PyTest test-suite passing when asserts should fail when testing another thread's results in Python?
我目前正在使用 pytest 来测试现有的(unittest 测试套件 per the documentation)。我目前正在编写一个等待分配 IP 地址的线程,然后 returns 它到回调函数,我正在编写单元测试来伴随它。
这是我写的测试用例class。
class TestGetIpAddressOnNewThread(unittest.TestCase):
def test_get_existing_ip(self):
def func(ip):
assert ip == "192.168.0.1" # Not the real IP
# Even when I introduce an assert statement that should fail, test still passes
assert ip == "shouldn't be the ip"
ip_check = GetInstanceIpThread(instance, func)
ip_check.start()
ip_check.join()
if __name__ == '__main__':
pytest.main()
这里是 GetInstanceIpThread
伪定义:
class GetInstanceIpThread(threading.Thread):
def __init__(self, instance, callback):
threading.Thread.__init__(self)
self.event = threading.Event()
self.instance = instance
self.callback = callback
def run(self):
self.instance.wait_until_running()
ip = self.instance.ip_address
self.callback(ip)
当我 运行 这个测试用例使用 pytest path_to_file.py::TestGetIpAddressOnNewThread
时,它通过了(耶!)但即使我引入了应该 100% 失败的断言语句(嘘!)。出了什么问题,我该如何编写实际失败的测试?
所以我正在回答我自己的问题,因为虽然我能够在 Whosebug 上找到答案,但它没有任何有用的关键字,因为大多数答案都在谈论如何使用 pytest-xdist
进行多线程测试,不测试多线程。我最终在调试过程中使用 pytest -s path_to_file.py::TestGetIpAddressOnNewThread
提到的 here,这表明我有一个打印错误导致的异常,但没有导致测试失败。
这让我想到了 this question,我最初忽略了这一点,因为我没有意识到断言只是引发 AssertError。
因此,我对 community wiki answer 进行了如下调整,以使断言正常工作!
class GetInstanceIpThread(threading.Thread):
def __init__(self, instance, callback=None):
threading.Thread.__init__(self)
self.event = threading.Event()
self.instance = instance
self.callback = callback
def _run(self):
# The original run code goes here
self.instance.wait_until_running()
ip = self.instance.ip_address
self.callback(ip)
def run(self):
self.exc = None
try:
self._run()
except BaseException as e:
self.exc = e
def join(self):
super(GetInstanceIpThread, self).join()
if self.exc:
raise self.exc
请注意,只要其他线程中存在任何异常,这就会使您的测试失败。这可能不是你想要的,所以如果你只想在断言失败或类似的情况下失败,你可以将 BaseException
更改为 AssertError
(或任何你想失败的)。
覆盖 join()
是必要的,因为您 必须 在 pytest 的主线程上引发异常以使其正确地使测试失败。
我 运行 遇到了同样的问题,但无法访问创建线程的代码。我为那个用例发布了一个小测试助手包,pytest-reraise:
pip install pytest-reraise
Pytest 风格的测试用例:
def test_get_existing_ip(reraise):
def func(ip):
with reraise:
assert ip == "192.168.0.1" # Not the real IP
assert ip == "shouldn't be the ip"
ip_check = GetInstanceIpThread(instance, func)
ip_check.start()
ip_check.join()
单元测试风格的测试用例:
from pytest-reraise import Reraise
class TestGetIpAddressOnNewThread(unittest.TestCase):
def test_get_existing_ip(self):
reraise = Reraise()
def func(ip):
with reraise:
assert ip == "192.168.0.1" # Not the real IP
assert ip == "shouldn't be the ip"
ip_check = GetInstanceIpThread(instance, func)
ip_check.start()
ip_check.join()
# Re-raise the first exception that the `reraise` context manager captured:
reraise()
两个测试用例都按预期失败并报告 AssertionError
。
我目前正在使用 pytest 来测试现有的(unittest 测试套件 per the documentation)。我目前正在编写一个等待分配 IP 地址的线程,然后 returns 它到回调函数,我正在编写单元测试来伴随它。
这是我写的测试用例class。
class TestGetIpAddressOnNewThread(unittest.TestCase):
def test_get_existing_ip(self):
def func(ip):
assert ip == "192.168.0.1" # Not the real IP
# Even when I introduce an assert statement that should fail, test still passes
assert ip == "shouldn't be the ip"
ip_check = GetInstanceIpThread(instance, func)
ip_check.start()
ip_check.join()
if __name__ == '__main__':
pytest.main()
这里是 GetInstanceIpThread
伪定义:
class GetInstanceIpThread(threading.Thread):
def __init__(self, instance, callback):
threading.Thread.__init__(self)
self.event = threading.Event()
self.instance = instance
self.callback = callback
def run(self):
self.instance.wait_until_running()
ip = self.instance.ip_address
self.callback(ip)
当我 运行 这个测试用例使用 pytest path_to_file.py::TestGetIpAddressOnNewThread
时,它通过了(耶!)但即使我引入了应该 100% 失败的断言语句(嘘!)。出了什么问题,我该如何编写实际失败的测试?
所以我正在回答我自己的问题,因为虽然我能够在 Whosebug 上找到答案,但它没有任何有用的关键字,因为大多数答案都在谈论如何使用 pytest-xdist
进行多线程测试,不测试多线程。我最终在调试过程中使用 pytest -s path_to_file.py::TestGetIpAddressOnNewThread
提到的 here,这表明我有一个打印错误导致的异常,但没有导致测试失败。
这让我想到了 this question,我最初忽略了这一点,因为我没有意识到断言只是引发 AssertError。
因此,我对 community wiki answer 进行了如下调整,以使断言正常工作!
class GetInstanceIpThread(threading.Thread):
def __init__(self, instance, callback=None):
threading.Thread.__init__(self)
self.event = threading.Event()
self.instance = instance
self.callback = callback
def _run(self):
# The original run code goes here
self.instance.wait_until_running()
ip = self.instance.ip_address
self.callback(ip)
def run(self):
self.exc = None
try:
self._run()
except BaseException as e:
self.exc = e
def join(self):
super(GetInstanceIpThread, self).join()
if self.exc:
raise self.exc
请注意,只要其他线程中存在任何异常,这就会使您的测试失败。这可能不是你想要的,所以如果你只想在断言失败或类似的情况下失败,你可以将 BaseException
更改为 AssertError
(或任何你想失败的)。
覆盖 join()
是必要的,因为您 必须 在 pytest 的主线程上引发异常以使其正确地使测试失败。
我 运行 遇到了同样的问题,但无法访问创建线程的代码。我为那个用例发布了一个小测试助手包,pytest-reraise:
pip install pytest-reraise
Pytest 风格的测试用例:
def test_get_existing_ip(reraise):
def func(ip):
with reraise:
assert ip == "192.168.0.1" # Not the real IP
assert ip == "shouldn't be the ip"
ip_check = GetInstanceIpThread(instance, func)
ip_check.start()
ip_check.join()
单元测试风格的测试用例:
from pytest-reraise import Reraise
class TestGetIpAddressOnNewThread(unittest.TestCase):
def test_get_existing_ip(self):
reraise = Reraise()
def func(ip):
with reraise:
assert ip == "192.168.0.1" # Not the real IP
assert ip == "shouldn't be the ip"
ip_check = GetInstanceIpThread(instance, func)
ip_check.start()
ip_check.join()
# Re-raise the first exception that the `reraise` context manager captured:
reraise()
两个测试用例都按预期失败并报告 AssertionError
。