首页
学习
活动
专区
圈层
工具
发布

测试开发进阶(三十八)

用例模块

需要使用httprunner来进行用例的执行与报告的生成

所以我们需要生成一个yaml用例文件,再执行它

代码语言:javascript
复制
@action(methods=['post'], detail=True)
def run(self, request, *args, **kwargs):
    instance = self.get_object()
    serializer = self.get_serializer(instance, data=request.data)
    serializer.is_valid(raise_exception=True)
    datas = serializer.validated_data
    env_id = datas.get('env_id')
    testcase_dir_path = os.path.join(settings.SUITES_DIR, datetime.strftime(datetime.now(), '%Y%m%d%H%M%S%f'))
    if not os.path.exists(testcase_dir_path):
        os.mkdir(testcase_dir_path)
    env = Envs.objects.filter(id=env_id, is_delete=False).first()
    # 生成yaml用例文件
    common.generate_testcase_files(instance, env, testcase_dir_path)
    # 运行用例
    return common.run_testcase(instance, testcase_dir_path)

生成yaml用例

官方文档有一个例子:

代码语言:javascript
复制
- config:
    name: testcase description
    variables: {}

- test:
    name: /api/get-token
    request:
        headers:
            Content-Type: application/json
            User-Agent: python-requests/2.18.4
            app_version: 2.8.6
            device_sn: FwgRiO7CNA50DSU
            os_platform: ios
        json:
            sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98
        method: POST
        url: http://127.0.0.1:5000/api/get-token
    extract:
        token: content.token
    validate:
        - eq: [status_code, 200]
        - eq: [headers.Content-Type, application/json]
        - eq: [content.success, true]

- test:
    name: /api/users/1000
    request:
        headers:
            Content-Type: application/json
            User-Agent: python-requests/2.18.4
            device_sn: FwgRiO7CNA50DSU
            token: $token
        json:
            name: user1
            password: '123456'
        method: POST
        url: http://127.0.0.1:5000/api/users/1000
    validate:
        - eq: [status_code, 201]
        - eq: [headers.Content-Type, application/json]
        - eq: [content.success, true]
        - eq: [content.msg, user created successfully.]

上述一个yaml对应的json格式为:

代码语言:javascript
复制
[
  {
    "config": {
      "name": "testcase description",
      "request": {
        "base_url": "",
        "headers": {
          "User-Agent": "python-requests/2.18.4"
        }
      },
      "variables": [],
      "output": ["token"],
      "path": "/abs-path/to/demo-quickstart-2.yml",
      "refs": {
        "env": {},
        "debugtalk": {
          "variables": {
            "SECRET_KEY": "DebugTalk"
          },
          "functions": {
            "gen_random_string": <function gen_random_string at 0x108596268>,
            "get_sign": <function get_sign at 0x1085962f0>,
            "get_user_id": <function get_user_id at 0x108596378>,
            "get_account": <function get_account at 0x108596400>,
            "get_os_platform": <function get_os_platform at 0x108596488>
          }
        },
        "def-api": {},
        "def-testcase": {}
      }
    },
    "teststeps": [
      {
        "name": "/api/get-token",
        "request": {
          "url": "http://127.0.0.1:5000/api/get-token",
          "method": "POST",
          "headers": {"Content-Type": "application/json", "app_version": "2.8.6", "device_sn": "FwgRiO7CNA50DSU", "os_platform": "ios", "user_agent": "iOS/10.3"},
          "json": {"sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"}
        },
        "extract": [
          {"token": "content.token"}
        ],
        "validate": [
          {"eq": ["status_code", 200]},
          {"eq": ["headers.Content-Type", "application/json"]},
          {"eq": ["content.success", true]}
        ]
      },
      {
        "name": "/api/users/1000",
        "request": {"url": "http://127.0.0.1:5000/api/users/1000", "method": "POST", "headers": {"Content-Type": "application/json", "device_sn": "FwgRiO7CNA50DSU", "token": "$token"},
        "json": {"name": "user1", "password": "123456"}},
        "validate": [
          {"eq": ["status_code", 201]},
          {"eq": ["headers.Content-Type", "application/json"]},
          {"eq": ["content.success", true]},
          {"eq": ["content.msg", "user created successfully."]}
        ]
      }
    ]
  },
  {...} # another testcase
]

所以我们需要通过一个函数将已有的接口,环境,配置写入一份yaml中

将对应的 debugtalk.py存放在yaml文件附近

