Twisted Trial单元测试中@inlineCallbacks回调触发异常问题
问题解析:@inlineCallbacks Deferred 手动调用 callback 的误区
你遇到的问题核心是对@inlineCallbacks装饰器返回的Deferred工作机制理解有误——它和你手动创建的Deferred完全不是一回事,不能用同样的方式手动触发。
先看普通Deferred的行为(addone函数)
你手动创建的Deferred是一个空白的回调容器:
- 你给它绑定了回调
cbk,但这个回调完全忽略传入的参数,直接返回val + 1 - 当你调用
d.callback(0)时,虽然把0传入了回调链,但cbk根本不用它,最终结果就是2+1=3,测试自然通过。
再看@inlineCallbacks生成的Deferred
@inlineCallbacks本质是一个生成器驱动工具,它返回的Deferred不是用来让你手动触发的——这个Deferred的作用是管理生成器的执行流程:
- 调用
call_addone(4)时,装饰器会创建一个Deferred,同时启动生成器执行 - 生成器执行到
yield addone(4)时暂停,等待addone(4)返回的Deferred完成 - 只有当
addone(4)的Deferred完成后,生成器才会继续执行,把结果赋值给res,再通过returnValue(res)让外层Deferred完成,最终结果是5。
而你手动调用d.callback(0),相当于直接强制完成了外层的Deferred,完全绕过了生成器的执行流程——生成器根本没机会走到yield之后的逻辑,外层Deferred的结果就被你硬设为0了,这就是测试失败的原因。
正确的测试方式
如果你想在不启动reactor的情况下测试call_addone,应该让yield的那个Deferred完成,而不是手动触发外层的Deferred。更简单的方式是利用Trial本身对Deferred的支持——Trial会自动处理返回Deferred的测试,不需要手动触发:
from twisted.trial import unittest from twisted.internet.defer import inlineCallbacks, returnValue, Deferred def addone(val): d = Deferred() def cbk(res): return val + 1 d.addCallback(cbk) return d @inlineCallbacks def call_addone(val): res = yield addone(val) returnValue(res) class Tester(unittest.TestCase): def test_addone(self): d = addone(2) d.callback(0) self.assertEqual(3, self.successResultOf(d)) # 修正后的测试:让Trial自动处理Deferred @inlineCallbacks def test_call_addone(self): res = yield call_addone(4) self.assertEqual(5, res) # 手动触发内部逻辑的调试方式(不推荐日常测试用) def test_call_addone_manual_debug(self): # 获取生成器对象(@inlineCallbacks本质是生成器装饰器) gen = call_addone(4) # 执行到yield语句,拿到addone返回的Deferred inner_deferred = next(gen) # 触发内部Deferred的完成 inner_deferred.callback(0) try: # 把addone的结果传入生成器,继续执行 gen.send(3) except StopIteration as e: # 生成器执行完毕,returnValue的值会在StopIteration的value属性中 self.assertEqual(5, e.value)
总结
- 手动创建的Deferred:你完全掌控它的触发时机,调用
callback会启动回调链 @inlineCallbacks返回的Deferred:它是生成器的“控制器”,不要手动调用它的callback/errback,应该让生成器内部的yield操作驱动它完成,或者交给Trial自动处理。
内容的提问来源于stack exchange,提问作者Benjámin Budai
相关产品推荐
相关产品推荐

