zoukankan      html  css  js  c++  java
  • HttpRunner-1.5.6功能复刻版

    前言

    • 依照httprunner-1.5.6标准结构,实现了请求默认值,base_url,variables,用例跳过,提取,断言,debugtalk.py函数支持
    • 基于unittest+ddt,由于ddt只支持单层循环,为了生成用例简便,暂时parameters使用unittest的subTest实现
    • TODO:
      • 断言方法尚需补充完整
      • CSV数据驱动
      • 使用JinJa2或HTMLRunner生成报告
      • JSONSchema整体结构预验证

    数据data.yaml

    - config:
        name: 测试用例名称
        request:
          base_url: https://httpbin.org
          headers:
            token: abc123
        variables:
          a: 100
          b: 200
          c: ${add($a, $b)}
    
    - test:
        name: 步骤1-GET请求
        setup_hooks:
          - ${setup('hello')}
        teardown_hooks:
          - ${teardown('world')}
        request:
          url: /get
          method: GET
        extract:
          - u: content.url
        validate:
          - eq: [status_code, 200]
          - comparator: eq
            check: content.url
            expect: https://httpbin.org/get
    
    - test:
        name: 步骤2-POST请求
        skip: True
        request:
          url: /post
          method: POST
          data:
            a: $a
            b: 2
            c: $c
    
    - test:
        name: 步骤4-数据驱动
        parameters:
          - a-b:
            - [1,2]
            - [3,4]
            - [5,6]
        request:
            url: /get
            method: GET
            params:
              a: $a
              b: $b
        validate:
          - eq: [content.args.a, '1']
    

    实现代码runner.py

    import re
    import json
    import requests
    import logging
    from string import Template
    from functools import reduce
    from operator import eq, gt, lt, ge, le
    import unittest
    import ddt
    import importlib
    
    
    COMPARE_FUNCS = dict(
        eq=eq, gt=gt, lt=lt, ge=ge, le=le,
        len_eq=lambda x, y: len(x) == len(y),
        str_eq=lambda x, y: str(x) == str(y),  
        type_match=lambda x, y: isinstance(x, y),
        regex_match=lambda x, y: re.match(y, x),
        # 待补充更多....
    )
    
    FUNTION_REGEX = re.compile('${(?P<func>.*?)}')
    
    def do_dot(item, key):
        if hasattr(item, key):
            return getattr(item, key)
        if key.isdigit():
            key = int(key)
        try:
            return item[key]
        except Exception as ex:
            logging.exception(ex)
            return key
    
    
    def get_field(context, expr):
        if '.' in expr:
            value = expr.split('.')
            field = context.get(value[0])
            return reduce(lambda x, y: do_dot(x, y), value[1:], field)
        else:
            return context.get(expr)
    
    
    def send_request(context, base_url, session, request):
        # 组装base_url
        if base_url and not request['url'].startswith('http'):
            request['url'] = base_url + request['url']
    
        # 发请求
        response = session.request(**request)
        print('响应数据', response.text)
        try:
            content = response.json()
        except json.decoder.JSONDecodeError:
            content = {}
    
        # 注册上下文变量
        context.update(
            response=response,
            request=response.request,
            content=content,
            status_code=response.status_code,
            headers=response.headers,
            ok=response.ok,
            reason=response.reason,
            response_time=response.elapsed.seconds
        )
    
    
    def do_extract(context, extract):
        # 处理提取变量
        for line in extract:
            key, value = tuple(line.items())[0]
            context[key] = get_field(context, value)
    
    
    def do_validate(context, validate):
        # 处理断言
        for line in validate:
            if 'comparator' in line:
                comparator = line.get('comparator')
                check = line.get('check')
                expect = line.get('expect')
            else:
                comparator, value = tuple(line.items())[0]
                check, expect = value
            compare_func = COMPARE_FUNCS.get(comparator)
            field = get_field(context, check)
            assert compare_func(field, expect)
    
    
    def get_functions():
        """从模块中获取功能函数"""
        module = importlib.import_module('debugtalk')
        functions = {key: value for key, value in module.__dict__.items()
                     if not key.startswith('__') and callable(value)}
        return functions
    
    
    def parse_dollar(context, data):
        """解析$变量"""
        data_str = json.dumps(data)
        if '$' in data_str:
            data_str = Template(data_str).safe_substitute(context)
            return json.loads(data_str)
        else:
            return data
    
    
    def do_test(self, test):
        parsed_test = parse_dollar(self.context, test)
        send_request(self.context, self.base_url, self.session, parsed_test.get('request'))
        do_extract(self.context, parsed_test.get('extract', []))
        do_validate(self.context, parsed_test.get('validate', []))
    
    
    def gen_parameter(context, parameters):
        # for line in parameters:
        line = parameters[0]
        keys, data = tuple(line.items())[0]
        keys = keys.split('-')
        for item in data:
            for index, key in enumerate(keys):
                context[key] = item[index]
                yield
    
    
    def parse_function(context, functions, data):
        data_str = json.dumps(data)
        if '$' in data_str:
            data_str = Template(data_str).safe_substitute(context)
    
        def repr_func(matched):
            """自定义re.sub替换方法"""
            if not matched:
                return
            return str(eval(matched.group(1), {}, functions))
    
        data_str = re.sub(FUNTION_REGEX, repr_func, data_str)
        return json.loads(data_str)
    
    
    def build(data):
        config = data[0].get('config')
        functions = get_functions()
        context = config.get('variables')
        config = parse_function(context, functions, config)
    
        @ddt.ddt
        class TestApi(unittest.TestCase):
            f"""{config.get('name')}"""
            @classmethod
            def setUpClass(cls):
                cls.session = requests.session()
                config_request = config.get('request')
                cls.base_url = config_request.pop('base_url') if 'base_url' in config_request else None
                cls.context = config.get('variables', {})
                for key, value in config_request.items():
                    setattr(cls.session, key, value)
    
            @ddt.data(*data[1:])
            def test_api(self, test):
                f"""{test.get('name')}"""
                test = test.get('test')
                if test.get('skip'):
                    raise unittest.SkipTest
                setup_hooks = test.get('setup_hooks')
                teardown_hooks = test.get('teardown_hooks')
    
                if setup_hooks:
                    parse_function(context, functions, setup_hooks)
    
                if teardown_hooks:
                    self.addCleanup(parse_function, context, functions, teardown_hooks)
    
                parameters = test.get('parameters')
                if parameters:
                    for _ in gen_parameter(self.context, parameters):
                        with self.subTest():
                            do_test(self, test)
                else:
                    do_test(self, test)
    
        suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestApi)
        return suite
    
    
    def run(suite):
        runner = unittest.TextTestRunner(verbosity=2)
        return runner.run(suite)
    
    
    if __name__ == '__main__':
        from filez import file  # 需要安装pip install filez
        data = file.load('data.yaml')
        suite = build(data)
        run(suite)
    
  • 相关阅读:
    MyBatis学习总结(11)——MyBatis动态Sql语句
    MyBatis学习总结(11)——MyBatis动态Sql语句
    Dubbo学习总结(3)——Dubbo-Admin管理平台和Zookeeper注册中心的搭建
    Dubbo学习总结(1)——Dubbo入门基础与实例讲解
    Dubbo学习总结(1)——Dubbo入门基础与实例讲解
    Maven学习详解(13)——Maven常用命令大全与pom文件讲解
    3分钟了解ServiceStage 应用智能化运维【华为云分享】
    OBS带你玩转图片
    高性能Web动画和渲染原理系列(3)——transform和opacity为什么高性能
    【Python成长之路】Python爬虫 --requests库爬取网站乱码(xe4xb8xb0xe5xa)的解决方法
  • 原文地址:https://www.cnblogs.com/superhin/p/12763611.html
Copyright © 2011-2022 走看看