代码语言:javascript
复制
def generate_testcase_files(instance, env, testcase_dir_path):
    testcase_list = []
    config = {
        'config': {
            'name': instance.name,
            'request': {
                'base_url': env.base_url if env else ''
            }
        }
    }
    testcase_list.append(config)

    include = json.loads(instance.include, encoding='utf8')
    request = json.loads(instance.request, encoding='utf8')
    interface_name = instance.interface.name
    project_name = instance.interface.project.name
    testcase_dir_path = os.path.join(testcase_dir_path, project_name)
    if not os.path.exists(testcase_dir_path):
        os.mkdir(testcase_dir_path)
        debugtalk_obj = DebugTalks.objects.filter(is_delete=False, project__name=project_name).first()
        if debugtalk_obj:
            debugtalk = debugtalk_obj.debugtalk
        else:
            debugtalk = ''
        with open(os.path.join(testcase_dir_path, 'debugtalk.py'),
                  mode='w', encoding='utf8') as one_file:
            one_file.write(debugtalk)
        testcase_dir_path = os.path.join(testcase_dir_path, interface_name)
        if not os.path.exists(testcase_dir_path):
            os.mkdir(testcase_dir_path)

        if 'config' in include:
            config_id = include.get('config')
            config_obj = Configures.objects.filter(is_delete=False, id=config_id).first()
            if config_obj:
                config_request = json.loads(config_obj.request, encoding='utf8')
                config_request.get('config').get('request').setdefault('base_url', env.base_url)
                config_request['config']['name'] = instance.name
                testcase_list[0] = config_request
            if 'testcases' in include:
                for t_id in include.get('testcases'):
                    testcase_obj = Testcases.objects.filter(is_delete=False, id=t_id).first()
                    if testcase_obj:
                        try:
                            testcase_request = json.loads(testcase_obj.request, encoding='utf8')
                        except Exception as e:
                            testcase_request = ''
                    else:
                        testcase_list.append(testcase_request)
    testcase_list.append(request)
    with open(os.path.join(testcase_dir_path, instance.name + '.yml'), 'w', encoding='utf8') as one_file:
        yaml.dump(testcase_list, one_file, allow_unicode=True)

执行yaml文件

从https://cn.httprunner.org/development/dev-api/可以看出,我们可以通过传入yaml路径来执行测试

代码语言:javascript
复制
def run_testcase(instance, testcase_dir_path):
    """
    运行用例
    :return:
    :param instance: 实例
    :param testcase_dir_path: 用例根目录路径
    :return dict
    """
    runner = HttpRunner()
    # runner.run(testcase_dir_path)
    try:
        runner.run(testcase_dir_path)
    except ParamsError:
        logger.error("用例参数有误")
        data = {
            "msg": "用例参数有误"
        }
        return Response(data, status=400)

    runner.summary = timestamp_to_datetime(runner.summary, type=False)

    try:
        report_name = instance.name
    except Exception as e:
        report_name = '被遗弃的报告' + '-' + datetime.strftime(datetime.now(), '%Y%m%d%H%M%S')

    report_id = create_report(runner, report_name=report_name)
    data_dict = {
        "id": report_id
    }

    return Response(data_dict, status=status.HTTP_201_CREATED)

报告中的时间格式需要进行调整

代码语言:javascript
复制
def create_report(runner, report_name=None):
    """
    创建测试报告
    :param runner:
    :param report_name:
    :return:
    """
    time_stamp = int(runner.summary["time"]["start_at"])
    start_datetime = datetime.fromtimestamp(time_stamp).strftime('%Y-%m-%d %H:%M:%S')
    runner.summary['time']['start_datetime'] = start_datetime
    # duration保留3位小数
    runner.summary['time']['duration'] = round(runner.summary['time']['duration'], 3)
    report_name = report_name if report_name else start_datetime
    runner.summary['html_report_name'] = report_name

    for item in runner.summary['details']:
        try:
            for record in item['records']:
                record['meta_data']['response']['content'] = record['meta_data']['response']['content']. \
                    decode('utf-8')
                record['meta_data']['response']['cookies'] = dict(record['meta_data']['response']['cookies'])

                request_body = record['meta_data']['request']['body']
                if isinstance(request_body, bytes):
                    record['meta_data']['request']['body'] = request_body.decode('utf-8')
        except Exception as e:
            continue

    summary = json.dumps(runner.summary, ensure_ascii=False)

    report_name = report_name + '_' + datetime.strftime(datetime.now(), '%Y%m%d%H%M%S')
    report_path = runner.gen_html_report(html_report_name=report_name)

    with open(report_path, encoding='utf-8') as stream:
        reports = stream.read()

    test_report = {
        'name': report_name,
        'result': runner.summary.get('success'),
        'success': runner.summary.get('stat').get('successes'),
        'count': runner.summary.get('stat').get('testsRun'),
        'html': reports,
        'summary': summary
    }
    report_obj = Reports.objects.create(**test_report)
    return report_obj.id

其他模块的执行与报告展示也调用了这两个函数

https://github.com/zx490336534/ApiTest

下一篇
举报
领券