Combined with unit testing framework pytest + data-driven model + allure
Table of contents
api: Storage test interface
conftest.py: Set prerequisite operations
Current pre-operations: 1. Get token and pass in headers, 2. Get command line parameters and give them to environment variables to specify the running environment.
commmon: storage encapsulated public method
connect_mysql.py: Connect to the database
http_requests.py: Encapsulate your own request method
logger.py: Encapsulate the output log file
read_yaml.py: Read the yaml file test case data
read_save_data.py: Read the saved data file
case: stores all test cases
data: stores data required for testing
save_data: stores interface return data and interface download files
test_data: stores test case dependency data
upload_data: stores upload interface files
logs: stores output log files
report: stores test output report
getpathinfo.py: encapsulates the project test path
pytest.int: Configuration file
requirement.txt: local python package (pip install -r requirements.txt installs all python packages in the project)
run_main.py: project running file
structural design
1. Each interface use case combination generates a py file in a test class
2. Encapsulate the interface called by each use case in a test class and generate a py file
3. Store the test data in the yml file and parameterize it through parametrize to achieve data drive
4. Generate test reports through allure
Code display
api /api_service.py #A type of interface that needs to be tested
'''
Code description:服务相关接口
Create time: 2020/12/3
Developer: 叶修
'''
import os
from common.http_requests import HttpRequests
class Api_Auth_Service(object):
def __init__(self):
self.headers = HttpRequests().headers
def api_home_service_list(self):
# 首页服务列表
# url = "http://192.168.2.199:9092/v1/auth/auth_service/findAuthService"
url = os.environ["host"] + "/v1/auth/auth_service/findAuthService" # 读取conftest.py文件地址进行拼接
response = HttpRequests().get(url, headers=self.headers, verify=False)
# print(response.json())
return response
def get_service_id(self):
#获取银行卡三要素认证服务id
url = "http://192.168.2.199:9092/v1/auth/auth_service/findAuthService"
#url = os.environ["host"] + "/v1/auth/auth_service/findAuthService" # 读取conftest.py文件地址进行拼接
response = HttpRequests().get(url,headers=self.headers)
#print(response.json()['data'][0]['service_list'][0]['id'])
service_id = response.json()['data'][0]['service_list'][1]['id']
return service_id
def api_service_info(self,serviceId='0b6cf45bec757afa7ee7209d30012ce1',developerId=''):
#服务详情
body = {
"serviceId" :serviceId,
"developerId":developerId
}
url = "http://192.168.2.199:9092/v1/auth/auth_service/findServiceDetail"
#url = os.environ["host"] + "/v1/auth/auth_service/findServiceDetail"#读取conftest.py文件地址进行拼接
response = HttpRequests().get(url,headers=self.headers,params = body,verify=False)
#print(response.json())
return response
def api_add_or_update_service(self,api_param_req,api_param_res,description,error_code,icon,id,interface_remarks,
name,product_info,request_method,sample_code,sort,type,url):
#服务添加或者更新
body={
"api_param_req": api_param_req,
"api_param_res": api_param_res,
"description": description,
"error_code": error_code,
"icon": icon,
"id": id,
"interface_remarks": interface_remarks,
"name": name,
"product_info": product_info,
"request_method": request_method,
"sample_code": sample_code,
"sort": sort,
"type": type,
"url": url,
}
#url = "http://192.168.2.199:9092/v1/auth/auth_service/insertOrUpdateService"
url = os.environ["host"] + "/v1/auth/auth_service/insertOrUpdateService" # 读取conftest.py文件地址进行拼接
response = HttpRequests().post(url,json=body,headers=self.headers,verify=False)
return response
def api_add_service_price(self,id,max_number,money,service_id,small_number):
#服务价格添加
body = {
"id": id,
"max_number": max_number,
"money": money,
"service_id": service_id,
"small_number": small_number
}
# url = "http://192.168.2.199:9092/v1/auth/auth_service/insertServicePrice"
url = os.environ["host"] + "/v1/auth/auth_service/insertServicePrice" # 读取conftest.py文件地址进行拼接
response = HttpRequests().post(url, json=body, headers=self.headers, verify=False)
return response
def api_apply_service(self,developer_id,service_id):
#申请服务
body ={
"developer_id": developer_id,
"service_id": service_id
}
# url = "http://192.168.2.199:9092/v1/auth/auth_service/applyService"
url = os.environ["host"] + "/v1/auth/auth_service/applyService" # 读取conftest.py文件地址进行拼接
response = HttpRequests().post(url, json=body, headers=self.headers, verify=False)
return response
if __name__ == '__main__':
#Auth_Service().api_home_service_list()
Api_Auth_Service().get_service_id()
#Auth_Service().api_service_info()
api/get_token.py#Get login token
'''
Code description:获取token
Create time:2020-12-03
Developer:叶修
'''
import os
import urllib3
from common.http_requests import HttpRequests
class Get_Token(object):
def get_token(self,account='****',password='****'):
#url = "http://192.168.2.199:9092/v1/auth/developer/accountLogin"
url = os.environ["host"]+"/v1/auth/developer/accountLogin"
body = {
"account": account,
"password": password,
}
urllib3.disable_warnings()
r = HttpRequests().post(url, json=body,verify=False)
#print(r.json())
token = r.json()['data']['token']
params = {
"access_token": token
}
HttpRequests().params.update(params)#更新token到session
return token
if __name__ == '__main__':
print(Get_Token().get_token())
case/test_service_info.py #A test case for the above interface
'''
Code description: 服务详情
Create time: 2020/12/3
Developer: 叶修
'''
import sys
import allure
import pytest
from common.logger import Log
from common.read_yaml import ReadYaml
from api.api_auth_service.api_auth_service import Api_Auth_Service
testdata = ReadYaml("auth_service.yml").get_yaml_data() # 读取数据
@allure.feature('服务详情')
class Test_Service_Info(object):
log = Log()
@pytest.mark.process
@pytest.mark.parametrize('serviceId,developerId,expect', testdata['service_info'],
ids=['服务详情'])
def test_service_info(self,serviceId,developerId,expect):
self.log.info('%s{%s}' % ((sys._getframe().f_code.co_name,'------服务详情接口-----')))
with allure.step('获取服务id'):
serviceId = Api_Auth_Service().get_service_id()
with allure.step('服务详情'):
msg = Api_Auth_Service().api_service_info(serviceId,developerId)
self.log.info('%s:%s' % ((sys._getframe().f_code.co_name, '获取请求结果:%s' % msg.json())))
# 断言
assert msg.json()["result_message"] == expect['result_message']
assert msg.json()['result_code'] == expect['result_code']
assert 'url' in msg.json()['data']
conftest.py
'''
Code description:配置信息
Create time: 2020/12/3
Developer: 叶修
'''
import os
import pytest
from api.get_token import Get_Token
from common.http_requests import HttpRequests
@pytest.fixture(scope="session")
def get_token():
'''前置操作获取token并传入headers'''
Get_Token().get_token()
if not HttpRequests().params.get("access_token", ""):#没有get到token,跳出用例
pytest.skip("未获取token跳过用例")
yield HttpRequests().req
HttpRequests().req.close()
def pytest_addoption(parser):
parser.addoption(
"--cmdhost", action="store", default="http://192.168.1.54:32099",
help="my option: type1 or type2"
)
@pytest.fixture(scope="session",autouse=True)
def host(request):
'''获取命令行参数'''
#获取命令行参数给到环境变量
#pytest --cmdhost 运行指定环境
os.environ["host"] = request.config.getoption("--cmdhost")
print("当前用例运行测试环境:%s" % os.environ["host"])
common/connect_mysql.py
'''
Code description: 配置连接数据库
Create time: 2020/12/3
Developer: 叶修
'''
import pymysql
dbinfo = {
"host":"******",
"user":"root",
"password":"******",
"port":31855
}
class DbConnect():
def __init__(self,db_conf,database=""):
self.db_conf = db_conf
#打开数据库
self.db = pymysql.connect(database = database,
cursorclass = pymysql.cursors.DictCursor,
**db_conf)
#使用cursor()方式获取操作游标
self.cursor = self.db.cursor()
def select(self,sql):
#sql查询
self.cursor.execute(sql)#执行sql
results = self.cursor.fetchall()
return results
def execute(self,sql):
#sql 删除 提示 修改
try:
self.cursor.execute(sql)#执行sql
self.db.commit()#提交修改
except:
#发生错误时回滚
self.db.rollback()
def close(self):
self.db.close()#关闭连接
def select_sql(select_sql):
'''查询数据库'''
db = DbConnect(dbinfo,database='auth_platform')
result = db.select(select_sql)
db.close()
return result
def execute_sql(sql):
'''执行SQL'''
db = DbConnect(dbinfo,database='auth_platform')
db.execute(sql)
db.close()
if __name__ == '__main__':
sql = "SELECT * FROM auth_platform.auth_service where name='四要素认证'"
sel = select_sql(sql)[0]['name']
print(sel)
common/http_requests.py
'''
Code description: 封装自己的请求类型
Create time: 2020/12/3
Developer: 叶修
'''
import requests
# 定义一个HttpRequests的类
class HttpRequests(object):
req = requests.session()#定义session会话
# 定义公共请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
'cookie':''
}
params = {
'access_token':''
}
# 封装自己的get请求,获取资源
def get(self, url='', params='', data='', headers=None, cookies=None,stream=None,verify=None):
response = self.req.get(url,params=params,data=data,headers=headers,cookies=cookies,stream=stream,verify=verify)
return response
# 封装自己的post方法,创建资源
def post(self, url='', params='',data='', json='', headers=None, cookies=None,stream=None,verify=None):
response = self.req.post(url,params=params,data=data,json=json,headers=headers,cookies=cookies,stream=stream,verify=verify)
return response
# 封装自己的put方法,更新资源
def put(self, url='', params='', data='', headers=None, cookies=None,verify=None):
response = self.req.put(url, params=params, data=data, headers=headers, cookies=cookies,verify=verify)
return response
# 封装自己的delete方法,删除资源
def delete(self, url='', params='', data='', headers=None, cookies=None,verify=None):
response = self.req.delete(url, params=params, data=data, headers=headers, cookies=cookies,verify=verify)
return response
common/logger.py
'''
Code description: 封装输出日志文件
Create time: 2020/12/3
Developer: 叶修
'''
import logging,time
import os
import getpathinfo
path = getpathinfo.get_path()#获取本地路径
log_path = os.path.join(path,'logs')# log_path是存放日志的路径
# 如果不存在这个logs文件夹,就自动创建一个
if not os.path.exists(log_path):os.mkdir(log_path)
class Log():
def __init__(self):
#文件的命名
self.logname = os.path.join(log_path,'%s.log'%time.strftime('%Y_%m_%d'))
self.logger = logging.getLogger()
self.logger.setLevel(logging.DEBUG)
#日志输出格式
self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
def __console(self,level,message):
#创建一个fileHander,用于写入本地
fh = logging.FileHandler(self.logname,'a',encoding='utf-8')
fh.setLevel(logging.DEBUG)
fh.setFormatter(self.formatter)
self.logger.addHandler(fh)
#创建一个StreamHandler,用于输入到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(self.formatter)
self.logger.addHandler(ch)
if level == 'info':
self.logger.info(message)
elif level == 'debug':
self.logger.debug(message)
elif level == 'warning':
self.logger.warning(message)
elif level == 'error':
self.logger.error(message)
#避免日志重复
self.logger.removeHandler(fh)
self.logger.removeHandler(ch)
#关闭打开文件
fh.close()
def debug(self,message):
self.__console('debug',message)
def info(self,message):
self.__console('info',message)
def warning(self,message):
self.__console('warning',message)
def error(self,message):
self.__console('error',message)
if __name__ == '__main__':
log = Log()
log.info('测试')
log.debug('测试')
log.warning('测试')
log.error('测试')
common/read_save_data.py
'''
Code description: 读取保存数据
Create time: 2020/12/8
Developer: 叶修
'''
import os
import yaml
import getpathinfo
class Read_Save_Date():
def __init__(self):
path = getpathinfo.get_path()#获取本地路径
self.head_img_path = os.path.join(path,'data','save_data')+"/"+'head_img_path.txt'# head_img_path文件地址
self.order_id_path = os.path.join(path, 'data', 'save_data') + "/" + 'order_id.txt' # order_id.txt文件地址
def get_head_img_path(self):
# 获取head_img_path
with open(self.head_img_path, "r", encoding="utf-8")as f:
return f.read()
def get_order_id(self):
# 获取order_id
with open(self.order_id_path, "r", encoding="utf-8")as f:
return f.read()
if __name__ == '__main__':
print(Read_Save_Date().get_head_img_path())
print(Read_Save_Date().get_order_id())
common/read_yaml.py
'''
Code description: 读取yml文件测试数据
Create time: 2020/12/3
Developer: 叶修
'''
import os
import yaml
import getpathinfo
class ReadYaml():
def __init__(self,filename):
path = getpathinfo.get_path()#获取本地路径
self.filepath = os.path.join(path,'data','test_data')+"/"+filename#拼接定位到data文件夹
def get_yaml_data(self):
with open(self.filepath, "r", encoding="utf-8")as f:
# 调用load方法加载文件流
return yaml.load(f,Loader=yaml.FullLoader)
if __name__ == '__main__':
data = ReadYaml("auth_service.yml").get_yaml_data()['add_or_update_service']
print(data)
data/
data/test_data/auth_service.yml
home_service_list:
- [{'result_code': '0', 'result_message': '处理成功'}]
service_info:
- ['','',{'result_code': '0', 'result_message': '处理成功'}]
add_or_update_service:
- [['1','1','1','1','1','123456','1','测试','1','1','1','1','1','1'],{'result_code': '0', 'result_message': '处理成功'}]
add_service_price:
- ['123456789','10','0','','0',{'result_code': '0', 'result_message': '处理成功'}]
apply_service:
- ['','',{'result_code': '0', 'result_message': '处理成功'}]
logs/
report/
getpathinfo.py
'''
Code description:配置文件路径
Create time: 2020/12/3
Developer: 叶修
'''
import os
def get_path():
# 获取当前路径
curpath = os.path.dirname(os.path.realpath(__file__))
return curpath
if __name__ == '__main__':# 执行该文件,测试下是否OK
print('测试路径是否OK,路径为:', get_path())
pytest.ini
#pytest.ini
[pytest]
markers = process
addopts = -p no:warnings
#addopts = -v --reruns 1 --html=./report/report.html --self-contained-html
#addopts = -v --reruns 1 --alluredir ./report/allure_raw
#addopts = -v -s -p no:warnings --reruns 1 --pytest_report ./report/Pytest_Report.html
requirements.txt
allure-pytest==2.8.18
allure-python-commons==2.8.18
BeautifulReport==0.1.3
beautifulsoup4==4.9.3
ddt==1.4.1
Faker==4.18.0
Flask==1.1.1
httpie==1.0.3
httplib2==0.9.2
HttpRunner==1.5.8
py==1.9.0
PyMySQL==0.10.1
pytest==6.1.1
pytest-base-url==1.4.2
pytest-cov==2.10.1
pytest-forked==1.3.0
pytest-html==2.1.1
pytest-instafail==0.4.2
pytest-metadata==1.10.0
pytest-mock==3.3.1
pywin32==228
PyYAML==5.3.1
requests==2.22.0
requests-oauthlib==1.3.0
requests-toolbelt==0.9.1
run_main.py
'''
Code description: 运行主流程测试用例
Create time: 2020/11/5
Developer: 叶修
'''
import os
import pytest
if __name__ == '__main__':
pytest.main(['-m','process', '-s','--alluredir', 'report/tmp'])#-m运行mark标记文件
os.system('allure generate report/tmp -o report/html --clean') # /report/tmp 为存放报告的源文件目录
Finally, I would like to thank everyone who reads my article carefully. Reciprocity is always necessary. Although it is not a very valuable thing, if you can use it, you can take it directly:
Software testing interview applet
A software test question bank that has been used by millions of people! ! ! Who is who knows! ! ! The most comprehensive interview test mini program on the Internet, you can use your mobile phone to answer questions, take the subway, bus, and roll it up!
Covers the following interview question sections:
1. Basic theory of software testing, 2. web, app, interface function testing, 3. network, 4. database, 5. linux
6. Web, app, interface automation, 7. Performance testing, 8. Programming basics, 9. HR interview questions, 10. Open test questions, 11. Security testing, 12. Computer basics
This information should be the most comprehensive and complete preparation warehouse for [software testing] friends. This warehouse has also accompanied tens of thousands of test engineers through the most difficult journey. I hope it can also help you!