通过Django Admin+HttpRunner1.5.6实现简易接口测试平台


Posted in Python onNovember 11, 2020

前言

这是一个使用HttpRunner开发接口平台的简单Demo。

新建Django项目

通过Django Admin+HttpRunner1.5.6实现简易接口测试平台

安装依赖包

通过Django Admin+HttpRunner1.5.6实现简易接口测试平台

pip install httprunner=1.5.6 -i https://pypi.doubanio.com/simple/

模型规划

  • 项目Project:包含 名称、创建时间、修改时间
  • 测试套件TestSuite:对应HttpRunner的一个yaml文件,包含所属项目、name、base_url、request请求配置、variables用户自定义变量、创建时间、修改时间
  • 测试用例TestCase:对应HttpRunner中的一个test段,包含所属TestSuite、name、skip、request、validate、extract、创建时间、修改时间
  • 测试结果TestResult:测试套件运行的一次结果信息,包含所属TestSuite、HttpRunner运行summary中的时间信息、统计信息、平台信息、详情等

自定义YamlField

由于TestSuite中的request、variables以及用例中的request我们需要使用Python的字典格式,用例中的validate和extract需要使用Python的列表格式。而Django中这些只能按字符串格式TextField存储。

我们编写一个自定义YamlField,存库时按字符串存,读取时转为Python字典或列表。

在apitest目录下新建fields.py,内容如下。

串存,读取时转为Python字典或列表。
在apitest目录下新建fields.py,内容如下。

import yaml
from django.db import models

class YamlField(models.TextField):
  def to_python(self, value): # 将数据库内容转为python对象时调用
    if not value:
      value = {}
    if isinstance(value, (list, dict)):
      return value
    return yaml.safe_load(value)

  def get_prep_value(self, value): # create时插入数据, 转为字符串存储
    return value if value is None else yaml.dump(value, default_flow_style=False)

  def from_db_value(self, value, expression, connection): # 从数据库读取字段是调用
    return self.to_python(value)

使用抽象模型

由于好几个项目、测试套件、测试用例都需要名称、创建时间、修改时间三个属性。为了简化代码,这里创建一个抽象模型ModelWithName,抽象模型用来通过继承来复用属性,并不会创建表。
修改apitest/models.py,添加:

from django.db import models
class ModelWithName(models.Model):
  class Meta:
    abstract = True

  name = models.CharField("名称", max_length=200)
  created = models.DateTimeField('创建时间', auto_now_add=True)
  modified = models.DateTimeField('最后修改时间', auto_now=True)
  def __str__(self):
    return self.name

编写模型

修改apitest/models.py,添加:

class Project(ModelWithName):
  class Meta:
    verbose_name_plural = verbose_name = '项目'


class TestSuite(ModelWithName):
  """对应httprunner的一个yaml文件"""
  class Meta:
    verbose_name_plural = verbose_name = '测试套件'
  project = models.ForeignKey(Project, verbose_name='项目', related_name='suites', on_delete=models.CASCADE)
  base_url = models.CharField('域名', max_length=500, blank=True, null=True) # 对应config/base_url
  request = YamlField('请求默认配置', blank=True) # 对应config/request
  variables = YamlField('变量', blank=True)

class TestCase(ModelWithName):
  """对应httprunner中的一个test"""
  class Meta:
    verbose_name_plural = verbose_name = '测试用例'

  suite = models.ForeignKey(TestSuite, verbose_name='测试套件', related_name='tests', on_delete=models.CASCADE)
  skip = models.BooleanField('跳过', default=False)
  request = YamlField('请求数据') # 对应config/request
  extract = YamlField('提取请求', blank=True)
  validate = YamlField('断言', blank=True)

class TestResult(models.Model):
  class Meta:
    verbose_name_plural = verbose_name = '测试结果'

  suite = models.ForeignKey(TestSuite, verbose_name='测试套件', related_name='results', on_delete=models.CASCADE)
  success = models.BooleanField('成功')
  start_at = models.DateTimeField('开始时间')
  duration = models.DurationField('持续时间')
  platform = models.TextField('平台信息')
  test_run = models.SmallIntegerField('运行')
  successes = models.SmallIntegerField('成功')
  skipped = models.SmallIntegerField('跳过')
  failures = models.SmallIntegerField('失败')
  errors = models.SmallIntegerField('出错')
  expected_failures = models.SmallIntegerField('预期失败')
  unexpected_successes = models.SmallIntegerField('非预期成功')
  details = models.TextField('详情')
  created = models.DateTimeField('创建时间', auto_now_add=True)

  def __str__(self):
    return self.suite.name + '-测试结果'

HttpRunner运行结果的summary的格式如下:

