接口测试数据管理

问题思考

在接口测试过程中,由于有些接口类型并不是安全的,比如DELETE类型,上一次请求之后下一次再请求结果就不一样了。甚至有时接口之间的数据还会相互干扰, 导致接口断言失败时不能断定到底是接口程序引起的错误,还是测试数据变化引起的错误,那么该如何有效解决这个问题呢?

解决思路

通过测试数据库,每轮测试之前将数据初始化,这样避免数据干扰。
 

Django数据库管理

在之前我们的接口项目django_resutful使用的数据库是Python自带的SQLite3

Django还支持以下几种数据库:

接下来我们将会以Mysql来进行演示。

mysql简介

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件。

mysql下载安装

下载地址:https://dev.mysql.com/downloads/installer/

安装教程:自学网-Mysql教程

Navicat是一套数据库管理工具,专为简化数据库的管理及降低系统管理成本而设。Navicat 是以直觉化的图形用户界面而建的,让你可以以安全并且简单的方式创建、组织、访问并共用信息。

Django迁移MySql

修改Setting配置

首先打开setting.pyDATABASES 修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'ENGINE':'django.db.backends.mysql',
'HOST':'127.0.0.1',
'PORT':'3306',
'NAME':'django_restful',
'USER':'root',
'PASSWORD':'',
'OPTIONS':{
'isolation_level':None,
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", },

}
}

也就是将原来sqllite换成Mysql

安装MySQLdb驱动

再打开django_restful中的__init__.py,添加如下代码:

1
2
import  pymysql
pymysql.install_as_MySQLdb()

上面代码表示安装MySQLdb驱动。

连接数据库

使用Navicat连接数据库如下所示:

navicat_mysql

然后创建数据库django_restful

创建Models

Django提供了完善的模型(model)层来创建和存取数据,它包含你所储存数据的必要字段和行为。通常,每个模型对应数据库中唯一的一张表。

打开api中的models.py 创建如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.db import models

# Create your models here.
class User(models.Model):
username=models.CharField(max_length=100)
email=models.CharField(max_length=100)
groups=models.CharField(max_length=100)

def __str__(self):
return self.username


class Group(models.Model):
name=models.CharField(max_length=100)

def __str__(self):
return self.name

上面代码表示创建数据库的表,一个是User,另外一个是Group

其中def __str__()的作用是美化字段的显示,方便查看。如果没有__st__方法,显示的结果是类似<__main__.Test object at 0x0000022D6D1387B8>

Django模型字段常用类型

image
image

导入Models

创建好Model后需要分别在serializers.pyviews.py来导入,同时去掉danjo默认的数据库。

serializers.py

1
2
3
# from django.contrib.auth.models import User,Group
from rest_framework import serializers
from api.models import User,Group

views.py

1
2
3
4
# from django.contrib.auth.models import User,Group
from rest_framework import viewsets
from api.serializers import UserSerializer,GroupSerializer
from api.models import User,Group

数据库迁移

1
2
3
python manage.py makemigrations api

python manage.py migrate

迁移完成之后需要重新设置一个超级管理员账户,然后登录。

1
python manage.py createsuperuser

数据库初始化

封装初始化操作

数据初始化操作主要包括:数据库连接,数据清除、数据插入、关闭数据库。
api项目下面新建一个目录test_project 然后创建文件:mysql_action.py

扩展资料:SQL命令教程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from pymysql import connect
import yaml

class DB():
def __init__(self):
'''连接数据库'''
print('connect db')
self.conn=connect(host='127.0.0.1',user='root',
password='',db='django_restful')

def clear(self,table_name):
'''清除表中数据'''
print('clear db...')
clear_sql = 'truncate ' + table_name + ';'
with self.conn.cursor() as cursor:
#清除外键约束
cursor.execute("set foreign_key_checks=0;")
cursor.execute(clear_sql)
self.conn.commit()

def insert(self, table_name, table_data):
'''插入数据'''
print('insert db...')

