参考github开源代码:github.com/githublitao…
环境搭建
新建项目
-
pacharm中选择django框架+虚拟环境
-
安装django
pip install django
数据库配置
-
修改settings.py
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'HOST': '127.0.0.1', # 数据库主机 'PORT': 3306, # 数据库端口 'USER': 'root', # 数据库用户名 'PASSWORD': 'admin1234', # 数据库用户密码 'NAME': 'api_test' # 数据库名字 } } -
安装相关库:
pip install mysqlclient==1.4.6pip install wheel
中间件配置
-
前提:使用rest-framework框架
-
安装 :
pip install djangorestframework -
修改settings.py :
在
INSTALLED_APPS添加'rest_framework'
-
-
格式化响应输出
-
新建
utils文件夹,新建custom_response_middleware.pyclass CustomResponseMiddleware: def __init__(self, get_response): self.get_response = get_response # 配置和初始化 def __call__(self, request): # 在这里编写视图和后面的中间件被调用之前需要执行的代码 # 这里其实就是旧的process_request()方法的代码 response = self.get_response(request) # if "code" not in response.data: # # data = response.data # response.data={ # "code":"0000", # "message":"查询成功", # "data":response.data # } # # 因返回时已经render过response,要想让这里的修改有效,需要手动在render一次 # response._is_rendered = False # response.render() # response["content-length"]=len(response.content) # 在这里编写视图调用后需要执行的代码 # 这里其实就是旧的 process_response()方法的代码 return response def process_template_response(self, request, response):# 推荐 if request.method == 'DELETE' and response.data is None: response.data = { "code": "0000", "message": "删除成功", "data": response.data } if "code" not in response.data: data = response.data response.data={ "code":"0000", "message":"操作成功", "data":response.data } # 在这里编写视图调用后需要执行的代码 # 这里其实就是旧的 process_response()方法的代码 return response -
修改settings.py :
在
MIDDLEWARE中最前面增加'utils.custom_response_middleware.CustomResponseMiddleware'。(响应中间件放最前面,请求中间件放最后面)
-
重写异常类
utils/custom_exception.py
from rest_framework import status
from rest_framework.exceptions import ValidationError
from rest_framework.views import exception_handler as drf_exception_handler
from utils.custom_response import CustomResponse
def exception_handler(exc,context):
"""
自定义异常处理
:param exc: 别的地方抛的异常就会传给exc
:param context: 字典形式。抛出异常的上下文(即抛出异常的出处;即抛出异常的视图)
:return: Response响应对象
"""
response = drf_exception_handler(exc,context)
if response is None:
# drf 处理不了的异常
print('%s - %s - %s' % (context['view'], context['request'].method, exc))
return CustomResponse({'detail': '服务器错误'}, code=500,msg="服务器内部错误",status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True)
if isinstance(exc,ValidationError):
message = ""
data = response.data
for key in data:
message += ";".join(data[key])
return CustomResponse(None,code="9999",msg=message)
return response
修改settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER':'utils.custom_exception.exception_handler',
}
重写响应类
utils/custom_response.py
from rest_framework.response import Response
# 重写响应
class CustomResponse(Response):
def __init__(self, *args, code='0000', msg="成功", **kwargs):
# 格式化data
data = {
"code": code,
"message": msg
}
if args is not None:
data["data"] = args[0]
kwargs["data"] = data
elif "data" in kwargs:
data["data"] = kwargs["data"]
kwargs["data"] = data
super().__init__(**kwargs)
实战
新建应用
-
python manage.py startapp guoya_api -
添加应用到
settings.py的INSTALLED_APPS中
路由分发
主路由:
urlpatterns = [
path('admin/', admin.site.urls),
path('v01/', include('guoya_api.urls')),
]
子路由:
urlpatterns = [
]
models
文件位置:guoya_api>models
-
直接使用开源代码中的models,再进行修改: github.com/githublitao…
-
数据迁移
生成迁移脚本:
python manage.py makemigrations执行迁移标本:
python manage.py migrate
对模型创建序列化器
文件位置:guoya_api>serializers.py
直接使用开源代码中的serializers:
-
对模型中的Project的序列化器进行修改
因为这几个字段、相关表都没有数据,先注释掉。
创建视图
使用到过滤后端
settings.py中INSTALLED_APPS添加'django_filters'
实现新增项目、查询项目(操作多个项目)
import django_filters
from . import serializers
from . import models
from rest_framework import generics
from rest_framework.response import Response
# 选择:
# APIView 不支持搜索展示所有
# GenericAPIView 支持搜索展示所有,使用过滤后端
# ModelViewSet
# 操作多个项目
class Projects(generics.GenericAPIView):
queryset = models.Project.objects.all()
serializer_class = serializers.ProjectSerializer
# 针对某个GenericAPIView视图添加过滤后端
filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
# 设置查询关键字,一对一和一对多 可以使用__来指定具体的属性
filterset_fields = ['name']
def get(self, request, *args, **kwargs):
"""
获取项目列表
"""
# 使用filter_queryset方法对查询集进行过滤
projects = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(instance=projects, many=True)
return Response(serializer.data)
def post(self, request, *args, **kwargs):
"""
新增项目
"""
# 拿到前端传入数据
data = request.data
serializer = serializers.ProjectDeserializer(data=data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.validated_data)
-
效果
post请求:新增项目
get请求:获取项目,支持按项目名精确搜索(如要实现模糊查询,参考前面讲过滤后端时的自定义查询类)
实现查询、修改、删除单个项目
restframework中对单个查、删、改时,如:
-
get请求
127.0.0.1:8000/projects/1表示查询id为1的数据 -
put请求
127.0.0.1:8000/projects/1表示修改id为1的数据 -
del请求
127.0.0.1:8000/projects/1表示删除id为1的数据
因为查询和修改、删除使用的不是一个序列化器,所以要分开使用两个不同的类
查询单个使用:GenericAPIView+mixins:RetireveAPIView
修改、删除单个使用:GenericAPIView+mixins:UpdateAPIView、DestoryAPIView
views.py
# 操作单个项目
class Project(mixins.DestroyModelMixin,mixins.UpdateModelMixin,generics.GenericAPIView):
queryset = models.Project.objects.all()
serializer_class = serializers.ProjectDeserializer
def get(self,request,*args, **kwargs):
"""
查询单个项目信息
"""
# 默认前端定义参数是pk时 ,相当于models.Project.objects.get(pk=pk)
project = self.get_object()
return Response(serializers.ProjectSerializer(instance=project).data)
def put(self,request,*args, **kwargs):
"""
修改单个项目
"""
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
"""
删除单个项目
"""
return self.destroy(request, *args, **kwargs)
urls.py
urlpatterns = [
re_path(r"projects/?", views.Projects.as_view()),
re_path(r"^project/(?P<pk>[\d]+)/?$", views.Project.as_view()),
]
-
效果
查询单个项目:
修改单个项目:
删除单个项目:
实现存储测试用例相关信息的功能
-
逻辑
将请求需要的参数存储,执行测试用例时,去读取数据库数据
-
涉及的表
automationcaseapi 存储一条用例的详细信息,如用例名、请求方法、请求路径、请求参数、断言等。
automationhead 存储用例对应的请求头信息
automationparameter 存储键值对形式请求参数
automationparameterraw 存储源格式的请求参数
-
找到对应表的序列化和反序列化器
AutomationCaseApiSerializer
AutomationCaseApiDeserializer
AutomationHeadSerializer
AutomationHeadDeserializer
AutomationParameterSerializer
AutomationParameterDeserializer
AutomationParameterRawSerializer
AutomationParameterRawDeserializer
-
基础数据准备
因为实战时跳过了一些步骤,需要补充一些基础数据:
globalhost表 - 测试域名
automationgrouplevelfirst表 - 接口分组
automationgrouptestcase表 - 测试套件表
-
使用源数据执行测试用例时的传入的参数如下
{ "project_id": 1, "automationTestCase_id": 2, "name": "测试第一条用例", "httpType": "HTTP", "requestType": "POST", "apiAddress": "/login", "headDict": [{ "name": "Content-Type", "value": "application/json", "interrelate": false }, { "name": "", "value": "", "interrelate": false } ], "requestParameterType": "raw", "formatRaw": false, "requestList": "{\n \"pwd\":\"gy1234\",\n \"username\":\"leitx1234\"\n}", "examineType": "no_check", "RegularParam": "", "httpCode": "", "responseData": "" } -
使用键值对形式传入时,请求参数如下: 不同处:
requestParameterType、requestList
{ "project_id": 1, "automationTestCase_id": 2, "name": "测试第二条用例", "httpType": "HTTP", "requestType": "POST", "apiAddress": "/login", "headDict": [{
"name": "Content-Type",
"value": "application/json",
"interrelate": false
},
{
"name": "",
"value": "",
"interrelate": false
}
],
"requestParameterType": "form-data",
"formatRaw": false,
"requestList": [{"name":"username","value":"leitx1234","interrelate":false},{"name":"pwd","value":"gy1234","interrelate":false}],
"examineType": "no_check",
"RegularParam": "",
"httpCode": "",
"responseData": ""
}
- 视图
views.py
```py
class TestCases(APIView):
def post(self,request):
# 获取所有请求参数
data = request.data
print(data)
# 剔除掉不需要存入数据库的数据
headers = data.pop("headDict")
body_data = data.pop("requestList")
project_id = data.pop("project_id")
regular_param = data.pop("RegularParam")
# 反序列化时,涉及到外键时,先把外键对象拿到,再把外键对象通过save方法传入
obj = models.AutomationTestCase.objects.get(id=data.pop("automationTestCase_id"))
# 把接口详细信息存入automationcaseapi这个表中
serializer = serializers.AutomationCaseApiDeserializer(data=data)
serializer.is_valid(raise_exception=True)
# 把外键对象通过save方法传入
test_case = serializer.save(automationTestCase=obj)
for h in headers:
if h['name'] == "":
continue
serializer = serializers.AutomationHeadDeserializer(data=h)
serializer.is_valid(raise_exception=True)
serializer.save(automationCaseApi=test_case)
if data["requestParameterType"] == 'raw':
raw ={"data":body_data}
serializer= serializers.AutomationParameterRawDeserializer(data=raw)
serializer.is_valid(raise_exception=True)
serializer.save(automationCaseApi=test_case)
elif (data["requestParameterType"] =='form-data'):
for p in body_data:
serializer = serializers.AutomationParameterDeserializer(data=p)
serializer.is_valid(raise_exception=True)
serializer.save(automationCaseApi=test_case)
pass
return Response("ok")
-
路由
urlpatterns = [ re_path(r"projects/?", views.Projects.as_view()), re_path(r"^project/(?P<pk>[\d]+)/?$", views.Project.as_view()), re_path(r"cases/?", views.TestCases.as_view()), ] -
效果
源格式
源格式存储
键值对
键值对形式存储
实现执行测试用例的功能
-
定报文
至少需要知道api_id、host_id,知道host_id指向测试的环境,知道api_id就能知道测试用例的相关信息
{ "api_id":5, "host_id":1 } -
视图(似乎有点问题)
class ExcuteCase(APIView): # 选择视图:执行的逻辑需要自定义排除ModelViewSet,对查询结果不进行筛选不需要分页排除GenericAPIView # 需要自定义报文内容,综上,选择APIView def post(self, request): # 获取请求数据,暂时不实现对请求数据的校验 data = request.data # 获取要执行的接口id api_id = data["api_id"] # 获取主机id host_id = data["host_id"] # 暂时不实现校验接口数据是否存在,数据是否有缺失 # 拿到api_id、host_id后,需要通过这两个数据运行用例,视图主要写校验代码,业务逻辑代码写在common文件夹下。 # 直接调用业务逻辑的方法 run_api(host_id=host_id, api_id=api_id) case_api = models.AutomationCaseApi.objects.filter(id=api_id).first() # 最新的结果在最后一条 res = case_api.test_result.all().last() serializer = serializers.AutomationTestResultSerializer(instance=res) return Response(serializer.data) -
业务逻辑部分,写在common文件夹下automation_case.py
import json import requests from guoya_api import models, serializers def run_api(host_id, api_id): # 从数据库获取host host = models.GlobalHost.objects.filter(id=host_id).first().host # 获取接口编号为api_id的用例对象 case_api = models.AutomationCaseApi.objects.filter(id=api_id).first() data = serializers.AutomationCaseApiSerializer(instance=case_api).data # 获取请求方法 # request_method = data['requestType'].upper() # 另一种写法 request_method = case_api.requestType.upper() # 另一种写法 # 拼接url 协议名://ip:port/请求地址 url = data['httpType'].lower() + "://" + host + data["apiAddress"] print("url:{}".format(url)) # 获取请求头 header_objs = case_api.header.all() headers = {h.name: h.value for h in header_objs} # 获取请求数据 response = None param = None json_data = None if data["requestParameterType"] == "form-data": api_query_set = case_api.parameterList.all() param = {q.name: q.value for q in api_query_set} response = send_request(method=request_method,url=url,param=param,headers=headers) elif data["requestParameterType"] == "raw": print("*****raw**这里{}".format(case_api.parameterRaw)) api_body = case_api.parameterRaw # 转为字典格式 json_data = json.loads(api_body.data) response = send_request(method=request_method, url=url, data=json_data, headers=headers) response_header = response.headers response_data = response.json() response_status_code = response.status_code # 把数据存入automationtestresult表中 models.AutomationTestResult(automationCaseApi=case_api,url=url,requestType=request_method,host=host, header=json.dumps(headers),parameter="&".join(["{}={}".format(k,param[k]) for k in param]) if param is not None else json.dumps(json_data), httpStatus=response_status_code,examineType=api_id.examineType,data=None,result="PASS", responseData=json.dumps(response_data)).save() return response_header,response_data,response_status_code def send_request(method=None,url=None,param=None,data=None,headers = None): res = None if method.upper()=="POST": res = requests.request(method=method,url=url,data=param,json=data,headers = headers) elif method.upper() == 'GET': res = requests.request(method=method, url=url, param=param,headers = headers) return res