{'platform': {'httprunner_version': '1.5.6', 'platform': 'Darwin-19.2.0-x86_64-i386-64bit', 'python_version': 'CPython 3.6.5'},
 'stat': {'errors': 0, 'expectedFailures': 0,'failures': 0,'skipped': 0,'successes': 1,'testsRun': 1,'unexpectedSuccesses': 0},
 'success': True,
 'time': {'duration': 2.2655465602874756, 'start_at': 1587895780.3771362}}
 'details': [  # 每个对应一个测试套件
  {'name': '套件名称',
   'base_url': 'https://httpbin.org',
   'stat': {'errors': 0, 'expectedFailures': 0,'failures': 0,'skipped': 0,'successes': 1,'testsRun': 1,'unexpectedSuccesses': 0},
   'success': True,
   'time': {'duration': 2.2655465602874756, 'start_at': 1587895780.3771362}},
   'output': [],
   'records': [  # 对应每一条用例
     {
      'name': '用例名',
      'status': 'success',
      'meta_data': {'request': {'url': ..., 'method': ..., 'start_timestamp': ...}, 
                 'response': {'content': ..., 'text': ..., 'json': ..., 'headers': ..., 'status_code': ..., 'elapsed_ms': ...}}
      'attachment': ['出错信息']
     }
   ]
 }

这里TestResult模型,对summary结果的信息做了简单的拆解。

组装用例数据

对于用例TestCase,我们需要将其name、skip、request、validate、extract组装成HttpRunner的字典格式。
在apitest/models.py的TestCase类中添加data属性方法,代码如下:

class TestCase(ModelWithName):
  ....
  @property
  def data(self):
    return dict(name=self.name,skip=self.skip,request=self.request,extract=self.extract,validate=self.validate)

一个套件最后解析后应该是包含name、config、apis、testcases的一个字典,我们需要将TestSuite对象及包含的所有TestCase对象组装成如下格式。

{"name": "套件名称", "config" : {...}, "apis": {}, "testcases": []}

补充:加载debugtalk.py的方法
config中可以指定一个yaml的path路径,会自动加载该路径下的debugtalk.py文件

- utils
   - config.yaml # 空文件即可
   - debugtalk.py

config的格式可以为:

config: 
   name: ...
   request: ...
   variables: ...
   path: .../config.yaml

这样可以自动加载debugtalk.py中的函数以供使用。

在apitest/models.py的TestSuite类中添加data属性方法,代码如下:

@property
  def data(self):
    request = self.request
    request['base_url'] = self.base_url
    data = dict(
      name=self.name,
      config=dict(request=self.request, variables=self.variables),
      api={},
      testcases=[test.data for test in self.tests.all()]
    )
    return data

由于TestCase在外联TestSuite时设置了关联名称tests,因此TestSuite对象可以通过self.tests.all()查询出所有关联它的用例。

注:HttpRunner-1.5.6版本的base_url是放在config/request中的,这里做了分离,要重新放入config/request中。

编写套件运行方法

从 httprunner.task模块中导入HttpRunner类,使用TestSuite数据,运行即可。由于运行时是安多个TestSuite模式运行的,因此TestSuite的数据要放到一个列表中。

在apitest/models.py的TestSuite类添加run方法。

from httprunner.task import HttpRunner
...

class TestSuite(ModelWithName):
  ...
  def run(self):
    runner = HttpRunner().run([self.data])
    summary = runner.summary
    if summary:
      # 保存结果到TestResult
      _time = summary['time']
      _stat = summary['stat']
      TestResult.objects.create(
        suite=self, success=summary['success'],
        start_at=datetime.datetime.fromtimestamp(_time['start_at']),
        duration=datetime.timedelta(seconds=_time['duration']),
        test_run=_stat['testsRun'], successes=_stat['successes'], skipped=_stat['skipped'], errors=_stat['errors'],
        failures=_stat['failures'], expected_failures=_stat['expectedFailures'],
        unexpected_successes=_stat['unexpectedSuccesses'],
        platform=json.dumps(summary['platform'], indent=2, ensure_ascii=False),
        details=summary['details']
      )
    return summary

运行后,解析summary并创建TestResult对象保存本次运行结果。

模型完整代码

import datetime
import json

from django.db import models
from httprunner.task import HttpRunner

from .fields import YamlField


class ModelWithName(models.Model):
  class Meta:
    abstract = True

  name = models.CharField("名称", max_length=200)
  created = models.DateTimeField('创建时间', auto_now_add=True)
  modified = models.DateTimeField('最后修改时间', auto_now=True)
  
  def __str__(self):
    return self.name

class Project(ModelWithName):
  class Meta:
    verbose_name_plural = verbose_name = '项目'


