Python 3.x 如何测试在flask/flask restx中接受解析器输入的GET方法?

Python 3.x 如何测试在flask/flask restx中接受解析器输入的GET方法?,python-3.x,unit-testing,flask,get,pytest,Python 3.x,Unit Testing,Flask,Get,Pytest,我正在使用制作一个flask应用程序,并通过以下方式从用户处获取输入: from flask_restx import Resource, reqparse from .services.calculator import DimensionCalculator parser = reqparse.RequestParser() parser.add_argument("dimensions", type=float, required

我正在使用制作一个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)
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
其中,
ns
是我定义的名称空间,
DimensionCalculator的简化版本。输入是:

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
我正在使用。我已经为所有类和方法编写了测试,唯一无法测试的是
GET
方法,该方法在
UserOutput
中定义。有没有办法测试
GET
方法


非常感谢您的帮助。

考虑到单元测试标签,我将介绍我提出的如何在完全隔离的情况下进行测试的方法。基本上,
get
方法对依赖项进行两次函数调用,因此在单元意义上,您必须检查是否确实进行了这些调用,以及断言参数,对吗

本示例中的项目结构:

+---Project
|   |   
|   |   __init__.py
|   |   config.py
|   |   dimension_calculator.py
|   |   parser_impl.py
|   |   user_output.py
|   |   user_output_test.py
因此,为了简单起见,一切都是平面的

最重要的是,您必须将
UserOutput
模块与依赖项分离。您不应该像这样硬编码依赖项:

from .services.calculator import DimensionCalculator
假设,
DimensionCalculator
可能包含不应在测试范围内的复杂业务逻辑。下面是
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)
如您所见,“外部”依赖项现在可以很容易地进行存根(这是称为依赖项注入的常见模式的一部分)。配置模块如下所示:

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)
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
最后,但并非最不重要的一点是,我们将在其中存根依赖项并注入它们:

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!
就我而言,考试通过了。缺少的一件事是参数的验证

为了完整起见,我还将包括DimensionCalculator和解析器本身,尽管它们与您的示例中完全相同。我只对它们进行了模块化:

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)")
以及尺寸_calculator.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
重要肯定有专门的框架用于此类存根/模拟的准备和配置(例如:)。我只是想介绍一下概念和最简单的方法