我刚刚将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,可以进行干净迁移。
答案 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