class TestSuite(ModelWithName):
  """对应httprunner的一个yaml文件"""
  class Meta:
    verbose_name_plural = verbose_name = '测试套件'
  project = models.ForeignKey(Project, verbose_name='项目', related_name='suites', on_delete=models.CASCADE)
  base_url = models.CharField('域名', max_length=500, blank=True, null=True) # 对应config/base_url
  request = YamlField('请求默认配置', blank=True) # 对应config/request
  variables = YamlField('变量', blank=True)

  @property
  def data(self):
    request = self.request
    request['base_url'] = self.base_url
    data = dict(
      name=self.name,
      config=dict(request=self.request, variables=self.variables),
      api={},
      testcases=[test.data for test in self.tests.all()]
    )
    return data

  def run(self):
    runner = HttpRunner().run([self.data])
    summary = runner.summary
    if summary:
      # 保存结果到TestResult
      _time = summary['time']
      _stat = summary['stat']
      TestResult.objects.create(
        suite=self, success=summary['success'],
        start_at=datetime.datetime.fromtimestamp(_time['start_at']),
        duration=datetime.timedelta(seconds=_time['duration']),
        test_run=_stat['testsRun'], successes=_stat['successes'], skipped=_stat['skipped'], errors=_stat['errors'],
        failures=_stat['failures'], expected_failures=_stat['expectedFailures'],
        unexpected_successes=_stat['unexpectedSuccesses'],
        platform=json.dumps(summary['platform'], indent=2, ensure_ascii=False),
        details=summary['details']
      )
    return summary


class TestCase(ModelWithName):
  """对应httprunner中的一个test"""
  class Meta:
    verbose_name_plural = verbose_name = '测试用例'

  suite = models.ForeignKey(TestSuite, verbose_name='测试套件', related_name='tests', on_delete=models.CASCADE)
  skip = models.BooleanField('跳过', default=False)
  request = YamlField('请求数据') # 对应config/request
  extract = YamlField('提取请求', blank=True)
  validate = YamlField('断言', blank=True)

  @property
  def data(self):
    return dict(name=self.name,skip=self.skip,request=self.request,extract=self.extract,validate=self.validate)


class TestResult(models.Model):
  class Meta:
    verbose_name_plural = verbose_name = '测试结果'

  suite = models.ForeignKey(TestSuite, verbose_name='测试套件', related_name='results', on_delete=models.CASCADE)
  success = models.BooleanField('成功')
  start_at = models.DateTimeField('开始时间')
  duration = models.DurationField('持续时间')
  platform = models.TextField('平台信息')
  test_run = models.SmallIntegerField('运行')
  successes = models.SmallIntegerField('成功')
  skipped = models.SmallIntegerField('跳过')
  failures = models.SmallIntegerField('失败')
  errors = models.SmallIntegerField('出错')
  expected_failures = models.SmallIntegerField('预期失败')
  unexpected_successes = models.SmallIntegerField('非预期成功')
  details = models.TextField('详情')
  created = models.DateTimeField('创建时间', auto_now_add=True)

  def __str__(self):
    return self.suite.name + '-测试结果'

使用Django Admin

修改apitest/admin.py,代码如下:

from django.contrib import admin

from apitest import models


@admin.register(models.Project)
class ProjectAdmin(admin.ModelAdmin):
  list_display = ('name', 'created', 'modified')


class TestCaseInline(admin.StackedInline):
  model = models.TestCase
  extra = 1


@admin.register(models.TestSuite)
class TestSuiteAdmin(admin.ModelAdmin):
  inlines = [TestCaseInline]
  list_display = ('name', 'project', 'base_url', 'created', 'modified')
  list_filter = ('project', )

  actions = ("run", )

  def run(self, request, queryset):
    for suite in queryset:
      suite.run()
  run.short_description = "运行"


@admin.register(models.TestResult)
class TestResultAdmin(admin.ModelAdmin):
  readonly_fields = ('suite', 'success', 'start_at', 'duration', 'platform',
            'test_run', 'successes', 'skipped', 'failures', 'errors',
            'expected_failures', 'unexpected_successes', 'details', 'created')
  fields = (('suite', 'success'),
       ('start_at', 'duration'),
       ('platform',),
       ('test_run', 'successes', 'skipped', 'failures', 'errors', 'expected_failures', 'unexpected_successes'),
       ('details',)
       )
  list_display = ('suite', 'success', 'test_run', 'successes', 'errors', 'failures', 'start_at', 'duration')
  list_filter = ('suite', )

这里将项目、测试套件、测试结果三个模型注册到Admin后台,测试用例则作为内联模型放到测试套件中进行编辑。
在测试套件模型中,自定义了一个“运行”,操作,支持运行选中的用例。

运行并测试项目

打开terminal终端,执行数据库变更并创建超级管理员。

python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py createsuperuser

运行开发服务器

python3 manage.py runserver

访问http://127.0.0.1:8000/admin并登录。

通过Django Admin+HttpRunner1.5.6实现简易接口测试平台

