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

前言

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

新建Django项目

安装依赖包

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并登录。

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

请求默认配置:

headers: x-text: abc123

变量:

a: 1b: 2

请求数据:

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

提取请求:

- res_url: content.url

断言:

- eq: [status_code, 200]

点击保存。

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

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

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

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

(0)

相关推荐

  • Django调用百度AI接口实现人脸注册登录代码实例

    面部识别----考勤打卡.注册登录.面部支付等等...感觉很高大上,又很方便,下面用python中的框架--django完成一个注册登录的功能,调用百度AI的接口,面部识别在网上也有好多教程,可以自己建模,训练模型,但是这都需要大量的数据去提高模型的准确度,我们直接用了百度AI的接口,十分的快捷.高效.准确,下来码一下代码啦!! 首先需要在百度AI官网注册一个应用,免费,并提供强大的人脸库. 1.注册表单 <div class="tab-content"> <div

  • 在Heroku云平台上部署Python的Django框架的教程

    Heroku是一个很棒的平台,它有很多的控件,并且搭建环境相对来说也比较容易.本指南中,我将一步一步指导你在Heroku平台上部署一个简单地Django应用 搭建开发环境 Heroku工具链 假设你已经在Heroku平台上注册了一个帐户,并且在里面创建了一款应用,为了一会儿通过CLI与Heroku交互,你需要安装Heroku工具链.在这篇指南中,我们用"Sample-Project"作为应用的名字. Git仓库 在部署你的应用到Heroku之前,你需要先将你的代码签入git仓库中.He

  • Django的HttpRequest和HttpResponse对象详解

    本文研究的主要是Django的HttpRequest和HttpResponse对象的相关内容,具体如下. 请求一张页面时,Django把请求的metadata数据包装成一个HttpRequest对象,然后Django加载合适的view方法,把这个HttpRequest 对象作为第一个参数传给view方法.任何view方法都应该返回一个HttpResponse对象. 我们在本书中大量使用这两个对象:本附录详细解释HttpRequest和HttpResponse对象. HttpRequest Htt

  • Django框架HttpRequest对象用法实例分析

    本文实例讲述了Django框架HttpRequest对象用法.分享给大家供大家参考,具体如下: 1.URL路径参数 (1)位置参数:使用正则分组,与视图中的参数一一对应,不可换位置 例: url(r'^(\d+)/(\d+)/$', views.index) def index(request, 参数1, 参数2): ... (2)关键字参数:参数位置可以变,与关键字保持一致即可 例: url(r'^(?P<value1>\d+)/(?P<value2>\d+)/$', views

  • Django+RestFramework API接口及接口文档并返回json数据操作

    系统:ubuntu18.04 x64 GitHub:https://github.com/xingjidemimi/DjangoAPI.git 安装 pip install django==2.1.5 pip install djangorestframework # rest api pip install coreapi pygments markdown # 自动化接口文档 API示例 创建django项目 django-admin startproject DjangoAPI 创建应用

  • Django使用HttpResponse返回图片并显示的方法

    做了一个关于Django的小案例,想要在网页中显示图片,直接在img标签的src属性写图片的路径是不能显示的,查询资料发现在Django中使用图片这类的资源相当繁琐需要进行一定D的配置,摸索了一会没有整明白,想到了写Java时使用文件流返回图片,于是想到使用该种方式来显示图片. 使用实例如下: views.py def my_image(request,news_id): d = path.dirname(__file__) #parent_path = path.dirname(d) prin

  • 使用Django简单编写一个XSS平台的方法步骤

    1) 简要描述 原理十分简单2333,代码呆萌,大牛勿喷 >_< 2) 基础知识 XSS攻击基本原理和利用方法 Django框架的使用 3) Let's start 0x01 工欲善其事必先利其器,首先我们需要准备编写代码的各种工具和环境,这里不细说.我这里的环境和工具如下: python 3.7.0 pycharm windows 10 mysql 8.0.15 Django 2.1.3 需要用到的第三方库: django pymysql requests 0x02 我们先看一下XSS脚本是

  • Django接收自定义http header过程详解

    add by zhj: Django将所有http header(包括你自定义的http header)都放在了HttpRequest.META这个Python标准字典中,当然HttpRequest.META 中还包含其它一些键值对,这些键值对是Django加进去的,如SERVER_PORT等.对于http header,Django进行了重命名,规则如下 (1) 所有header名大写,将连接符"-"改为下划线"_" (2) 除CONTENT_TYPE和CONTE

  • Django框架HttpResponse对象用法实例分析

    本文实例讲述了Django框架HttpResponse对象用法.分享给大家供大家参考,具体如下: 1.HttpResponse 可通过HttpResponse构造响应对象: HttpResponse(content=响应体, content_type=响应体数据类型, status=状态码) 响应头可以直接将HttpResponse对象当做字典进行响应头键值对的设置: response = HttpResponse() response['itcast'] = 'Python' # 自定义响应头

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

    前言 这是一个使用HttpRunner开发接口平台的简单Demo. 新建Django项目 安装依赖包 pip install httprunner=1.5.6 -i https://pypi.doubanio.com/simple/ 模型规划 项目Project:包含 名称.创建时间.修改时间 测试套件TestSuite:对应HttpRunner的一个yaml文件,包含所属项目.name.base_url.request请求配置.variables用户自定义变量.创建时间.修改时间 测试用例Te

  • Django Admin实现上传图片校验功能

    Django 为未来的开发人员提供了许多功能:一个成熟的标准库,一个活跃的用户社区,以及 Python 语言的所有好处.虽然其他 Web 框架也声称能提供同样的内容,但 Django 的独特之处在于它内置了管理应用程序 -- admin. admin 提供了开箱即用的高级 Create-Read-Update-Delete (CRUD) 功能,减少了重复工作所需的时间.这是许多 Web 应用程序的关键所在,程序员可以在开发时快速浏览他们的数据库模型:非技术最终用户可以在部署时使用 admin 添

  • Django admin美化插件suit使用示例

    本文主要对Django美化插件做一个简单介绍,具体如下. Django Suit 效果 使用前django页面 使用后django页面 安装 官方文档 http://django-suit.readthedocs.io/en/develop/index.html pip install django-suit 环境:python2.7.10,django1.9.8 配置使用 settings设置 INSTALLED_APPS = [ 'suit', # 添加suit支持 'django.cont

  • Django admin实现图书管理系统菜鸟级教程完整实例

    Django 有着强大而又及其易用的admin后台,在这里,你可以轻松实现复杂代码实现的功能,如搜索,筛选,分页,题目可编辑,多选框. 简单到,一行代码就可以实现一个功能,而且模块之间耦合得相当完美. 不信,一起来看看吧!?用Django实现管理书籍的系统,并能在前台界面对书籍进行增删查改,筛选,分页,以及批量查询修改功能. 准备工作 #准备好你的数据库模型思维导图 0.新建一个Django项目,起名为books,并且同时新建一个应用book11 1.首先要设置models模块,根据思维导图,我

  • django admin添加数据自动记录user到表中的实现方法

    1.需求:在后台添加一条数据的同时要把添加者记录到表中. 2.models.py class Setting(models.Model): ... user = models.CharField(max_length=50, blank=True, help_text=u"添加者") ... 3.admin.py @register(Setting) class SettingAdmin(admin.ModelAdmin): list_display = ("pk"

  • django admin.py 外键,反向查询的实例

    如下所示: class OrderAdmin(admin.ModelAdmin): list_display = ( '_nick_name', 'time_order', 'year', 'item', 'status', 'number', 'money', 'deduction_point', 'deduction_account', 'pay', '_open_id', 'out_trade_no', ) search_fields = [ 'user__nick_name', 'use

  • 在django admin中添加自定义视图的例子

    django admin提供了完善的用户管理和数据模型管理,方便实用.研究了一下在admin里面添加自己的页面. 在admin.py里继承django.contrib.admin.ModelAdmin基类 class FaceAdmin(admin.ModelAdmin): 然后在里面写自己的视图处理函数.基类里面的save_model和delete_model函数可以做数据对象的新建和删除的预处理和后处理.自建的函数如果要redirect到自己建的view,反向映射要包含admin:,写成 r

  • 在Django admin中编辑ManyToManyField的实现方法

    如何定义多对多关系 Django 本身自带了一个很强大的ORM,支持自定义model并将其映射到数据库的表中 model中可以定义各种类型的数据,比较直观的比如整形, 字符型,也可以定义外键 ForeignKey来指定一对多关系. 关系数据库中有一种多对多的关系,在Django的ORM中也支持它,使用ManyToManyField. 下面使用EmailGroup和Email来展示如何使用 class EmailTo(models.Model): name = models.CharField(m

  • Django admin.py 在修改/添加表单界面显示额外字段的方法

    问题描述: 我有个blogextra表继承自blog,现在我想在blog的admin管理change界面显示对应的blogextra字段 解决方法: 可以使用admin.py的inline内联方法 代码: models.py from django.db import models # Create your models here. class Blog(models.Model): Name = models.CharField(max_length=350) def __unicode__

  • Django admin model 汉化显示文字的实现方法

    1.将添加blog的后台基本操作 在blog文件夹下新建一个admin.py文件加入一下代码: from django.contrib import admin from djcms.apps.blog.models import Post, Category, Tag admin.site.register(Post) admin.site.register(Category) admin.site.register(Tag) 重启服务,再到浏览器上登录到后台,是不是增加了对blog的基本操作

随机推荐