前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Python的flask和Nose对Twilio应用进行单元测试

使用Python的flask和Nose对Twilio应用进行单元测试

作者头像
用户7466307
发布2020-06-17 16:16:13
4.9K0
发布2020-06-17 16:16:13
举报

让我们削减一些代码

首先,我们将在安装了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应用程序,从而减少了用手机手动测试所花费的时间,并减少了您听到可怕的“应用程序错误”声音的次数。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 软件测试test 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 让我们削减一些代码
  • 现在让我们测试一下
  • 现在,让我们确保此代码可以实现我们想要的功能
  • 我们的测试以供重用
  • 进行测试
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档