创建一个项目,测试项目,然后创建一个TestSuite,如下:

通过Django Admin+HttpRunner1.5.6实现简易接口测试平台

请求默认配置:

headers: x-text: abc123

变量:

a: 1b: 2

通过Django Admin+HttpRunner1.5.6实现简易接口测试平台

请求数据:

url: /getmethod: GETparams: a: $a b: $b

提取请求:

- res_url: content.url

断言:

- eq: [status_code, 200]

点击保存。

回到TestSuite列表,选中测试套件,动作下拉框中选择“运行”,点击Go按钮。

通过Django Admin+HttpRunner1.5.6实现简易接口测试平台

返回测试结果列表、查看测试结果。

通过Django Admin+HttpRunner1.5.6实现简易接口测试平台

通过Django Admin+HttpRunner1.5.6实现简易接口测试平台

程序代码https://github.com/hanzhichao/apirunner

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
下载安装setuptool和pip linux安装pip    
Jan 24 Python
python中文编码问题小结
Sep 28 Python
在Python的Django框架中更新数据库数据的方法
Jul 17 Python
Django框架中的对象列表视图使用示例
Jul 21 Python
python微信好友数据分析详解
Nov 19 Python
Python OpenCV实现视频分帧
Jun 01 Python
python 字典 setdefault()和get()方法比较详解
Aug 07 Python
简单分析python的类变量、实例变量
Aug 23 Python
使用Python将字符串转换为格式化的日期时间字符串
Sep 01 Python
解析python实现Lasso回归
Sep 11 Python
Python reshape的用法及多个二维数组合并为三维数组的实例
Feb 07 Python
关于 Python json中load和loads区别
Nov 07 Python
Django自定义YamlField实现过程解析
Nov 11 #Python
Python监听剪切板实现方法代码实例
Nov 11 #Python
如何通过python计算圆周率PI
Nov 11 #Python
python中turtle库的简单使用教程
Nov 11 #Python
python 怎样进行内存管理
Nov 10 #Python
python tqdm实现进度条的示例代码
Nov 10 #Python
python 解决Windows平台上路径有空格的问题
Nov 10 #Python
You might like
ThinkPHP的Widget扩展实例
2014/06/19 PHP
PHP中比较时间大小实例
2014/08/21 PHP
关于PHP中Session文件过多的问题及session文件保存位置
2016/03/17 PHP
Yii2增加验证码步骤详解
2016/04/25 PHP
jquery 表格的增行删行实现思路
2013/03/21 Javascript
jQuery 绑定事件到动态创建的元素上的方法实例
2013/08/18 Javascript
js加密解密字符串可自定义密码因子
2014/05/13 Javascript
DOM 高级编程
2015/05/06 Javascript
Javascript仿新浪游戏频道鼠标悬停显示子菜单效果
2015/08/21 Javascript
Node.js用readline模块实现输入输出
2016/12/16 Javascript
javascript 网页进度条简单实例
2017/02/22 Javascript
详解利用jsx写vue组件的方法示例
2017/07/17 Javascript
vue2.0 资源文件assets和static的区别详解
2018/04/08 Javascript
通过封装scroll.js 获取滚动条的值
2018/07/13 Javascript
layDate插件设置开始和结束时间
2018/11/15 Javascript
浅谈Vue.js 中的 v-on 事件指令的使用
2018/11/25 Javascript
小程序自定义圆形进度条
2020/11/17 Javascript
Python RuntimeError: thread.__init__() not called解决方法
2015/04/28 Python
详解Django中的ifequal和ifnotequal标签使用
2015/07/16 Python
基于Python在MacOS上安装robotframework-ride
2018/12/28 Python
零基础使用Python读写处理Excel表格的方法
2019/05/02 Python
Python处理时间日期坐标轴过程详解
2019/06/25 Python
Python绘制堆叠柱状图的实例
2019/07/09 Python
Django REST框架创建一个简单的Api实例讲解
2019/11/05 Python
Win系统PyQt5安装和使用教程
2019/12/25 Python
Python 使用 PyQt5 开发的关机小工具分享
2020/07/16 Python
python 实现表情识别
2020/11/21 Python
手机端用rem+scss做适配的详解
2017/11/15 HTML / CSS
phpquery中文手册
2021/03/18 PHP
乡镇2014法制宣传日活动总结
2014/11/01 职场文书
2015年双拥工作总结
2015/04/08 职场文书
五星级酒店宣传口号
2015/12/25 职场文书
使用Pytorch训练two-head网络的操作
2021/05/28 Python
vue实现移动端div拖动效果
2022/03/03 Vue.js
MySQL普通表如何转换成分区表
2022/05/30 MySQL
使用 Koa + TS + ESLlint 搭建node服务器的过程详解
2022/05/30 NodeJs