Python 如何单元测试线程是否已生成?

Python 如何单元测试线程是否已生成?,python,multithreading,mocking,Python,Multithreading,Mocking,我在试图修复的单元测试中遇到了一个种族问题 假设有一个模块spam.py: import threading def foo(*args, **kwargs): pass def bar(): t = threading.Timer(0.5, foo, args=('potato',), kwargs={'x': 69, 'y':'spam'}) t.start() 下面是对它的测试: from mock import patch from spam import

我在试图修复的单元测试中遇到了一个种族问题

假设有一个模块
spam.py

import threading

def foo(*args, **kwargs):
    pass

def bar():
    t = threading.Timer(0.5, foo, args=('potato',), kwargs={'x': 69, 'y':'spam'})
    t.start()
下面是对它的测试:

from mock import patch
from spam import bar
from unittest import TestCase

class SpamTest(TestCase):
    def test_bar(self):
        with patch('spam.foo') as mock:
            bar()
            mock.assert_called_once_with('potato', y='spam', x=69)
当然,这个测试失败了,
AssertionError:应该被调用一次。调用了0次。
因为对
bar()
的调用是非阻塞的,所以断言发生得太早


可以通过在断言之前添加
time.sleep(1)
来通过测试,但这显然是有缺陷的-模拟/单元测试异步内容的公认方法是什么?

修改
条以返回一个thead对象如何:

def bar():
    t = threading.Timer(0.5, foo, args=('potato',), kwargs={'x': 69, 'y':'spam'})
    t.start()
    return t # <----
更新


在Python3.x中,补丁
threading.Event
而不是
threading.\u Event

有趣的想法。。。但是它似乎有点侵入性,我不喜欢它增加测试代码的运行时间(例如,如果我想在调用foo之前等待1小时怎么办?感觉测试应该有某种模拟时钟intsead)@wim,我更新了答案,添加了一种替代方法。谢谢,您选择修补
线程有什么原因吗?改为使用
patch.object(foo,'wait')
进行了尝试,它似乎也能工作,但没有访问该(u)属性。@falstru我的意思是
patch.object(foo,'wait')
。Python3.x在
线程上给出了
属性错误。然而,我已经用“等待”补丁实现了我的测试,我认为这对于我的用例来说是很好的,因为我的等待时间都很短。我还使用了一个contextmanager,以便于重用此代码,如:
with wait\u for\u thread(mock):bar()
class SpamTest(TestCase):
    def test_bar(self):
        with patch('spam.foo') as mock:
            t = bar()
            t.join() # <----
            mock.assert_called_once_with('potato', y='spam', x=69)
import threading
import time

...

class SpamTest(TestCase):
    def test_bar(self):
        foo = threading.Event()
        with patch('spam.foo', side_effect=lambda *args, **kwargs: foo.set()) as mock:
            # Make the callback `foo` to be called immediately
            with patch.object(threading._Event, 'wait', time.sleep(0.000001)):
                bar()
            foo.wait() # Wait until `spam.foo` is called. (instead of time.sleep)
            mock.assert_called_once_with('potato', y='spam', x=69)