#遍历数据
for key in table_data:
table_data[key] = "'" + str(table_data[key]) + "'"

key = ','.join(table_data.keys())
value = ','.join(table_data.values())

print(key)
print(value)

insert_sql = "insert into " + table_name + "(" + key + ")" + "values" + "(" + value + ")"
print(inser_sql)

with self.conn.cursor() as cursor:
cursor.execute(insert_sql)
self.conn.commit()

def close(self):
'''关闭数据库连接'''
print('close db')
self.conn.close()

def init_data(self, datas):
'''初始化数据'''
print('init db...')
for table, data in datas.items():
self.clear(table)
for d in data:
self.insert(table, d)
self.close()


if __name__ == '__main__':
db=DB()
#调试各个方法
db.clear('api_user')
db.clear('api_group')
user_data={'id':1,'username':'51zxw','email':'51zxw@163.com'}
db.insert('api_user',user_data)
group_data={'id':1,'name':'Developer'}
db.insert('api_group',group_data)
db.close()

#初始化数据
f=open('datas.yaml','r')
datas=yaml.load(f)
db.init_data(datas)

封装初始化数据

我们将初始化数据使用Yaml来封装,可以将数据与代码分离,方便测试数据的维护。在test_project目录中创建datas.yaml数据内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
api_user:
- id: 1
username: sutune
email: sutune@163.com
groups: http://127.0.0.1:8000/groups/1/

- id: 2
username: 51zxw
email: 51zxw@163.com
groups: http://127.0.0.1:8000/groups/2/

api_group:
- id: 1
name: Developer
- id: 2
name: Tester

扩展资料: Appium yaml教程 5-1~5-4

执行初始化后数据如下:

api-user-init

api-group-init

测试用例封装

test_project下面创建一个测试模块test_diango_restful.py

test_django_restful.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

import requests
import unittest
from mysql_action import DB
import yaml

class UserTest(unittest.TestCase):
def setUp(self):
self.base_url='http://127.0.0.1:8000/users'
self.auth=('51zxw','zxw20182018')

def test_001_get_user(self):
r=requests.get(self.base_url+'/1/',auth=self.auth)
result=r.json()

self.assertEqual(result['username'],'sutune')
self.assertEqual(result['email'],'sutune@163.com')

# @unittest.skip('skip add user')
def test_002_add_user(self):
form_data={'id':3,'username':'zxw666','email':'zxw666@qq.com','groups':'http://127.0.0.1:8000/groups/2/'}
r=requests.post(self.base_url+'/',data=form_data,auth=self.auth)
result=r.json()

self.assertEqual(result['username'],'zxw666')

# @unittest.skip('skip test_delete_user')
def test_003_delete_user(self):
r=requests.delete(self.base_url+'/2/',auth=self.auth)

self.assertEqual(r.status_code,204)

def test_004_update_user(self):
form_data={'email':'zxw2018@163.com'}
r=requests.patch(self.base_url+'/1/',auth=self.auth,data=form_data)
result=r.json()

self.assertEqual(result['email'],'zxw2018@163.com')

def test_005_no_auth(self):
r=requests.get(self.base_url)
result=r.json()

self.assertEqual(result['detail'],'Authentication credentials were not provided.')

class GroupTest(unittest.TestCase):
def setUp(self):
self.base_url='http://127.0.0.1:8000/groups'
self.auth=('51zxw','zxw20182018')

def test_001_group_developer(self):
r=requests.get(self.base_url+'/1/',auth=self.auth)
result=r.json()

self.assertEqual(result['name'],'Developer')


def test_002_add_group(self):
form_data={'name':'Pm'}
r=requests.post(self.base_url+'/',auth=self.auth,data=form_data)
result=r.json()

self.assertEqual(result['name'],'Pm')

def test_003_update_group(self):
form_data={'name':'Boss'}
r=requests.patch(self.base_url+'/2/',auth=self.auth,data=form_data)
result=r.json()

