四十二、实战演练之接口自动化平台的测试步骤执行,测试场景执行,测试计划执行

一、接口设计

接口名称: /test_steps/run/

请求方式: POST

参数格式: JSON

请求参数:

参数

变量名

类型

说明

是否必传

测试环境id

env

整数

环境id

测试数据

data

json

测试步骤的详细数据

请求示例:json格式参数

{
    "data": {
        "id": 2,
        "interface": {
            "url": "/users/login/",
            "method": "POST" },
        "title": "登录失败",
        "headers": {},
        "request": {
            "json": {
                "username": "xinlan",
                "password": "123123"
            },
            "params": {}},
        "file": [],
        "setup_script": "# 前置脚本(python):\n# global_tools:全局工具函数\n# data:用例数据 \n# env: 局部环境\n# ENV: 全局环境\n# db: 数据库操作对象\n",
        "teardown_script": "# 后置脚本(python):\n# global_tools:全局工具函数\n# data:用例数据 \n# response:响应对象response \n# env: 局部环境\n# ENV: 全局环境\n# db: 数据库操作对象\n"
    },
    "env": 1
}

返回示例

响应状态码:

200 响应数据:

{
    "name":"登录失败",
    "log_data":[["INFO","【INFO】	|	开始执行用例:【登录失败】\n"],
                ["DEBUG","临时变量:\n{}"],["DEBUG","全局变量:\n{'host':   'http://127.0.0.1:8080','headers': {'customer-header': 'wahaha'}, 'key1': 'value1', 'key2': 'value2'}"],
				["INFO","【INFO】	|	*********执行前置脚本*********"],["INFO","【INFO】	|	发送 [POST]请求 : 请求地址为http://127.0.0.1:8080/users/login/:"],
                ["DEBUG","请求头:\n{'User-Agent': 'python-requests/2.28.1', 'Accept-Encoding': 'gzip, deflate','Accept': '*/*', 'Connection': 'keep-alive', 'customer-header': 'wahaha', 'Content-Length': '44', 'Content-Type': 'application/json'}"],
                ["DEBUG","请求体:\n{\n	\"username\": \"xinlan\",\n	\"password\": \"123123\"\n}"],
  				["INFO","【INFO】	|	请求响应状态码:401"],
                ["DEBUG","响应头:\n{'Date': 'Thu, 21 Jul 2022 12:42:41 GMT', 'Server': 'WSGIServer/0.2 CPython/3.8.5', 'Content-Type': 'application/json', 'WWW-Authenticate': 'Bearer realm=\"api\"', 'Vary': 'Accept, Origin', 'Allow': 'POST, OPTIONS', 'X-Frame-Options': 'DENY', 'Content-Length': '37', 'X-Content-Type-Options': 'nosniff', 'Referrer-Policy': 'same-origin'}"],
  				["DEBUG","响应体:\n{\n	\"detail\": \"用户名密码错误!\"\n}"],
                ["INFO","【INFO】	|*********执行后置脚本*********"],
                ["INFO","【INFO】	|	登录失败执行——>【通过】\n"]],
    "url":"http://127.0.0.1:8080/users/login/",
    "method":"POST",
    "status_cede":401,
    "response_header":{"Date":"Thu, 21 Jul 2022 12:42:41 GMT","Server":"WSGIServer/0.2 CPython/3.8.5","Content- Type":"application/json","WWW-Authenticate":"Bearer realm=\"api\"","Vary":"Accept, Origin","Allow":"POST, OPTIONS","X-Frame- Options":"DENY","Content-Length":"37","X-Content-Type- Options":"nosniff","Referrer-Policy":"same-origin"},"requests_header":{"User- Agent":"python-requests/2.28.1","Accept-Encoding":"gzip, deflate","Accept":"*/*","Connection":"keep-alive","customer-
    header":"wahaha","Content-Length":"44","Content- Type":"application/json"},
    "response_body":"{\n	\"detail\": \"用户名密码错误!\"\n}",
    "requests_body":"{\n	\"username\": \"xinlan\",\n	\"password\":\"123123\"\n}",
    "state":"成功",
    "run_time":"0.345s"
    }

二、 后端代码

1. 测试执行函数

创建testplans/tasks.py 模块,然后编写如下代码:

# -*- coding: utf-8 -*-
# time: 2022/11/9 21:45
# file: tasks.py
# author: fade
from projects.models import TestEnv
from .models import TestScene, TestPlan
from .serializers import TestSceneRunSerializer, TestPlanRunSerializer
from reports.models import Record, Report
from celery import shared_task

from apitestengine.core.cases import run_test


def __get_env_config(env_id, debug=True):
    """获取测试环境的配置"""
    env = TestEnv.objects.get(id=env_id)
    variable = {**env.global_variable, **env.debug_global_variable} if debug else env.global_variable
    _ENV = {
        **variable,  # 因为debug模式下,var中有临时的调试变量,解包不能写到下面,否自会覆盖真正的host,headers
        'host': env.host,
        'headers': env.headers,
    }
    config = {
        'ENV': _ENV,
        'DB': env.db,
        'global_func': env.global_func
    }
    return config


def run_case(cases, env_id):
    """运行单条用例"""
    # 准备好测试数据
    # 环境
    config = __get_env_config(env_id=env_id)
    # 执行用例
    res, debug_var = run_test(case_data=[{"Cases": [cases]}], env_config=config, debug=True)
    # 获取执行的结果
    result = res['results'][0]['cases'][0]
    # 保存调试环境的下的环境变量
    env = TestEnv.objects.get(id=env_id)
    env.debug_global_variable = debug_var
    env.save()
    return result


def run_scene(pk, env_id):
    """"执行测试场景"""
    # 1.获取测试环境
    config = __get_env_config(env_id, debug=True)
    # 获取测试场景
    scene = TestScene.objects.get(pk=pk)
    # 根据测试场景对象组织测试场景的测试数据
    cases = TestSceneRunSerializer(scene).data['scenedata_set']
    # 套内用例排序
    cases.sort(key=lambda x: x['sort'])
    # 组装用例数据
    test_case = {'Cases': [item['step'] for item in cases], 'name': scene.name}
    res, debug_var = run_test(case_data=[test_case], env_config=config, debug=True)
    env = TestEnv.objects.get(pk=env_id)
    env.debug_global_variable = debug_var
    env.save()
    return res['results'][0]


@shared_task
def run_plan(plan_id, env_id, record_id):
    """执行测试计划"""
    # 获取测试环境
    config = __get_env_config(env_id=env_id, debug=False)
    # 获取执行计划的数据
    plan = TestPlan.objects.get(pk=plan_id)
    task_data = TestPlanRunSerializer(plan).data
    scene_list = []
    for scene in task_data['scenes']:
        cases = scene['scenedata_set']
        cases.sort(key=lambda x: x['sort'])
        scene_list.append({'Cases': [item['step'] for item in cases], 'name': scene['name']})
    res = run_test(case_data=scene_list, env_config=config, debug=False)
    # 保存运行结果
    record = Record.objects.get(pk=record_id)
    # 往执行结果中加字段
    res['plan'] = plan_id
    res['test_env'] = env_id
    res['tester'] = record.tester
    # 创建测试报告
    Report.objects.create(info=res, record=record)
    # 更新record中的数据
    record.all = res.get('all', 0)
    record.success = res.get('success', 0)
    record.fail = res.get('fail', 0)
    record.error = res.get('error', 0)
    record.pass_rate = '{:.2f}'.format(100 * res.get('success', 0) / res.get('all', 0)) if res.get('all', 0) else "0"
    record.status = '执行完毕'
    record.save()
    # return res['results']

其中,函数run_case 用来运行单条用例,_ get_env_config 函数用来获取需要运行的环境配置。

2. 视图

还是通过视图集action扩展,在测试步骤视图集中添加一个run 方法如下:

import os

from django.shortcuts import render
from django.conf import settings

from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated
from rest_framework.exceptions import ValidationError
from rest_framework.decorators import action

from .filters import TestStepFilter
from .serializers import TestStepSerializer, NestInterfaceSerializer, UploadFileSerializer, TestPlanSerializer, \
    TestSceneSerializer, TestSceneStepSerializer
from .models import TestStep, TestPlan, SceneData, UploadFile, TestScene
from projects.models import TestEnv
from .tasks import run_case, run_scene, run_plan
from reports.serializers import RecordSerializer


class TestStepViewSet(ModelViewSet):
    """测试步骤视图集"""
    serializer_class = TestStepSerializer
    queryset = TestStep.objects.all()
    permission_classes = [IsAuthenticated]
    # filterset_fields = ['interface']
    filterset_class = TestStepFilter

    def get_serializer_class(self):
        """
        复写这个方法,实现不同得操作使用不同得序列化器
        list,create,retrieve,update,partial_update,destroy
        """
        if self.action == 'retrieve':
            return NestInterfaceSerializer
        return self.serializer_class

    @action(methods=['post'], detail=False)
    def run(self, request, *args, **kwargs):
        # 获取测试数据
        cases = request.data.get('data')
        env_id = request.data.get('env')
        if not env_id:
            raise ValidationError('请求参数env必填')
        try:
            TestEnv.objects.get(id=env_id)
        except:
            raise ValidationError('参数env传入的值无效')
        res = run_case(cases=cases, env_id=env_id)
        return Response(res)


class UploadFileViewSet(ModelViewSet):
    """文件上传视图"""
    queryset = UploadFile.objects.all()
    serializer_class = UploadFileSerializer
    permission_classes = [IsAuthenticated]

    def perform_create(self, serializer):
        # 限制文件大小,文件重复
        # 生成info数据
        size = self.request.data['file'].size
        name = self.request.data['file'].name
        if size > 1024 * 300:
            raise ValidationError(detail='上传的文件大小不可超过300KB')
        if os.path.isfile(settings.MEDIA_ROOT / name):
            raise ValidationError(detail=f'文件【{name}】已存在')
        file_type = self.request.data['file'].content_type
        file_path = str(settings.MEDIA_ROOT / name)
        info = [name, file_path, file_type]
        serializer.save(info=info)

    def perform_destroy(self, instance):
        """文件删除"""
        # 删除本地保存的文件
        os.remove(instance.file.path)
        instance.delete()


class TestPlanViewSet(ModelViewSet):
    """测试计划视图"""
    queryset = TestPlan.objects.all()
    serializer_class = TestPlanSerializer
    permission_classes = [IsAuthenticated]
    filterset_fields = ['project']

    @action(methods=['post'], detail=True)
    def run(self, request, pk, *args, **kwargs):
        # 获取环境id
        env_id = request.data.get('env')
        # 生成测试记录
        serializer = RecordSerializer(data={
            'test_env': env_id,
            'plan': pk,
            'status': '执行中',
            'tester': request.user.username
        })
        # raise_exception这个参数为True,如果校验失败会直接抛出异常
        serializer.is_valid(raise_exception=True)
        record = serializer.save()
        # 同步执行测试计划
        run_plan(plan_id=pk,env_id=env_id,record_id=record.id)
        return Response(serializer.data)

class TestSceneViewSet(ModelViewSet):
    """测试场景视图"""
    queryset = TestScene.objects.all()
    serializer_class = TestSceneSerializer
    permission_classes = [IsAuthenticated]
    filterset_fields = ['testplan', 'project']

    @action(methods=['post'], detail=True)
    def run(self, request, pk, *args, **kwargs):
        # 获取环境id
        env_id = request.data.get('env')
        # 执行测试场景
        res = run_scene(pk=pk, env_id=env_id)
        return Response(res)


class TestSceneStepViewSet(ModelViewSet):
    queryset = SceneData.objects.all().order_by('sort')
    serializer_class = TestSceneStepSerializer
    permission_classes = [IsAuthenticated]
    filterset_fields = ['scene']

    # order方法是实际的视图方法,写法跟普通的类试图中视图方法写法一致
    # 装饰器action需要修饰整个额外的方法
    # 其中参数mathods接受的是列表,表示要处理的http请求方法
    # 额外的方法会自动生成路由,默认情况会使用方法的名称
    # 例如:/test_scene_steps/order
    # 1.当detail=False时会生成/test_scene_steps/order  表示要处理查询集
    # 2.当detail=True时会生成/test_scene_steps/<int:pk>/order  表示要处理单个对象
    # 自定义url路径,使用url_path参数,会代替方法名
    # url_name参数是修改url默认的名字,默认的名字是小写模型名_方法名
    @action(methods=['put'], detail=False, )
    def order(self, request, *args, **kwargs):
        """排序接口"""
        objs = []
        for item in request.data:
            obj = SceneData.objects.get(pk=item['id'])
            obj.sort = item['sort']
            obj.save()  # 单独保存,推荐
            # objs.append(obj)
        # SceneData.objects.abulk_update(objs, ['sort'])  # 批量保存
        return Response(request.data)

3. 序列化器

# -*- coding: utf-8 -*-
# time: 2022/11/4 15:27
# file: serializers.py
# author: fade


from rest_framework import serializers

from projects.models import Interface
from projects.serializers import InterfaceSerializer
from .models import TestStep, TestPlan, SceneData, UploadFile, TestScene


class NestInterfaceSerializer(serializers.ModelSerializer):
    """嵌套测试步骤(接口)详情序列化器"""
    # 可以用这个方法或者用depth,分离两个序列化器得原因是:不是每个操作都需要嵌套,当查看得时候才需要嵌套;
    interface = InterfaceSerializer()

    class Meta:
        model = TestStep
        fields = "__all__"


class TestStepSerializer(serializers.ModelSerializer):
    """接口用例/测试步骤序列化器"""

    class Meta:
        model = TestStep
        fields = '__all__'
        # 嵌套外键字段,这里使用嵌套得话,创建得时候就无法设置那个接口了,interface这个参数被设置为只读,所以要分情况去返回序列化器(见view);
        # depth = 1


class UploadFileSerializer(serializers.ModelSerializer):
    """文件上传序列化器"""

    class Meta:
        model = UploadFile
        fields = '__all__'
        extra_kwargs = {'file': {'write_only': True}, 'info': {'read_only': True}}


class TestPlanSerializer(serializers.ModelSerializer):
    """测试计划"""

    class Meta:
        model = TestPlan
        fields = '__all__'


class TestSceneSerializer(serializers.ModelSerializer):
    """测试场景序列化器"""

    class Meta:
        model = TestScene
        fields = '__all__'


class NestTestStepSerializer(serializers.ModelSerializer):
    """嵌套测试步骤序列化器"""

    class Meta:
        model = TestStep
        fields = ['id', 'title']


class TestSceneStepSerializer(serializers.ModelSerializer):
    """场景步骤序列化器"""
    stepInfo = NestTestStepSerializer(source='step', read_only=True)

    class Meta:
        model = SceneData
        fields = '__all__'


# class InterfaceRunSerializer(serializers.ModelSerializer):
#     class Meta:
#         model = Interface
#         fields = '__all__'


class TestStepRunSerializer(serializers.ModelSerializer):
    interface = InterfaceSerializer()

    class Meta:
        model = TestStep
        fields = '__all__'


class TestSceneStepRunSerializer(serializers.ModelSerializer):
    step = TestStepRunSerializer()

    class Meta:
        model = SceneData
        fields = '__all__'


class TestSceneRunSerializer(serializers.ModelSerializer):
    scenedata_set = TestSceneStepRunSerializer(many=True)  # 因为是多条数据,所以设置many=True

    class Meta:
        model = TestScene
        fields = '__all__'


class TestPlanRunSerializer(serializers.ModelSerializer):
    scenes = TestSceneRunSerializer(many=True)

    class Meta:
        model = TestPlan
        fields = '__all__'

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值