欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

测试开发进阶(三十八)

程序员文章站 2024-03-22 08:06:22
...

用例模块

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

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

@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用例

官方文档有一个例子:

- 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格式为:

[	
  {	
    "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文件附近

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路径来执行测试

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)

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

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