用于编写alexa技能的Python ask-sdk提供了两种编写意图处理程序的方法。一种是从AbstractRequestHandler
派生并实现两个功能can_handle()
和handle()
。另一个使用函数装饰器(@SkillBuilder.request_handler()
)。
在装饰器中使用第二种方法,我无法直接调用处理函数(在单元测试中)。尝试访问该函数时,解释器显示错误TypeError: 'NoneType' object is not callable
。
以下是意图处理程序以及测试代码(与this github issue的建议类似)的最小示例。
意图处理程序
sb = SkillBuilder()
# Built-in Intent Handlers
@sb.request_handler(can_handle_func=is_request_type("LaunchRequest"))
def test_intent_handler(handler_input):
return "Hello, test handler"
测试功能
def test_intent():
request = new_request('TestIntent', {})
# THE FOLLOWING LINE THROWS THE ERROR
response = test_intent_handler(request)
expected_response = "Hello, test handler"
assert response == expected_response
根据对this question的回答,装饰器函数必须返回一个函数,但是request_handler()
似乎已经是这种情况,正如您在github上看到的那样
装饰器函数确实返回包装函数,因此test_intent_handler
应该是函数类型。我想念什么?
编辑
Adam Smith的答案是解决此问题的好方法。
发生这种情况的原因是函数SkillBuilder.request_handler
返回一个包装函数,该包装函数不返回任何内容。该包装函数用作处理程序函数的装饰器。由于装饰器的结果已分配给test_intent_handler
,并且装饰器(包装器)未返回任何内容,因此结果为NoneType。
因此,在用@sb.request_handler
装饰处理程序之后,原来的函数将不再可用。
要解决此问题,包装器函数只需要返回传入的处理函数。遵循亚当·斯密(Adam Smith)的建议,我创建了一个pull request来进行更改,以便“用于Python的Alexa Skills Kit SDK”变得更具可测试性。
答案 0 :(得分:1)
装饰器的目的是就地修改函数似乎。装饰器不会(没有一些自定义逻辑)保留对调用者的底层函数的引用。但这很好,因为您要测试的不是请求处理程序,而是回调本身。
ask-SDK不太可能具有一些用于编写处理程序单元测试的框架,但是如果没有,请为您自己保存回调的引用。
# handler code
sb = SkillBuilder()
def _test_intent_handler(handler_input):
return "Hello, test handler"
# Built-in Intent Handlers
@sb.request_handler(can_handle_func=is_request_type("LaunchRequest"))
test_intent_handler = sb.request_handler(
can_handle_func=is_request_type("LaunchRequest"))(_test_intent_handler)
# test suite
def test_intent():
request = new_request('TestIntent', {})
response = _test_intent_handler(request)
expected_response = "Hello, test handler"
assert response == expected_response
如果这打扰了您(并且我不会怪您-这很丑陋),您可以编写自己的装饰器来保留我上面提到的自定义逻辑。
import functools
def meta_decorator(dec, *args, **kwargs):
@functools.wraps(dec)
def wrapper(f):
@functools.wraps(f)
def wrapped(*args, **kwargs):
return f(*args, **kwargs)
wrapped._original_function = f
return dec(*args, **kwargs)(wrapped)
return wrapper
sb = SkillBuilder()
@meta_decorator(sb.request_handler, can_handle_func=is_request_type("LaunchRequest"))
def test_intent_handler(handler_input):
return "Hello, test handler"
# Test suite 2
def test_intent():
request = new_request('Test Intent', {})
response = test_intent_handler._original_function(request)
expected_response = "Hello, test handler"
assert response == expected_response