python接口自动化框架

接口测框架

python接口自动化框架_第1张图片

 

 

安装教程

  1. 需要3.5及以上版本的python
  2. pip install -r requirements.txt

使用说明

    1. 运行manage.py创建项目
    2. 创建的项目在projects目录下
    3. 在项目的cases目录下编写测试用例,可以参考litemall项目中如何编写测试用例
    4. 执行项目目录下的run.py运行所有测试用例

 python接口自动化框架_第2张图片

 

 

 

一、config配置文件

 三个文件:

const_template.py

run_template.py

setting.py

文件代码:

const_template.py

1 import os
2 
3 host = 'http://ip:port'  # 测试环境地址
4 
5 project_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
6 
7 data_path = os.path.join(project_path, 'data')  # 存测试数据的目录
8 report_path = os.path.join(project_path, 'report')  # 存报告的目录
9 case_path = os.path.join(project_path, 'cases')  # 存测试用例的目录

 

run_template.py

 1 from utils.send_message import send_mail
 2 from config.setting import email_template
 3 from projects.Iitemall.public.const import case_path, report_path
 4 import nnreport as bf
 5 import datetime
 6 import unittest
 7 import os
 8 import sys
 9 
10 root_dir = os.path.dirname(
11     os.path.dirname(
12         os.path.dirname(
13             os.path.abspath(__file__))))
14 # 项目根目录,加入环境变量,否则直接在命令行里面运行的时候有问题, 找不到其他的模块
15 sys.path.insert(0, root_dir)
16 
17 
18 def run():
19     test_suite = unittest.defaultTestLoader.discover(case_path, 'test*.py')
20     # 这里是指定找什么开头的.py文件,运行用例的时候可以自己改
21     report = bf.BeautifulReport(test_suite)
22     title = '{project_name}_测试报告'
23     filename = title + '_' + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + '.html'
24     report.report(description=title,
25                   filename=filename,
26                   log_path=report_path)
27     email_content = email_template.format(pass_count=report.success_count,
28                                           fail_count=report.failure_count,
29                                           all_count=report.success_count + report.failure_count)
30 
31     report_abs_path = os.path.join(report_path, filename)
32     send_mail(filename, email_content, report_abs_path)
33 
34 
35 if __name__ == '__main__':
36     run()

 

setting.py

 1 import os
 2 import nnlog
 3 
 4 mysql_info = {
 5     'default':
 6         {
 7             'host': 'ip',
 8             'port': 3306,
 9             'user': 'dbuser',
10             'password': 'dbpassword',
11             'db': 'db',
12             'charset': 'utf8',
13         }
14 }  # 数据库配置,多个数据库,在字典里加key就可以了
15 
16 redis_info = {
17     'default': {
18         'host': 'ip',
19         'port': 6379,
20         'db': 0,
21         'decode_responses': True
22     }
23 }  # redis配置,多个数据库,在字典里加key就可以了
24 
25 email_info = {
26     'host': 'smtp.163.com',  #
27     'user': '[email protected]',  # 用户
28     'password': '5tgb6yhn',  # 密码
29     'port': 465,
30 }
31 
32 email_to = ['[email protected]']
33 email_cc = ['[email protected]']
34 
35 base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
36 log_path = os.path.join(base_path, 'logs', 'utp.log')  # 指定日志文件
37 projects_path = os.path.join(base_path, 'projects')  # 项目目录
38 log = nnlog.Logger(log_path)
39 
40 email_template = '''
41 各位好:
42     本次接口测试结果如下:总共运行{all_count}条用例,通过{pass_count}条,失败【{fail_count}】条。
43     详细信息请查看附件。
44 '''  # 邮件模板

 

 

一、utils公共方法

 该文件夹为python文件夹,需待__init__.py文件

 七个文件:

clean.py

data_util.py

 db_util.py

project.py

request.py

send_message.py

utils.py

 

文件代码:

clean.py

 1 import os
 2 import time
 3 from config.setting import projects_path
 4 
 5 
 6 def clean_report(days=10):
 7     '''清理测试报告'''
 8     for cur_dir, dirs, files in os.walk(projects_path):  # 递归获取项目目录下所有文件夹
 9         if cur_dir.endswith('report'):  # 判断如果文件夹是report的话,获取文件夹下面的文件