self.assertEqual(result['name'],'Boss')

def test_004_detele_group(self):
r=requests.delete(self.base_url+'/1/',auth=self.auth)

self.assertEqual(r.status_code,204)


if __name__ == '__main__':
db=DB()
f = open('datas.yaml', 'r')
datas = yaml.load(f)
db.init_data(datas)
unittest.main()

这样每次迭代回归测试就不用担心数据环境相互干扰的问题了。

执行用例&测试报告

test_project目录下面创建reports目录,然后新建run.py模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import unittest
from BSTestRunner import BSTestRunner
from mysql_action import DB
import time
import yaml

#数据初始化操作
db=DB()
f = open('datas.yaml', 'r')
datas = yaml.load(f)
db.init_data(datas)

#指定测试用例和测试报告的路径
test_dir = '.'
report_dir = './reports'

#加载测试用例
discover = unittest.defaultTestLoader.discover(test_dir, pattern='test_django_restful.py')

#定义报告的文件格式
now = time.strftime("%Y-%m-%d %H_%M_%S")
report_name = report_dir + '/' + now + ' test_report.html'

#运行用例并生成测试报告
with open(report_name, 'wb') as f:
runner = BSTestRunner(stream=f, title=" API Test Report", description="Django Restful Api Test Report")
runner.run(discover)

最后生成的测试报告如下:

django-api-report

日志配置

在自动化测试项目中,日志是非常重要的一个部分,特别是当运行出现错误时,需要查看日志来分析定位解决问题。
test_project目录下面创建日志配置文件 log.conf 下面日志配置文件,定义的日志的输出格式,输出路径等信息。然后创建文件夹logs存放日志信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[loggers]
keys=root,infoLogger

[logger_root]
level=DEBUG
handlers=consoleHandler,fileHandler

[logger_infoLogger]
handlers=consoleHandler,fileHandler
qualname=infoLogger
propagate=0

[handlers]
keys=consoleHandler,fileHandler

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=form02
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=INFO
formatter=form01
args=('./logs/runlog.log', 'a')

[formatters]
keys=form01,form02

[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

[formatter_form02]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

扩展资料: Appium教程5-6~5-10

run.py引入配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import unittest
from BSTestRunner import BSTestRunner
import time,yaml
from mysql_action import DB

import logging.config

#引入日志配置文件
CON_LOG='log.conf'
logging.config.fileConfig(CON_LOG)
logging=logging.getLogger()

#初始化数据
db=DB()
f=open('datas.yaml','r')
datas=yaml.load(f)
db.init_data(datas)

test_dir='.'
report_dir='./reports'

discover=unittest.defaultTestLoader.discover(test_dir,pattern='test_django_restful.py')

now=time.strftime('%Y-%m-%d %H_%M_%S')
report_name=report_dir+'/'+now+' test_report.html'

with open (report_name,'wb') as f:
runner=BSTestRunner(stream=f,title='API Test Report',description='Django Restful API Test Report')
logging.info('============API Test================')
runner.run(discover)

mysql_action.py添加日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from pymysql import connect
import yaml
import logging

class DB():
def __init__(self):
logging.info('=============init data=================')
logging.info('connect db...')
self.conn=connect(host='127.0.0.1',user='root',password='',db='django_restful')

def clear(self,table_name):
logging.info('clear db...')
clear_sql='truncate '+table_name+';'
with self.conn.cursor() as cursor:
cursor.execute('set foreign_key_checks=0;')
cursor.execute(clear_sql)
self.conn.commit()

def insert(self,table_name,table_data):
for key in table_data:
table_data[key]="'"+str(table_data[key])+"'"

key=','.join(table_data.keys())
value=','.join(table_data.values())

logging.info(key)
logging.info(value)

insert_sql='insert into '+table_name+'('+key+')'+'values'+'('+value+')'
logging.info(insert_sql)

with self.conn.cursor() as cursor:
cursor.execute(insert_sql)
self.conn.commit()

def close(self):
logging.info('close db')
self.conn.close()
logging.info('==============init data finished!=============')

def init_data(self,datas):
for table,data in datas.items():
self.clear(table)
for d in data:
self.insert(table,d)
self.close()



if __name__ == '__main__':
db=DB()
# db.clear('api_user')
# db.clear('api_group')
# user_data={'id':1,'username':'zxw2018','email':'zxw2018@163.com'}
# db.insert('api_user',user_data)
# db.close()

f=open('datas.yaml','r')
datas=yaml.load(f)
db.init_data(datas)

最后在test_django_restful.py添加日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import requests
import unittest
from mysql_action import DB
import yaml
import logging

class UserTest(unittest.TestCase):
def setUp(self):
self.base_url='http://127.0.0.1:8000/users'
self.auth=('51zxw','zxw20182018')


def test_001_get_user(self):
logging.info('test_001_get_user')
r=requests.get(self.base_url+'/1/',auth=self.auth)
result=r.json()

self.assertEqual(result['username'],'sutune')
self.assertEqual(result['email'],'sutune@163.com')

def test_002_add_user(self):
logging.info('test_002_add_user')
form_data={'username':'zxw666','email':'zxw666@163.com','groups':'http://127.0.0.1:8000/groups/2/'}
r=requests.post(self.base_url+'/',data=form_data,auth=self.auth)
result=r.json()

self.assertEqual(result['username'],'zxw666')
self.assertEqual(result['email'],'zxw666@163.com')


def test_003_update_user(self):
logging.info('test_003_update_user')
form_data={'email':'zxw2018@163.com'}
r=requests.patch(self.base_url+'/1/',data=form_data,auth=self.auth)
result=r.json()

self.assertEqual(result['email'],'zxw2018@163.com')

def test_004_delete_user(self):
logging.info('test_004_delete_user')
r=requests.delete(self.base_url+'/2/',auth=self.auth)

self.assertEqual(r.status_code,204)

def test_005_no_auth(self):
logging.info('test_005_no_auth')
r=requests.get(self.base_url)
result=r.json()

self.assertEqual(result['detail'],'Authentication credentials were not provided.')


class GroupTest(unittest.TestCase):
def setUp(self):
self.base_url='http://127.0.0.1:8000/groups'
self.auth=('51zxw','zxw20182018')


def test_001_group_developer(self):
logging.info('test_001_group_developer')
r=requests.get(self.base_url+'/1/',auth=self.auth)
result=r.json()

self.assertEqual(result['name'],'Developer')

def test_002_add_group(self):
logging.info('test_002_add_group')
form_data={'name':'Pm'}
r=requests.post(self.base_url+'/',data=form_data,auth=self.auth)
result=r.json()

self.assertEqual(result['name'],'Pm')

def test_003_update_group(self):
logging.info('test_003_update_group')
form_data={'name':'Boss'}
r=requests.patch(self.base_url+'/2/',data=form_data,auth=self.auth)
result=r.json()

self.assertEqual(result['name'],'Boss')

def test_004_delete_group(self):
logging.info('test_004_delete_group')
r=requests.delete(self.base_url+'/1/',auth=self.auth)
self.assertEqual(r.status_code,204)

if __name__ == '__main__':
db=DB()
f=open('datas.yaml','r')
datas=yaml.load(f)
db.init_data(datas)
unittest.main()

运行完成之后可以在logs目录里面看到如下log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2018-08-20 11:25:57,456 mysql_action.py[line:7] INFO =============init data=================
2018-08-20 11:25:57,456 mysql_action.py[line:8] INFO connect db...
2018-08-20 11:25:57,465 mysql_action.py[line:12] INFO clear db...
2018-08-20 11:25:57,469 mysql_action.py[line:26] INFO email,username,id,groups
2018-08-20 11:25:57,469 mysql_action.py[line:27] INFO 'sutune@163.com','sutune','1','http://127.0.0.1:8000/groups/1/'
2018-08-20 11:25:57,470 mysql_action.py[line:30] INFO insert into api_user(email,username,id,groups)values('sutune@163.com','sutune','1','http://127.0.0.1:8000/groups/1/')
2018-08-20 11:25:57,471 mysql_action.py[line:26] INFO email,username,id,groups
2018-08-20 11:25:57,471 mysql_action.py[line:27] INFO '51zxw@163.com','51zxw','2','http://127.0.0.1:8000/groups/2/'
2018-08-20 11:25:57,473 mysql_action.py[line:30] INFO insert into api_user(email,username,id,groups)values('51zxw@163.com','51zxw','2','http://127.0.0.1:8000/groups/2/')
2018-08-20 11:25:57,473 mysql_action.py[line:12] INFO clear db...
2018-08-20 11:25:57,477 mysql_action.py[line:26] INFO name,id
2018-08-20 11:25:57,477 mysql_action.py[line:27] INFO 'Developer','1'
2018-08-20 11:25:57,477 mysql_action.py[line:30] INFO insert into api_group(name,id)values('Developer','1')
2018-08-20 11:25:57,478 mysql_action.py[line:26] INFO name,id
2018-08-20 11:25:57,478 mysql_action.py[line:27] INFO 'Tester','2'
2018-08-20 11:25:57,479 mysql_action.py[line:30] INFO insert into api_group(name,id)values('Tester','2')
2018-08-20 11:25:57,479 mysql_action.py[line:37] INFO close db
2018-08-20 11:25:57,479 mysql_action.py[line:39] INFO ==============init data finished!=============
2018-08-20 11:25:57,658 run.py[line:28] INFO ============API Test================
2018-08-20 11:25:57,659 test_django_restful.py[line:60] INFO test_001_group_developer
2018-08-20 11:25:57,783 test_django_restful.py[line:67] INFO test_002_add_group
2018-08-20 11:25:57,903 test_django_restful.py[line:75] INFO test_003_update_group
2018-08-20 11:25:58,009 test_django_restful.py[line:83] INFO test_004_delete_group
2018-08-20 11:25:58,106 test_django_restful.py[line:14] INFO test_001_get_user
2018-08-20 11:25:58,211 test_django_restful.py[line:22] INFO test_002_add_user
2018-08-20 11:25:58,309 test_django_restful.py[line:32] INFO test_003_update_user
2018-08-20 11:25:58,405 test_django_restful.py[line:40] INFO test_004_delete_user
2018-08-20 11:25:58,496 test_django_restful.py[line:46] INFO test_005_no_auth

集成Jenkins

使用Jenkins持续集成平台我们可以自动定时执行自动化任务,自动发送邮件推送测试报告,这样会有效提高自动化测试执行效率。

打开Jenkins创建项目django_restful_api 然后在构建中选择 “执行Windows批处理”
填入如下内容:

1
2
3
d:
cd D:\django_restful\api\test_project
C:\Python35\python.exe run.py

image

执行完成后可以查看到控制台输出

image

最后在对应的报告文件夹可以看到生成对应的测试报告。

Jenkins定时构建语法

1
* * * * *

(五颗星,中间用空格隔开)

  • 第一个*表示分钟,取值0~59
  • 第二个*表示小时,取值0~23
  • 第三个*表示一个月的第几天,取值1~31
  • 第四个*表示第几月,取值1~12
  • 第五个*表示一周中的第几天,取值0~7,其中0和7代表的都是周日
使用案例

每天下午18点定时构建一次

1
0 18 * * 1-5

每天早上8点构建一次

1
0 8 * * *

每30分钟构建一次:

1
H/30 * * * *

Python邮件发送

参考资料:Selenium教程7-1~7-4

视频操作演示

《Python接口自动化测试教程》5-12~5-18

参考资料