【问题标题】:Preserve response context testing flask app with pytest使用 pytest 保留响应上下文测试烧瓶应用程序
【发布时间】:2020-04-17 22:27:21
【问题描述】:

我正在使用 py.test 使用以下代码测试烧瓶应用程序:

response = flask_app_test_client.post('/users', data=json.dumps(user))
assert response.status_code == 201
assert response.content_type == 'application/json'
assert isinstance(response.json, dict)
assert set(response.json.keys()) >= {'id', 'status', 'created_at', 'updated_at'}
assert response.json['name'] == user['name']
assert response.json['status'] == 'pending'

当某些断言失败时,我会得到这样的结果:

            response = test_client.post('/users', data=json.dumps(user))
    >       assert response.status_code == 201
    E       assert 400 == 201
    E        +  where 400 = <JSONResponse streamed [400 BAD REQUEST]>.status_code
    ============== 1 failed, 3 passed in 0.10 seconds ===================

我做了很多 TDD,所以我希望我的测试在开发过程中经常失败。我的问题是断言错误消息在没有其余响应数据(正文、标题等)的情况下是无用的。

我只得到 response.status_code 为 400 的输出,但我没有得到响应正文中的错误描述:{"errors": ["username is already taken", "email is required"]}。理想情况下,我希望在断言失败时完整转储请求和响应(标头 + 正文)。

如何打印每个失败断言的响应摘要?

【问题讨论】:

  • 我通常利用assert 关键字的打印功能,所以我在断言之后加上response.json。我会扩展您的断言以使其看起来像assert response.status_code == 201, reponse.json,然后您将看到断言失败时端点返回的内容。如果响应正文没有太大帮助,那么您需要通过在端点代码中放置断点来进行调试。看看stackoverflow.com/a/5142453/7320870

标签: python flask pytest


【解决方案1】:

Assert statement graamar

assert response.status_code == 201, "Anything you want"

您可以随心所欲地冗长。您还可以使用 UnitTest 的 helper methods 套件 - 通过这种滥用没有测试用例类 - https://github.com/nose-devs/nose2/blob/master/nose2/tools/such.py#L34

【讨论】:

  • 确实,现在我在每个断言上添加 response.json() ,但似乎不是正确的方法。我不能以某种方式增强断言以打印自定义内容吗?我很确定我在这里遗漏了一些东西......
  • @pablomolnar 我很抱歉,在截止日期前工作并误读了您的问题。如果只是这个特定的用例,也许try{assert block}except AssertionError: print(more.data); raise
【解决方案2】:

我想出了两种不同的解决方案。

解决方案 #1:尝试/捕获

try:
    assert response.status_code == 201
    assert response.content_type == 'application/json'
    assert isinstance(response.json, dict)
    assert set(response.json.keys()) >= {'id', 'status', 'created_at', 'updated_at'}
    assert response.json['name'] == user['name']
    assert response.json['status'] == 'pending'
except AssertionError as e:
    except AssertionError as e:
    raise ResponseAssertionError(e, response)

class ResponseAssertionError(AssertionError):
    def __init__(self, e, response):
        response_dump = "\n +  where full response was:\n" \
                        "HTTP/1.1 {}\n" \
                        "{}{}\n".format(response.status, response.headers, response.json)

        self.args = (e.args[0] + response_dump,)

解决方案 #2:不需要 try/catch(如果 repr 太长,有时会被切断...)

扩展和覆盖 Flask 响应对象

import json
class JSONResponse(Response):

    def __repr__(self):
        headers = {}
        while len(self.headers) > 0:
            tuple_ = self.headers.popitem()
            headers[tuple_[0]] = tuple_[1]

        data = {
            'status': self.status,
            'headers': headers,
            'body': self.json
        }
        return json.dumps(data)

    @pytest.fixture(scope='session')
    def test_client(flask_app):
        flask_app.response_class = JSONResponse
        return flask_app.test_client()

【讨论】:

  • 出于好奇,您更喜欢哪个?第一种形式似乎更理想,因为您不会更改应用程序代码以使测试输出有用,但看起来更干净,然后必须包装您的测试。
【解决方案3】:

我知道这是一个较老的问题,但 Pytest 有一个选项--pdb,如果您的测试失败,它会将您弹出到 PDB shell。非常方便的方式来“环顾四周”,而不必将大量内容传递给异常消息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-12
    • 1970-01-01
    • 2017-08-15
    • 1970-01-01
    • 2016-02-14
    • 1970-01-01
    • 2019-01-04
    • 2018-12-17
    相关资源
    最近更新 更多