10             for report in files:
11                 if report.endswith('.html'):  # 如果是.html结尾的
12                     report_path = os.path.join(cur_dir, report)
13                     if os.path.getctime(
14                             report_path) < time.time() - 60 * 60 * 24 * days:
15                         os.remove(report_path)

 

data_util.py

 1 import os
 2 import xlrd
 3 
 4 from config.setting import log
 5 from .db_util import get_mysql_connect
 6 
 7 
 8 class GetTestData:
 9     @staticmethod
10     def data_for_txt(file_name):
11         '''
12         从文本文件里面获取参数化数据
13         :param file_name: 文件名
14         :return:二维数组
15         '''
16         log.debug('开始读取参数化文件%s' % file_name)
17         if os.path.exists(file_name):
18             with open(file_name, encoding='utf-8') as fr:
19                 data = []
20                 for line in fr:
21                     if line.strip():
22                         line_data = line.strip().split(',')
23                         data.append(line_data)
24             return data
25         log.error('%s参数化文件不存在' % file_name)
26         raise Exception('%s参数化文件不存在' % file_name)
27 
28     @staticmethod
29     def data_for_excel(file_name, sheet_name=None):
30         '''
31         从excel里面读参数化数据
32         :param file_name: 文件名
33         :param sheet_name: sheet页名字,默认不写取第一个sheet页
34         :return: 二维数组
35         '''
36         log.debug('开始读取参数化文件%s' % file_name)
37         if os.path.exists(file_name):
38             data = []
39             book = xlrd.open_workbook(file_name)
40             if sheet_name:
41                 sheet = book.sheet_by_name(sheet_name)
42             else:
43                 sheet = book.sheet_by_index(0)
44             for row_num in range(1, sheet.nrows):
45                 row_data = sheet.row_values(row_num)
46                 data.append(row_data)
47             return data
48         log.error('%s参数化文件不存在' % file_name)
49         raise Exception('%s参数化文件不存在' % file_name)
50 
51     @staticmethod
52     def data_for_mysql(sql, db_config='default'):
53         '''
54         从数据库里面获取测试数据
55         :param sql:sql语句
56         :param db_config:从配置文件里面配置的mysql信息
57         :return:从数据库里面查出来的二维数组
58         '''
59         mysql = get_mysql_connect(db_config)
60         return mysql.get_list_data(sql)

 

db_util.py

 1 import pymysql
 2 import redis
 3 from config.setting import mysql_info, redis_info
 4 
 5 
 6 class Mysql:
 7     def __init__(self, host, user, password, db, port=3306, charset='utf8'):
 8         # 构造函数,类在实例化的时候会自动执行构造函数
 9         self.db_info = {'user': user, 'password': password, "db": db, "port": port, 'charset': charset,
10                         'autocommit': True, 'host': host}
11         self.__connect()
12 
13     def __del__(self):
14         self.__close()
15 
16     def __connect(self):
17         try:
18             self.conn = pymysql.connect(**self.db_info)  # 建立连接
19         except Exception as e:
20             raise Exception("连接不上数据库,请检查数据库连接信息")
21 
22         else:
23             self.__set_cur()  # 设置游标
24 
25     def execute_many(self, sql):
26         self.cur.execute(sql)
27         return self.cur.fetchall()
28 
29     def execute_one(self, sql):
30         self.cur.execute(sql)
31         return self.cur.fetchone()
32 
33     def __set_cur(self, type=pymysql.cursors.DictCursor):  # 设置游标,默认是字典类型
34         self.cur = self.conn.cursor(cursor=type)
35 
36     def get_list_data(self, sql):
37         '''从数据库获取到的数据是list'''
38         self.__set_cur(type=None)  # 设置游标为空,返回的就不是字典了
39         self.cur.execute(sql)
40         self.__set_cur()  # 查完之后重新设置游标为字典类型
41         return self.cur.fetchall()
42 
43     def __close(self):
44         self.conn.close()
45         self.cur.close()
46 
47 
48 def get_redis_connect(name='default'):
49     '''获取redis连接,如果不传name,获取默认的链接'''
50     redis_config = redis_info.get(name)
51     return redis.Redis(**redis_config)
52 
53 
54 def get_mysql_connect(name='default'):
55     '''获取mysql连接,如果不传name,获取默认的链接'''
56     mysql_config = mysql_info.get(name)
57     return Mysql(**mysql_config)

 

