从 py2 迁移到 py3 后,将 cherrypy 的响应读取为 bytes/string
Reading cherrypy's response as bytes/string after migrating from py2 to py3
我刚刚将一个 cherrypy 应用程序从 2.7 迁移到 python(运行ning 在 3.6 上)。我之前有一堆基于 this recipe 的测试设置。配方的重点是模拟网络并在各个端点上执行测试单元。
现在我的服务器似乎 运行 没问题。但是,如果我 运行 测试单元,它们在 py3 中主要是 return 错误(全部通过 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,我可以做一个干净的迁移。
找到了。
需要在 cptestcase 中进行编辑。问题的核心是这个配方在某种程度上依赖于 Cherrypy 的内部工作,2to3(我用来完成迁移的繁重工作的工具)无法很好地管理细节以达到 cp 的喜好。
总结是,您需要切换到 io.BytesIO,而不是 io.StringIO(这是 2to3 默认提供的)。因此,之前对 StringIO(data) 的调用应该是 BytesIO(data)。关键是 cp 现在内部期望那些 strings/bytes (因为 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
我刚刚将一个 cherrypy 应用程序从 2.7 迁移到 python(运行ning 在 3.6 上)。我之前有一堆基于 this recipe 的测试设置。配方的重点是模拟网络并在各个端点上执行测试单元。
现在我的服务器似乎 运行 没问题。但是,如果我 运行 测试单元,它们在 py3 中主要是 return 错误(全部通过 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,我可以做一个干净的迁移。
找到了。
需要在 cptestcase 中进行编辑。问题的核心是这个配方在某种程度上依赖于 Cherrypy 的内部工作,2to3(我用来完成迁移的繁重工作的工具)无法很好地管理细节以达到 cp 的喜好。
总结是,您需要切换到 io.BytesIO,而不是 io.StringIO(这是 2to3 默认提供的)。因此,之前对 StringIO(data) 的调用应该是 BytesIO(data)。关键是 cp 现在内部期望那些 strings/bytes (因为 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