通过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 相关文章推荐
在Python的setuptools框架下生成egg的教程
Apr 13 Python
简单介绍Python的Tornado框架中的协程异步实现原理
Apr 23 Python
python通过smpt发送邮件的方法
Apr 30 Python
python 2.7.14安装图文教程
Apr 08 Python
python学习笔记--将python源文件打包成exe文件(pyinstaller)
May 26 Python
Python3 使用cookiejar管理cookie的方法
Dec 28 Python
用Python和WordCloud绘制词云的实现方法(内附让字体清晰的秘笈)
Jan 08 Python
基于python traceback实现异常的获取与处理
Dec 13 Python
TensorFlow内存管理bfc算法实例
Feb 03 Python
Django中的session用法详解
Mar 09 Python
Django 允许局域网中的机器访问你的主机操作
May 13 Python
简单介绍一下pyinstaller打包以及安全性的实现
Jun 02 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
php下拉选项的批量操作的实现代码
2013/10/14 PHP
浅谈PHP实现大流量下抢购方案
2017/12/15 PHP
PHP递归统计系统中代码行数
2019/09/19 PHP
获取JavaScript用户自定义类的类名称的代码
2007/03/08 Javascript
JQuery实现自定义对话框的代码
2008/06/15 Javascript
javascript 延迟加载技术(lazyload)简单实现
2011/01/17 Javascript
文字不间断滚动(上下左右)实例代码
2013/04/21 Javascript
JavaScript编程中的Promise使用大全
2015/07/28 Javascript
常用的JQuery函数及功能小结
2016/03/24 Javascript
bootstrap快速制作后台界面
2016/12/05 Javascript
JS常用知识点整理
2017/01/21 Javascript
vuejs指令详解
2017/02/07 Javascript
一个简单的node.js界面实现方法
2018/06/01 Javascript
Angular封装搜索框组件操作示例
2019/04/25 Javascript
微信小程序之高德地图多点路线规划过程示例详解
2021/01/18 Javascript
[02:57]2014DOTA2国际邀请赛-观众采访
2014/07/19 DOTA
python+matplotlib绘制旋转椭圆实例代码
2018/01/12 Python
TensorFlow实现卷积神经网络CNN
2018/03/09 Python
tensorflow实现加载mnist数据集
2018/09/08 Python
django-初始配置(纯手写)详解
2019/07/30 Python
django将数组传递给前台模板的方法
2019/08/06 Python
Django--权限Permissions的例子
2019/08/28 Python
Python3 全自动更新已安装的模块实现
2020/01/06 Python
Tensorflow设置显存自适应,显存比例的操作
2020/02/03 Python
结束运行python的方法
2020/06/16 Python
python-图片流传输的思路及示例(url转换二维码)
2020/12/21 Python
哥伦比亚最大的网上商店:Linio哥伦比亚
2016/09/25 全球购物
开办化妆品公司创业计划书
2013/12/26 职场文书
期末考试动员演讲稿
2014/01/10 职场文书
50岁生日感言
2014/01/23 职场文书
九一八事变演讲稿范文
2014/09/14 职场文书
对外汉语专业大学生职业生涯规划书
2014/10/11 职场文书
小学生手册家长意见
2015/06/03 职场文书
关于五一放假的通知
2015/08/18 职场文书
《蚂蚁和蝈蝈》教学反思
2016/02/22 职场文书
分享mysql的current_timestamp小坑及解决
2021/11/27 MySQL