project.py

 1 import os
 2 
 3 
 4 class Project:
 5     base_path = os.path.dirname(
 6         os.path.dirname(
 7             os.path.abspath(__file__)))  # 工程目录
 8     projects_path = os.path.join(base_path, 'projects')  # 项目目录
 9     child_dirs = ['cases', 'data', 'report', 'public']
10 
11     def __init__(self, project_name):
12         self.project_name = project_name
13         self.project_path = os.path.join(
14             self.projects_path, project_name)  # 要创建的项目目录
15 
16     def create_project(self):
17         '''校验项目是否存在,不存在的话,创建'''
18         if os.path.exists(self.project_path):
19             raise Exception("项目已经存在!")
20         else:
21             os.mkdir(self.project_path)
22 
23     def create_init_py(self, path):
24         '''
25         创建__init__.py文件
26         :param path: 路径
27         :return:
28         '''
29         py_file_path = os.path.join(path, '__init__.py')
30         self.write_content(py_file_path, '')  # 打开一个空文件
31 
32     def create_dir(self, ):
33         '''创建项目下面的子目录'''
34         for dir in self.child_dirs:
35             dir_path = os.path.join(self.project_path, dir)
36             os.mkdir(dir_path)
37             if dir == 'cases':  # 如果是cases文件夹的话,创建__init__.py
38                 # cases是个package查找用例的时候才会找到那个目录下所有子目录里面的测试用例
39                 self.create_init_py(dir_path)
40 
41     def create_run_py(self):
42         '''生成run.py'''
43         run_template_path = os.path.join(
44             self.base_path, 'config', 'run_template')
45         content = self.get_template_content(
46             run_template_path).format(project_name=self.project_name)
47         run_file_path = os.path.join(self.project_path, 'run.py')
48         self.write_content(run_file_path, content)
49 
50     def create_const_py(self):
51         '''生成const.py'''
52         run_template_path = os.path.join(
53             self.base_path, 'config', 'const_template')
54         content = self.get_template_content(run_template_path)
55         run_file_path = os.path.join(self.project_path, 'public', 'const.py')
56         self.write_content(run_file_path, content)
57 
58     def main(self):
59         '''创建项目'''
60         self.create_project()  # 创建项目
61         self.create_dir()  # 创建项目下面的文件夹
62         self.create_run_py()  # 创建run.py
63         self.create_const_py()  # 创建const.py
64 
65     @staticmethod
66     def get_template_content(file_name):
67         '''读取文件内容'''
68         with open(file_name, encoding='utf-8') as fr:
69             return fr.read()
70 
71     @staticmethod
72     def write_content(file, content):
73         '''写入文件'''
74         with open(file, 'w', encoding='utf-8') as fw:
75             fw.write(content)

 

request.py

 1 import requests
 2 # 反射
 3 
 4 
 5 class MyRequest:
 6     def __init__(self, url, method='get', data=None,
 7                  headers=None, is_json=True):
 8         method = method.lower()
 9         self.url = url
10         self.data = data
11         self.headers = headers
12         self.is_json = is_json
13         if hasattr(self, method):
14             getattr(self, method)()
15 
16     def get(self):
17         try:
18             req = requests.get(
19                 self.url,
20                 self.data,
21                 headers=self.headers).json()
22         except Exception as e:
23             self.response = {"error": "接口请求出错%s" % e}
24         else:
25             self.response = req
26 
27     def post(self):
28         try:
29             if self.is_json:
30                 req = requests.post(
31                     self.url,
32                     json=self.data,
33                     headers=self.headers).json()
34             else:
35                 req = requests.post(
36                     self.url, self.data, headers=self.headers).json()
37         except Exception as e:
38             self.response = {"error": "接口请求出错%s" % e}
39         else:
40             self.response = req

 

