简体   繁体   English

如何测试在 flask/flask-restx 中获取解析器输入的 GET 方法?

[英]How to test the GET method that takes parser inputs in flask/flask-restx?

I am making a flask app using Flask-restx and I take inputs from the user by request parsing as follows:我正在使用Flask-restx制作 flask 应用程序,我通过请求解析从用户那里获取输入,如下所示:

from flask_restx import Resource, reqparse
from .services.calculator import DimensionCalculator
parser = reqparse.RequestParser()
parser.add_argument("dimensions", type=float,
                    required=True,
                    action='split',
                    help="Dimensions of the rectangle (in meters)")
parser.add_argument("angle_inclination", type=float,
                    required=True,
                    action='append',
                    help="Angle of inclination of the Dachfläche (Neigung)")
@ns.route("/output")
class UserOutput(Resource):
    @ns.expect(parser, validation=True)
    def get(self):
        args = parser.parse_args()
        return DimensionCalculator.inputs(**args)

where ns is a namespace I have defined and the simplified version of DimensionCalculator.inputs is:其中ns是我定义的命名空间, DimensionCalculator.inputs的简化版本是:

class DimensionCalculator:
    def inputs(**user_input):
        installation_place = user_input['installation_place']
        num_rectangles = user_input['num_rectangles']
        dimensions = user_input['dimensions']
        angle_inclination = user_input['angle_inclination']
        alignment = user_input['alignment']
        direction = user_input['direction']
        vendor = user_input['vendor']
        output = {
                    "installation_place": installation_place,
                    "num_rectangles": num_rectangles,
                    "area_shape": area_shape,
                    "vendor": vendor
                }
        return output

I am writing tests using pytest .我正在使用pytest编写测试。 I have written the tests for all the classes and methods and the only one that I am unable to test is the GET method defined in the UserOutput .我已经为所有类和方法编写了测试,唯一无法测试的是UserOutput中定义的GET方法。 Is there a way to test the GET method?有没有办法测试GET方法?

Any help is appreciated.任何帮助表示赞赏。

Considering unit-testing tag, I'll present what I came up with on how you could test it in total isolation.考虑到单元测试标签,我将介绍我想出的关于如何完全隔离测试它的方法。 Basically, get method makes two function calls on dependencies, so in unit sense, you have to check if these calls have indeed been made, as well as assert the arguments, right?基本上, get方法对依赖项进行了两次 function 调用,因此在单元意义上,您必须检查是否确实进行了这些调用,以及断言 arguments,对吗?

Project structure for purpose of the example:用于示例的项目结构:

+---Project
|   |   
|   |   __init__.py
|   |   config.py
|   |   dimension_calculator.py
|   |   parser_impl.py
|   |   user_output.py
|   |   user_output_test.py

So, everything is flat for simplicity.因此,为简单起见,一切都是平坦的。

Most importantly, you have to decouple your UserOutput module from dependencies.最重要的是,您必须将UserOutput模块与依赖项分离。 You shouldn't be hard-coding dependencies like that:您不应该像这样对依赖项进行硬编码:

from .services.calculator import DimensionCalculator

Hypothetically, DimensionCalculator could contain complex business logic which shouldn't be in scope of the test.假设, DimensionCalculator可能包含复杂的业务逻辑,不应该出现在测试的 scope 中。 So, here's how the UserOutput module could look like:所以,下面是UserOutput模块的样子:

from flask_restx import Resource, Api
from flask import Flask
from .config import Config

app = Flask(__name__)
api = Api(app)
ns = api.namespace('todos', description='TODO operations')

@ns.route("/output")
class UserOutput(Resource):
    @ns.expect(Config.get_parser_impl(), validation=True)
    def get(self):
        args = Config.get_parser_impl().parse_args()
        return Config.get_dimension_calculator_impl().inputs(**args)


if __name__ == '__main__':
    app.run(debug=True)

As you can see, "external" dependencies can now be easily stubbed (this is part of a common pattern called dependency injection ).如您所见,“外部”依赖现在可以很容易地被存根(这是一种称为依赖注入的常见模式的一部分)。 Config module looks as follows:配置模块如下所示:

from .parser_impl import parser
from .dimension_calculator import DimensionCalculator


class Config(object):
    parser_impl = parser
    calculator = DimensionCalculator

    @staticmethod
    def configure_dimension_calculator_impl(impl):
        Config.calculator = impl

    @staticmethod
    def configure_parser_impl(impl):
        Config.parser_impl = impl

    @staticmethod
    def get_dimension_calculator_impl():
        return Config.calculator

    @staticmethod
    def get_parser_impl():
        return Config.parser_impl

Last, but not least, is the place where we'll be stubbing the dependencies and injecting them:最后但并非最不重要的一点是,我们将在其中存根依赖项并注入它们:

from .user_output import UserOutput
from flask import Flask
from .config import Config

class ParserStub(object):
    parse_args_call_count = 0
    @staticmethod
    def parse_args():
        ParserStub.parse_args_call_count = ParserStub.parse_args_call_count + 1
        return {'parser_stub': 2}

class DimensionCalculatorStub(object):
    inputs_call_count = 0
    @staticmethod
    def inputs(**args):
        DimensionCalculatorStub.inputs_call_count = DimensionCalculatorStub.inputs_call_count + 1
        return {'stub': 1}

app = Flask(__name__)

def test_user_request_get():
    with app.test_request_context():
        # given
        Config.configure_dimension_calculator_impl(DimensionCalculatorStub)
        Config.configure_parser_impl(ParserStub)
        uo = UserOutput()
        
        # when
        uo.get()
        
        # then
        assert DimensionCalculatorStub.inputs_call_count == 1
        assert ParserStub.parse_args_call_count == 1
        # assert arguments as well!

Test passes in my case.在我的情况下测试通过。 One thing missing is validation of arguments.缺少的一件事是对 arguments 的验证。

For completeness, I'll also include DimensionCalculator and the parser itself, though they are exactly the same as in your example.为了完整起见,我还将包括 DimensionCalculator 和解析器本身,尽管它们与您的示例完全相同。 I've only modularized them:我只是将它们模块化:

from flask_restx import reqparse

parser = reqparse.RequestParser()
parser.add_argument("dimensions", type=float,
                    required=True,
                    action='split',
                    help="Dimensions of the rectangle (in meters)")
parser.add_argument("angle_inclination", type=float,
                    required=True,
                    action='append',
                    help="Angle of inclination of the Dachfläche (Neigung)")

and the dimension_calculator.py:和维度计算器.py:

class DimensionCalculator:
    @staticmethod
    def inputs(**user_input):
        installation_place = user_input['installation_place']
        num_rectangles = user_input['num_rectangles']
        dimensions = user_input['dimensions']
        angle_inclination = user_input['angle_inclination']
        alignment = user_input['alignment']
        direction = user_input['direction']
        vendor = user_input['vendor']
        output = {
                    "installation_place": installation_place,
                    "num_rectangles": num_rectangles,
                    "area_shape": "EMPTY",
                    "vendor": vendor
                }
        return output

Important definitely there are dedicated frameworks for such stubs/mocks preparation and configuration (for example: https://pypi.org/project/pytest-mock/ ).重要的是肯定有专门的框架用于此类存根/模拟准备和配置(例如: https://pypi.org/project/pytest-mock/ )。 I just wanted to present the concept and easiest approach possible.我只是想介绍这个概念和最简单的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM