首先,我们将在安装了Twilio和Flask模块的Python环境中打开一个文本编辑器,并开发出一个简单的应用程序,该应用程序将使用动词和名词创建一个Twilio会议室。
这是我们将其命名为app的文件的简要介绍 。py:
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测试客户端向应用发出测试请求,并查看应用是否抛出错误。
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
哦,饼干-好像我们有个错误。
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文件并更正错误。
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)现在更正了会议线,我们可以使用与上面相同的命令重新运行测试:
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:
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运行两个测试:
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测试客户端。
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工作的基本验证。
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。
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)太好了–现在,我们可以使用新的帮助器方法重构会议的原始测试,从而使测试更短:
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进行测试,看看我们是否成功。
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应用程序,从而减少了用手机手动测试所花费的时间,并减少了您听到可怕的“应用程序错误”声音的次数。
▼
更多精彩推荐,请关注我们
▼
扫码关注更多精彩
你点的每个赞,我都认真当成了喜欢