send_message.py

 1 import yamail
 2 import traceback
 3 from config.setting import email_info, email_cc, email_to, log
 4 
 5 
 6 def send_mail(subject, content, files=None):
 7     '''
 8     发送邮件
 9     :param subject:主题
10     :param content: 内容
11     :param files: 附件
12     :return:
13     '''
14     try:
15         smtp = yamail.SMTP(**email_info)
16         smtp.send(subject=subject, contents=content,
17                   to=email_to, cc=email_cc, attachments=files)
18     except Exception as e:
19         log.error("发送邮件失败+%s" % traceback.format_exc())
20 
21 
22 def send_sms():
23     '''
24     发送短信验证码
25     :return:
26     '''
27     pass

 

utils.py

 1 import jsonpath
 2 
 3 
 4 def get_value(dic, key):
 5     '''
 6     这个函数是从一个字典里面,根据key获取vlaue
 7     :param dic:传一个字典
 8     :param key:传一个
 9     :return:如果有,返回key取到value,如果key没有,返回空字符串
10     '''
11     result = jsonpath.jsonpath(dic, '$..%s' % key)
12     if result:
13         return result[0]
14     return ''

 

 

三、 Projects项目模块(litemall项目)

 四个文件夹和一个python文件:

cases(python文件夹)

    test_address.py

    test_coupon.py

data (存放测试数据)

    address.xlsx

    goods.txt

public

    const.py (存放测试常量)

    tools.py

report

run.py

 

文件代码:

test_address.py

 1 import unittest
 2 import parameterized
 3 import os
 4 from urllib.parse import urljoin
 5 from projects.litemall.public.const import host, test_user, data_path
 6 from projects.litemall.public import tools
 7 from utils.request import MyRequest
 8 from utils.db_util import get_mysql_connect
 9 from utils.data_util import GetTestData
10 
11 address_data_path = os.path.join(data_path, 'address.xlsx')  # 拼接测试数据文件的路径
12 test_address_data = GetTestData.data_for_excel(address_data_path)  # 获取参数化使用的数据
13 
14 
15 class TestAddress(unittest.TestCase):
16     url = urljoin(host, '/wx/address/save')
17 
18     @classmethod
19     def setUpClass(cls):
20         # cls.mysql = get_mysql_connect()  # 获取mysql连接
21         token = tools.WxLogin(**test_user).get_token()  # 登录获取token
22         cls.header = {'X-Litemall-Token': token}  # 拼header
23 
24     @parameterized.parameterized.expand(test_address_data)  # 参数化
25     def test_create(self, name, tel, isDefault):
26         '''测试添加收货地址'''
27         is_default = True if isDefault == '1' else False
28         data = {
29             "name": name,
30             "tel": "%d" % tel,
31             "country": "",
32             "province": "北京市",
33             "city": "市辖区",
34             "county": "东城区",
35             "areaCode": "110101",
36             "postalCode": "",
37             "addressDetail": "西二旗",
38             "isDefault": is_default
39         }
40         req = MyRequest(
41             self.url,
42             'post',
43             data=data,
44             headers=self.header)  # 发请求
45         self.assertEqual(0, req.response.get('errno'), msg='添加失败')  # 校验错误码是否为0
46         address_id = req.response.get('data')
47         # sql = 'select name from litemall_address where id = %s;' % address_id
48         # db_data = self.mysql.execute_one(sql)
49         # self.assertIsNotNone(db_data, msg='litemall:查询地址不存在')#校验是否从数据库查到数据
50         # self.assertEqual(db_data.get('name'), name) #判断数据库存的名字和添加的名字是否一样

 

test_coupon.py

 1 import unittest
 2 from urllib.parse import urljoin
 3 from projects.Iitemall.public.const import host, test_admin_user
 4 from projects.Iitemall.public import tools
 5 from utils.request import MyRequest
 6 from utils.utils import get_value
 7 
 8 
 9 class TestCoupon(unittest.TestCase):
10 
11     @classmethod
12     def setUpClass(cls):
13         token = tools.AdminLogin(**test_admin_user).get_token()  # 登录获取token
14         cls.header = {'X-Litemall-Admin-Token': token}  # 拼header
15 
16     def add_coupon(self):
17         url = urljoin(host, '/admin/coupon/create')
18         name = 'Python自动化测试优惠券'
19         data = {
20             "name": name,
21             "desc": "介绍",
22             "total": "29",
23             "discount": "1",
24             "min": "999",
25             "limit": 1,
26             "type": 0,
27             "status": 0,
28             "goodsType": 0,
29             "goodsValue": [],
30             "timeType": 0,
31             "days": "1",
32             "startTime": None,
33             "endTime": None
34         }
35         req = MyRequest(url, 'post', data=data, headers=self.header)
36         print(req.response)
37         self.assertEqual(0, req.response.get('errno'), msg='添加失败')
38         coupon_id = get_value(req.response, 'id')
39         return name, coupon_id
40 
41     def test_coupon(self):
42         '''测试添加优惠券后,在首页是否查到'''
43         url = urljoin(host, '/wx/coupon/list')
44         name, id = self.add_coupon()  # 添加优惠券
45         req = MyRequest(url)
46         coupon_list = get_value(req.response, 'list')
47         tag = False
48         for coupon in coupon_list:
49             if name == coupon.get('name') and coupon.get('id') == id:
50                 tag = True
51                 break
52         self.assertTrue(tag, msg='添加的优惠券查不到')

 

 const.py

 1 import os
 2 host = 'http://proxy.nnzhp.cn'
 3 
 4 test_user = {
 5     'username': 'user123',
 6     'password': 'user123'
 7 }  # 测试用户
 8 
 9 test_admin_user = {
10     'username': 'admin123',
11     'password': 'admin123'
12 }  # 测试用户
13 
14 project_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
15 
16 data_path = os.path.join(project_path, 'data')  # 存测试数据的目录
17 report_path = os.path.join(project_path, 'report')  # 存报告的目录
18 case_path = os.path.join(project_path, 'cases')  # 存测试用例的目录

 

tools.py

 1 from utils.utils import get_value
 2 from utils.request import MyRequest
 3 from config.setting import log
 4 from urllib import parse
 5 from .const import host
 6 
 7 
 8 class AdminLogin:
 9     '''admin登录'''
10     url = parse.urljoin(host, '/admin/auth/login')  # 拼接url
11 
12     def __init__(self, username, password):
13         self.username = username
14         self.password = password
15 
16     def get_token(self):
17         data = {'username': self.username, 'password': self.password}
18         req = MyRequest(self.url, 'post', data=data, is_json=True)
19         token = get_value(req.response, 'token')
20         log.debug("登录的返回结果,%s" % req.response)
21         if token:
22             return token
23         log.error('litemall:登录失败' % req.response)
24         raise Exception('登录失败,错误信息%s' % req.response)
25 
26 
27 class WxLogin(AdminLogin):
28     '''Wx登录'''
29     url = parse.urljoin(host, '/wx/auth/login')

 

run.py

 1 import unittest
 2 import datetime
 3 import os
 4 import nnreport
 5 from projects.Iitemall.public.const import case_path, report_path
 6 from config.setting import email_template
 7 from utils.send_message import send_mail
 8 
 9 
10 def run():
11     test_suite = unittest.defaultTestLoader.discover(case_path, 'test*.py')
12     # 这里是指定找什么开头的.py文件,运行用例的时候可以自己改
13     report = nnreport.BeautifulReport(test_suite)
14     title = 'litemall_测试报告'
15     filename = title + '_' + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + '.html'
16     report.report(description=title,
17                   filename=filename,
18                   log_path=report_path)
19     email_content = email_template.format(pass_count=report.success_count,
20                                           fail_count=report.failure_count,
21                                           all_count=report.success_count + report.failure_count)
22 
23     report_abs_path = os.path.join(report_path, filename)
24     send_mail(filename, email_content, report_abs_path)
25 
26 
27 if __name__ == '__main__':
28     run()

 python接口自动化框架_第3张图片

 

 

 

四、必装第三方模块

requirement.txt

nnreport
pymysql
yamail
requests
jsonpath
nnlog
xlrd
redis
parameterized

 

 

你可能感兴趣的:(python接口自动化框架)