从py2迁移到py3后,以字节/字符串的形式读取cherrypy的响应

时间:2019-01-24 03:38:41

标签: python-3.x response cherrypy

我刚刚将Cherrypy应用程序从2.7迁移到了python(在3.6上运行)。在基于this recipe之前,我进行了大量测试设置。食谱的重点是模拟网络并在各个端点上执行测试单元。

现在我的服务器似乎可以正常运行。但是,如果我运行测试单元,它们通常会在py3中返回错误(在py2中全部通过),这似乎与响应以字节(在py3中)而不是字符串(在py2中)有关。

A test response
    ======================================================================
    FAIL: test_index (__main__.EndpointTests)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/home/anonymous/PycharmProjects/server-py3/tests/test_DServer.py", line 67, in test_index

        self.assertEqual(response.body, ['Hello World!'])
    AssertionError: Lists differ: [b'Hello World!'] != ['Hello World!']

    First differing element 0:
    b'Hello World!'
    'Hello World!'

    - [b'Hello World!']
    ?  -

    + ['Hello World!']

类似地:

======================================================================
FAIL: test_valid_login (__main__.EndpointTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/anonymous/PycharmProjects/server-py3/tests/test_DServer.py", line 73, in test_valid_login
    self.assertEqual(response.output_status, '200 OK')
AssertionError: b'200 OK' != '200 OK'

我知道字节的行为有些不同(例如解释的here)。

实际上有2个问题:

在我的测试中解决此问题的最佳方法是什么?我是否需要在服务器响应中声明的每个字符串前添加b?

在服务器上进行快速测试似乎表明它可以工作。但是我是否可能以其他方式被这个问题咬住?关于其他带有cherrypy并迁移到py3的陷阱有什么智慧的话吗?

此后,我不再支持py2,可以进行干净迁移。

1 个答案:

答案 0 :(得分:0)

找到了。

需要在cptestcase中进行编辑。问题的核心是此食谱在某种程度上取决于Cherrypy的内部工作原理,因为2to3(我用来进行迁移工作的工具)无法很好地管理细节,无法满足cp的喜好。 >

摘要是,您需要切换到io.BytesIO,而不是io.StringIO(这是2to3默认提供的功能)。因此,以前对StringIO(data)的调用应该是BytesIO(data)。关键是cp现在内部希望这些字符串/字节(因为py2确实没有使2之间有任何区别)是实际字节(因为py3实际上是有区别的)。是的,在进行断言的实际测试中,您必须将response.output_status&response.body从字节转换为字符串,或者将它们与字节进行比较,如下所示:

self.assertEqual(response.output_status, b'200 OK')

但是,query_string(用于GET)必须仍然保留为字符串。

这是对我有用的代码的完整编辑:

from io import BytesIO
import unittest
import urllib.request, urllib.parse, urllib.error

import cherrypy

cherrypy.config.update({'environment': "test_suite"})
cherrypy.server.unsubscribe()

local = cherrypy.lib.httputil.Host('127.0.0.1', 50000, "")
remote = cherrypy.lib.httputil.Host('127.0.0.1', 50001, "")

__all__ = ['BaseCherryPyTestCase']

class BaseCherryPyTestCase(unittest.TestCase):
    def request(self, path='/', method='GET', app_path='', scheme='http',
                proto='HTTP/1.1', data=None, headers=None, **kwargs):

        h = {'Host': '127.0.0.1'}

        if headers is not None:
            h.update(headers)

        if method in ('POST', 'PUT') and not data:
            data = urllib.parse.urlencode(kwargs).encode('utf-8')
            kwargs = None
            h['content-type'] = 'application/x-www-form-urlencoded'

        qs = None
        if kwargs:
            qs = urllib.parse.urlencode(kwargs)

        fd = None
        if data is not None:
            h['content-length'] = '%d' % len(data.decode('utf-8'))
            fd = BytesIO(data)

        app = cherrypy.tree.apps.get(app_path)
        if not app:
            raise AssertionError("No application mounted at '%s'" % app_path)

        app.release_serving()

        request, response = app.get_serving(local, remote, scheme, proto)
        try:
            h = [(k, v) for k, v in h.items()]
            response = request.run(method, path, qs, proto, h, fd)
        finally:
            if fd:
                fd.close()
                fd = None

        if response.output_status.startswith(b'500'):
            print(response.body)
            raise AssertionError("Unexpected error")

        response.collapse_body()
        return response