让我们削减一些代码
首先,我们将在安装了Twilio和Flask模块的Python环境中打开一个文本编辑器,并开发出一个简单的应用程序,该应用程序将使用动词和名词创建一个Twilio会议室。
这是我们将其命名为app的文件的简要介绍 。py:
代码语言:javascript复制from flask import Flask
from twilio import twiml
app = Flask(__name__)
@app.route('/conference', methods=['POST'])
def voice():
response = twiml.Response()
with response.dial() as dial:
dial.conf("Rob's Blog Party")
return str(response)
if __name__ == "__main__":
app.debug = True
app.run(port=5000)
现在让我们测试一下
我认为这段代码可能是正确的,但是让我们通过编写快速的单元测试来确保。为此,我们将打开另一个名为test_app的文件 。py。在该文件中,我们将导入我们的应用程序,并在Python标准库中使用unittest定义一个单元测试 。然后,我们将使用Flask测试客户端向应用发出测试请求,并查看应用是否抛出错误。
代码语言:javascript复制from flask import Flask
from twilio import twiml
# 定义我们的应用程序
app = Flask(__name__)
# NoseDefine要用作会议室的端点
@app.route('/conference', methods=['POST'])
def voice():
response = twiml.Response()
with response.dial() as dial:
# 现在我们使用正确的属性。
dial.conference("Rob's Blog Party")
return str(response)
# 在端口5000上以调试模式运行应用程序
if __name__ == "__main__":
app.debug = True
app.run(port=5000)
后,我们使用Nose运行单元测试通过发出以下命令,Nose将遍历我们的单元测试文件,找到所有 TestCase对象并执行每个以test_为前缀的方法 :
nosetests - v test_app 。py
哦,饼干-好像我们有个错误。
代码语言:javascript复制test_conference (test_intro.TestConference) ... FAIL
======================================================================
FAIL: test_conference (test_intro.TestConference)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/rspectre/workspace/test_post/test_intro.py", line 16, in test_conference
self.assertEquals(response.status, "200 OK")
AssertionError: '500 INTERNAL SERVER ERROR' != '200 OK'
-------------------- >> begin captured logging << --------------------
app: ERROR: Exception on /conference [POST]
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1504,
in wsgi_app response = self.full_dispatch_request()
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1264,
in full_dispatch_request rv = self.handle_user_exception(e)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1262,
in full_dispatch_request rv = self.dispatch_request()
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1248,
in dispatch_request return self.view_functions[rule.endpoint](**req.view_args)
File "/home/rspectre/workspace/test_post/app.py", line 13,
in voice dial.conf("Rob's Blog Party")
AttributeError: 'Dial' object has no attribute 'conf'
--------------------- >> end captured logging << ---------------------
----------------------------------------------------------------------
Ran 1 test in 0.009s
FAILED (failures=1)
天啊 用于会议的TwiML名词的名称不是“ Conf”,而是“ Conference”。让我们重新访问我们的 应用程序。py文件并更正错误。
代码语言:javascript复制from flask import Flask
from twilio import twiml
# Define our app
app = Flask(__name__)
# 定义要用作会议室的终结点
@app.route('/conference', methods=['POST'])
def voice():
response = twiml.Response()
with response.dial() as dial:
# 现在我们使用正确的属性。
dial.conference("Rob's Blog Party")
return str(response)
# 在端口5000上以调试模式运行应用程序
if __name__ == "__main__":
app.debug = True
app.run(port=5000)
现在更正了会议线,我们可以使用与上面相同的命令重新运行测试:
代码语言:javascript复制rspectre@drgonzo:~/workspace/test_post$ nosetests -v test_app.py
test_conference (test_app.TestConference) ... ok
test_conference_valid (test_app.TestConference) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.011s
OK
太棒了 而且,我们不必拿起电话来找出错误。
现在,让我们确保此代码可以实现我们想要的功能
确保代码不会引发错误是很好的第一步,但是我们还想确保Twilio应用程序能够按预期方式执行。首先,我们需要检查应用程序是否返回了Twilio可以解释的响应,请确保它正在创建有效的Dial动词,最后确保Dial指向正确的会议室。
为了提供帮助,我们将使用ElementTree,它是Python标准库中的XML解析器。这样,我们可以像Twilio一样解释TwiML响应。让我们看看如何将其添加到 test_app 。py:
代码语言:javascript复制import unittest
from app import app
# 导入XML解析器
from xml.etree import ElementTree
class TestConference(unittest.TestCase):
def test_conference(self):
# 保留以前的测试。
self.test_app = app.test_client()
response = self.test_app.post('/conference', data={'From': ' 15556667777'})
self.assertEquals(response.status, "200 OK")
def test_conference_valid(self):
# 创建一个新的测试来验证我们的TwiML是否在做它应该做的事情。
self.test_app = app.test_client()
response = self.test_app.post('/conference', data={'From': ' 15556667777'})
# 将结果解析为ElementTree对象
root = ElementTree.fromstring(response.data)
# 断言根元素是响应标记
self.assertEquals(root.tag, 'Response',
"Did not find tag as root element "
"TwiML response.")
# 断言响应有一个拨号动词
dial_query = root.findall('Dial')
self.assertEquals(len(dial_query), 1,
"Did not find one Dial verb, instead found: %i " %
len(dial_query))
# 断言拨号动词只有一个名词
dial_children = list(dial_query[0])
self.assertEquals(len(dial_children), 1,
"Dial does not go to one noun, instead found: %s" %
len(dial_children))
# 断言拨入会议名词
self.assertEquals(dial_children[0].tag, 'Conference',
"Dial is not to a Conference, instead found: %s" %
dial_children[0].tag)
# Assert Conference is Rob's Blog Party
self.assertEquals(dial_children[0].text, "Rob's Blog Party",
"Conference is not Rob's Blog Party, instead found: %s" %
dial_children[0].text)
现在使用Nose运行两个测试:
代码语言:javascript复制rspectre@drgonzo:~/workspace/test_post$ nosetests -v test_app.py
test_conference (test_app.TestConference) ... ok
test_conference_valid (test_app.TestConference) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.011s
OK
现在,我们有信心该应用程序除了返回适当的响应外,还会执行我们想要的操作。
我们的测试以供重用
非常高兴知道我们的新Twilio端点无需手动测试即可工作,但是Twilio应用程序很少使用单个webhook端点。随着应用程序复杂性的增加,我们可以看到这两个测试将重复很多代码。让我们看看是否可以将测试重构为通用测试用例,以用于将来构建的任何Twilio Webhook端点。
为此,我们将创建一个通用的 TwiMLTest类,并利用内置的 setUp ()方法在每个测试中自动实例化Flask测试客户端。
代码语言:javascript复制import unittest
from app import app
from xml.etree import ElementTree
class TwiMLTest(unittest.TestCase):
def setUp(self):
# 创建每个测试用例都可以使用的测试应用程序。
self.test_app = app.test_client()
伟大的开始–现在让我们创建一个辅助方法,该方法接受响应并进行TwiML工作的基本验证。
代码语言:javascript复制import unittest
from app import app
from xml.etree import ElementTree
class TwiMLTest(unittest.TestCase):
def setUp(self):
# 创建每个测试用例都可以使用的测试应用程序。
self.test_app = app.test_client()
def assertTwiML(self, response):
# 检查错误。
self.assertEquals(response.status, "200 OK")
# 将结果解析为ElementTree对象
root = ElementTree.fromstring(response.data)
# 断言根元素是响应标记
self.assertEquals(root.tag, 'Response',
"Did not find tag as root element "
"TwiML response.")
最后,让我们创建两个其他的辅助方法,而不是为每次测试创建一个新的POST请求,这些方法将为调用和消息创建Twilio请求,我们可以使用自定义参数轻松地对其进行扩展。让我们向test_app添加一个新类 。py。
代码语言:javascript复制import unittest
from app import app
from xml.etree import ElementTree
class TwiMLTest(unittest.TestCase):
def setUp(self):
self.test_app = app.test_client()
def assertTwiML(self, response):
self.assertEquals(response.status, "200 OK")
root = ElementTree.fromstring(response.data)
self.assertEquals(root.tag, 'Response',
"Did not find tag as root element "
"TwiML response.")
def call(self, url='/voice', to=' 15550001111',
from_=' 15558675309', digits=None, extra_params=None):
"""Simulates Twilio Voice request to Flask test client
Keyword Args:
url: The webhook endpoint you wish to test. (default '/voice')
to: The phone number being called. (default ' 15500001111')
from_: The CallerID of the caller. (default ' 1558675309')
digits: DTMF input you wish to test (default None)
extra_params: Dictionary of additional Twilio parameters you
wish to simulate, like QueuePosition or Digits. (default: {})
Returns:
Flask test client response object.
"""
# 为Twilio接收的消息设置一些常用参数。
params = {
'CallSid': 'CAtesting',
'AccountSid': 'ACxxxxxxxxxxxxx',
'To': to,
'From': from_,
'CallStatus': 'ringing',
'Direction': 'inbound',
'FromCity': 'BROOKLYN',
'FromState': 'NY',
'FromCountry': 'US',
'FromZip': '55555'}
# 添加模拟DTMF输入。
if digits:
params['Digits'] = digits
# 添加默认情况下未定义的额外参数。
if extra_params:
params = dict(params.items() extra_params.items())
# 返回应用程序的响应。
return self.test_app.post(url, data=params)
def message(self, body, url='/message', to=" 15550001111",
from_=' 15558675309', extra_params={}):
"""Simulates Twilio Message request to Flask test client
Args:
body: The contents of the message received by Twilio.
Keyword Args:
url: The webhook endpoint you wish to test. (default '/sms')
to: The phone number being called. (default ' 15500001111')
from_: The CallerID of the caller. (default ' 15558675309')
extra_params: Dictionary of additional Twilio parameters you
wish to simulate, like MediaUrls. (default: {})
Returns:
Flask test client response object.
"""
# 为Twilio接收的消息设置一些常用参数。
params = {
'MessageSid': 'SMtesting',
'AccountSid': 'ACxxxxxxx',
'To': to,
'From': from_,
'Body': body,
'NumMedia': 0,
'FromCity': 'BROOKLYN',
'FromState': 'NY',
'FromCountry': 'US',
'FromZip': '55555'}
# 添加默认情况下未定义的额外参数。
if extra_params:
params = dict(params.items() extra_params.items())
# 返回应用程序的响应。
return self.test_app.post(url, data=params)
太好了–现在,我们可以使用新的帮助器方法重构会议的原始测试,从而使测试更短:
代码语言:javascript复制import unittest
from app import app
from xml.etree import ElementTree
class TwiMLTest(unittest.TestCase):
def setUp(self):
self.test_app = app.test_client()
def assertTwiML(self, response):
self.assertEquals(response.status, "200 OK")
root = ElementTree.fromstring(response.data)
self.assertEquals(root.tag, 'Response',
"Did not find tag as root element "
"TwiML response.")
def call(self, url='/voice', to=' 15550001111',
from_=' 15558675309', digits=None, extra_params=None):
"""Simulates Twilio Voice request to Flask test client
Keyword Args:
url: The webhook endpoint you wish to test. (default '/voice')
to: The phone number being called. (default ' 15550001111')
from_: The CallerID of the caller. (default ' 15558675309')
digits: DTMF input you wish to test (default None)
extra_params: Dictionary of additional Twilio parameters you
wish to simulate, like QueuePosition or Digits. (default: {})
Returns:
Flask test client response object.
"""
# Set some common parameters for messages received by Twilio.
params = {
'CallSid': 'CAtesting',
'AccountSid': 'ACxxxxxxxxxxxxx',
'To': to,
'From': from_,
'CallStatus': 'ringing',
'Direction': 'inbound',
'FromCity': 'BROOKLYN',
'FromState': 'NY',
'FromCountry': 'US',
'FromZip': '55555'}
# Add simulated DTMF input.
if digits:
params['Digits'] = digits
# Add extra params not defined by default.
if extra_params:
params = dict(params.items() extra_params.items())
# Return the app's response.
return self.test_app.post(url, data=params)
def message(self, body, url='/message', to=" 15550001111",
from_=' 15558675309', extra_params={}):
"""Simulates Twilio Message request to Flask test client
Args:
body: The contents of the message received by Twilio.
Keyword Args:
url: The webhook endpoint you wish to test. (default '/sms')
to: The phone number being called. (default ' 15550001111')
from_: The CallerID of the caller. (default ' 15558675309')
extra_params: Dictionary of additional Twilio parameters you
wish to simulate, like MediaUrls. (default: {})
Returns:
Flask test client response object.
"""
# 为Twilio接收的消息设置一些常用参数。
params = {
'MessageSid': 'SMtesting',
'AccountSid': 'ACxxxxxxx',
'To': to,
'From': from_,
'Body': body,
'NumMedia': 0,
'FromCity': 'BROOKLYN',
'FromState': 'NY',
'FromCountry': 'US',
'FromZip': '55555'}
# 添加默认情况下未定义的额外参数。
if extra_params:
params = dict(params.items() extra_params.items())
# 返回应用程序的响应。
return self.test_app.post(url, data=params)
class TestConference(TwiMLTest):
def test_conference(self):
response = self.call(url='/conference')
self.assertTwiML(response)
def test_conference_valid(self):
# 创建一个新的测试来验证我们的TwiML是否在做它应该做的事情。
response = self.call(url='/conference')
# 将结果解析为ElementTree对象
root = ElementTree.fromstring(response.data)
# 断言响应有一个拨号动词
dial_query = root.findall('Dial')
self.assertEquals(len(dial_query), 1,
"Did not find one Dial verb, instead found: %i " %
len(dial_query))
# 断言拨号动词只有一个名词
dial_children = list(dial_query[0])
self.assertEquals(len(dial_children), 1,
"Dial does not go to one noun, instead found: %s" %
len(dial_children))
# 断言拨入会议名词
self.assertEquals(dial_children[0].tag, 'Conference',
"Dial is not to a Conference, instead found: %s" %
dial_children[0].tag)
# Assert Conference is Rob's Blog Party
self.assertEquals(dial_children[0].text, "Rob's Blog Party",
"Conference is not Rob's Blog Party, instead found: %s" %
dial_children[0].text)
完美–让我们使用Nose进行测试,看看我们是否成功。
代码语言:javascript复制rspectre@drgonzo:~/workspace/test_post$ nosetests -v test_app.py
test_conference (test_app.TestConference) ... ok
test_conference_valid (test_app.TestConference) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.014s
OK
世界一切都很好。
进行测试
使用我们针对Twilio应用程序的通用测试用例,现在编写测试既快速又简单。我们编写了一个快速的会议应用程序,使用Nose对它进行了测试,然后将这些测试重构为可以与所有应用程序一起使用的通用案例。通过使用此测试用例,可以快速轻松地测试我们基于Flask构建的Twilio应用程序,从而减少了用手机手动测试所花费的时间,并减少了您听到可怕的“应用程序错误”声音的次数。