程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

First: python+selenium+pytest build an interface automation test framework

編輯:Python

One . Project directory

Two . Project structure

3、 ... and . Source code address

gitee :https://gitee.com/your_dad_died/bm_pytest_api
github: https://github.com/Theshy0000/bm_pytest_api

Four .conftest File global variable settings

1. Use conftest coordination @pytest.fixture() It can be used directly in the file where the use case is located without importing .

conftest.py

import pytest
from data.config_data import *
@pytest.fixture()
def login():
print(u" Login successful !")
@pytest.fixture()
def global_variable():
''' Global variable configuration '''
# call config_data Information configured in the file TEST_IP,DOWNSTREAM_TOKEN,UPSTREAM_TOKEN
variable = {

'test_ip': TEST_IP,
'downstream_token': DOWNSTREAM_TOKEN,
'upstream_token': UPSTREAM_TOKEN
}
return variable

5、 ... and . Parameter configuration ( One )

1. Other files are read through import

config_data.py

# To configure ip,token,headers
# Test environment 
TEST_IP = 'https://xxx.cn'
# Yashen token
YA_SHENG_TOKEN = 'eyxxcFoxODdrbk1DWG85QTdxY3pwakNoUGVvVEM1Ym1BPSJ9'
# Zhongchang token The downstream 
DOWNSTREAM_TOKEN='eyxPSJ9'
DOWNSTREAM_HEADERS={

'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
'token':DOWNSTREAM_TOKEN,
'Content-Type':'application/json'
}
# Three smiles token The upstream 
UPSTREAM_TOKEN='eyJlbmNyeXB0HFUN3d1Q5QfQ=='
UPSTREAM_HEADERS={

'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
'token':UPSTREAM_TOKEN,
'Content-Type':'application/json'
}
# Database configuration , Use... When using database operations 
MYSQL_CONFIG=("101.xx.xx.xx",3306, 'root', '123456', 'drf')

6、 ... and . Parameter configuration ( Two )

1.create_bill_data and delete_bill_data Is the original interface parameter , Store in data Under the document .

2.replace_expression Is the substitution of dependent expressions between interfaces , Written directly in variables , It can also be written in the configuration yaml Under the document .

3.rv Is the dictionary stored by the extracted parameters .

raw_parameter.py

''' File management can be divided here , It's not good to write it all together , I'll just write a few examples '''
upstream_create_waybill_data={

"waybillNo":"Y22011101026000006",
"orderNo":"",
"projectName":"XM- Yangzhou Sanxiao - Yangzhou Kangyuan ",
"projectNo":"320SF000206004",
"customer":" Yangzhou Sanxiao Logistics Co., Ltd ",
"customerNo":"320SF000206",
"customerOrderNo":" Customer order No ",
"thirdOrderNo":"12",
"shipperName":" Joba ",
"shipperPhone":"18000001111",
"shipperProvince":" Hainan ",
"shipperProvinceCode":"460000",
"shipperCity":" Ding'an County ",
"shipperCityCode":"469021",
"shipperDistrict":" Ding'an County ",
"shipperDistrictCode":"469021",
"shipperAddress":" Mingzhu Road, Xujing Town, Qingpu District 1008 Number ",
"receiverName":" Monkey D Luffy ",
"receiverPhone":"18000002222",
"receiverProvince":" Guangdong province, ",
"receiverProvinceCode":"440000",
"receiverCity":" Zhongshan city, ",
"receiverCityCode":"442000",
"receiverDistrict":" Zhongshan city, ",
"receiverDistrictCode":"442000",
"receiverAddress":" The county government ",
"requiredDeliveryDate":None,
"requiredArrivalDate":None,
"line":" Ding'an County - Zhongshan city, ",
"hasReceipt":True,
"requirementRemark":"",
"transportMode":"WHOLE_VEHICLE",
"carModel":"GUA_CHE",
"carLength":"5.0",
"freight":555,
"contractFreight":None,
"serviceType":None,
"chargeMode":"CAR_SAY_VALUATION",
"settlement":"D",
"cashPayment":"",
"arrivalPayment":555,
"receiptPayment":"",
"goodsList":[
{

"goodsName":"1",
"goodsWeight":1,
"goodsVolume":1,
"goodsNums":1,
"goodsWorth":1,
"goodsCosts":1,
"goodsUnit":"1"
}
],
"showFreight":None,
"sourceFrom":"PC"
}
create_bill_data={

"contractNo":"HT-91367323584",
"sheetDateEnum":"SEND_DATE",
"year":"2022",
"month":"01",
"projectName":"XM- Three smiles shipping - Zhongchang ",
"projectNo":"320SF000206003",
"downCompanyName":"",
"startDate":"2022-01-01 00:00:00",
"endDate":"2022-01-31 23:59:59",
"upCompanyName":" reviewers ",
"generateBillOperator":"012001011"
}
delete_bill_data={

'id':1111
}

6、 ... and . Interface parameters depend on Association encapsulation

1. Parameter extraction : call parameter.update_rv() Method to set parameter extraction

2.set() The set de duplication feature is used in the marked place first , Finally, use the list to return l

parameter_setting.py

# Solve parameter dependency substitution between interfaces , Find out the same key value first , Then assign the value of the returned result of the interface to the value of the replacement expression , Finally, assign the value of the replacement expression to the request parameter 
class ParameterSetting(object):
''' Parameter related tool method collection Dictionary '''
returned_value = {
}
def parameters_depend(self, request_parameter, replace_expression, rv):
''' :param request_parameter: Original request parameters :param replace_expression: Replace parameter expression Dictionaries :param rv: Interface return interface save Dictionary :return: '''
key_list = self.__identical_key(request_parameter, replace_expression)
for i in key_list:
replace_expression[i] = rv[i]
request_parameter[i] = replace_expression[i]
return request_parameter
def __identical_key(self, v1, v2):
''' :param v1: Dictionaries :param v2: Dictionaries :return: find 2 Keys with the same dictionary return key values in list format '''
s = set()
for k in v1.keys():
for k1 in v2.keys():
if k == k1:
s.add(k)
return list(s)
@property
def rv(self):
''' Solve the parameter dependency between interfaces , Read the returned results of other interfaces , Use the variable name as the dictionary key value to read '''
return self.returned_value
def update_rv(self, value):
''' Write the return result , Save using variable names and values :param value: Dictionary format data '''
for k, v in value.items():
self.returned_value[k] = v

7、 ... and .requests Secondary simple packaging

1. direct writing get and post Of 2 Methods , Ahead of time ip,headers Seal before the letter , Package upstream and downstream enterprises for the company's business .

requests_.py

# Secondary simple packaging 
import json
import requests
from data.config_data import TEST_IP,DOWNSTREAM_HEADERS,UPSTREAM_HEADERS
class Requests:
def __init__(self, up_or_down='up'):
self.ip = TEST_IP # call config_data Configuration information in the configuration file TEST_IP
self.up_or_down = up_or_down
if self.up_or_down == 'up':
self.headers = UPSTREAM_HEADERS # call config_data Configuration information in the configuration file UPSTREAM_HEADERS
elif self.up_or_down == 'down':
self.headers = DOWNSTREAM_HEADERS # call config_data Configuration information in the configuration file DOWNSTREAM_HEADERS
def get(self, url):
# url Use ip+url Splicing , And call headers Parameters 
result = requests.get(url=self.ip+url, headers=self.headers)
return result
def post(self, url, data):
# url Use ip+url Splicing , And call headers Parameters , Convert input parameters json Format 
result = requests.post(url=self.ip+url, headers=self.headers, data=json.dumps(data))
return result

8、 ... and . Instance of a

bill_test.py

# Billing related processes :1. New bill ,2. Delete Bill 
import random
import allure
from data.raw_parameter import *
from tool.requests_ import Requests
from tool.parameter_setting import ParameterSetting
# call ParameterSetting class , And instantiate the class 
parameter = ParameterSetting()
# Storage interface return parameters , Read when the interface depends ( Call instantiation parameter Class rv Method )
rv = parameter.rv
# call Requests class , And instantiate the class ( call Requests Class )
requests = Requests()
@allure.description(u" Billing operation ")
class TestBill:
@allure.description(u" Create a bill ")
def test_create_bill(self,global_variable): # Call global conftest.py In the document global_variable Method 
print(u' Use the following global variables {global_variable["test_ip"]}')
# call requests Class post Method 
# call raw_parameter Class create_bill_data Parameters , And request to return json data 
result = requests.post('/bill/createCommonBill',create_bill_data).json()
print(result)
# call ParameterSetting Class update_rv Method , Store the value returned by the interface 
parameter.update_rv({
'id':result['content']['id']})
@allure.description(u" Delete Bill ")
def test_delete_bill(self):
# Use replace_expression The parameterized upstream interface stores the returned value , here replace_expression And call ParameterSetting The expressions in the class are consistent 
# Use rv Indicates that the returned results of the interface are saved as dictionaries and calls ParameterSetting Methods in class rv Agreement 
replace_expression = {
'id':rv['id']}
# call ParameterSetting Class dparameters_depend Method 
# call raw_parameter Class delete_bill_data Parameters 
parameter.parameters_depend(delete_bill_data,replace_expression,rv)
# call requests Class post Method 
result = requests.post('/bill/deleteBillCommon',delete_bill_data)
print(result.json()) # And request to return json data 

1. Raw data ’id’:1111 Also replaced .

Nine . Example 2

1.result The complete response body content returned for the interface , Extract values from dictionary keys , Assign this value to waybillNo This variable , Subsequently passed waybillNo You can use this extracted value .

2.upstream_create_waybill_data Is the original interface parameter , Store in data Under the document .

3.replace_expression Is a dependency expression , Written directly in variables , It can also be written in the configuration file yaml Under the document .

4.rv The dictionary stored by the previously extracted parameters .

main_process_test.py

''' Main business process : 1. Get the waybill number 2. Recording 3. Obtain the dispatch order number 4. Create a dispatch list and select a downstream carrier ( Pay by receipt ) 5. Send a single 6. Start 7. arrive 9. Upload receipt 10. Receipt confirmation 11. Generate receipts for payment orders 12. Get payment order information 13. Online payment to card 14. Payment approval information query 15. The payment is approved 16. Add an upstream bill 17. Confirm the upstream bill 19. Switch to bambo system claiming flow 20. Write off upstream bills in a daily manner 21. Generate downstream bills 22. Confirm downstream bills 23. Generate final payment 24. Payment by tail '''
import random
import allure
import pytest
from data.raw_parameter import *
from tool.requests_ import Requests
from tool.parameter_setting import ParameterSetting
# call ParameterSetting class , And instantiate the class 
parameter = ParameterSetting()
# Storage interface return parameters , Read when the interface depends ( Call instantiation parameter Class rv Method )
rv = parameter.rv # Storage interface return parameters , Read when the interface depends 
# call Requests class , And instantiate the class ( call Requests Class )
requests = Requests()
@allure.description(u" Main process ")
@pytest.mark.waybill
class TestMainProcess:
@allure.description(u" Get the waybill number ")
def test_get_waybill_no(self):
result = requests.get('/waybill/generateWaybillNo').json()
# Parameter extraction 
# call ParameterSetting Class update_rv Method , Store the value returned by the interface 
# result The complete response body content returned for the interface , Extract values from dictionary keys , Assign this value to waybillNo This variable ,
# Subsequently passed waybillNo You can use this extracted value .
parameter.update_rv({
'waybillNo': result['content']})
assert result['code'] == 0
@allure.description(u" Create waybill ")
def test_create_waybill(self):
# Dependency expressions 
# Use replace_expression The parameterized upstream interface stores the returned value , here replace_expression And call ParameterSetting The expressions in the class are consistent 
# Use rv Indicates that the returned results of the interface are saved as dictionaries and calls ParameterSetting Methods in class rv Agreement 
# upstream_create_waybill_data Is the original interface parameter , Store in data Under the document .
# replace_expression Is a dependency expression , Written directly in variables , It can also be written in the configuration yaml In the file .
# rv Is the dictionary stored by the extracted parameters .
replace_expression = {
'waybillNo': rv['waybillNo']}
# Method parameter substitution 
# call ParameterSetting Class dparameters_depend Method 
# call raw_parameter Class upstream_create_waybill_data Parameters 
parameter.parameters_depend(upstream_create_waybill_data, replace_expression, rv)
# Dictionary original parameter replacement 
upstream_create_waybill_data['customerOrderNo'] = 'ce{random.randint(100, 999)}'
result = requests.post('/waybill/createWaybill', upstream_create_waybill_data).json()
assert result['code'] == 1

5. Be careful : In screenshots “waybillNo”:“Y22011101026000006”, It has changed after replacement .

Ten . Log encapsulation

1. Color file storage location , Storage file log level log name, etc

2.from tool.log import logger Call log encapsulation file

log.py

import logging
# Set the package of log color 
import colorlog
import datetime
''' Loggers: Recorder , Provide interfaces that can be directly used by application code ; Handlers: processor , Send logs generated by the recorder to the destination ; Filters: filter , Provide better granularity control , Decide which logs will be output ; Formatters: formatter , Set composition structure and message field of log content . %(name)s Logger Name # That's one of them .getLogger The path in , Or we can use his file name to see what we fill in %(levelno)s Log level in digital form # The level of the printed objects in the log %(levelname)s Log level in text form # The name of the level %(pathname)s The full pathname of the module calling the log output function , There may be no %(filename)s The file name of the module calling the log output function %(module)s The module name that calls the log output function %(funcName)s Call the function name of the log output function %(lineno)d The line of code that calls the log output function %(created)f current time , use UNIX The standard means the float of time The number of points indicates %(relativeCreated)d When outputting log information , since Logger Create to Milliseconds from %(asctime)s Current time in string form . The default format is “2003-07-08 16:49:45,896”. After the comma is milliseconds %(thread)d Threads ID. There may be no %(threadName)s The thread of . There may be no %(process)d process ID. There may be no %(message)s User output message '''
''' Log color configuration '''
log_colors_config = {

# Color support blue blue ,green green ,red Red ,yellow yellow ,cyan Cyan 
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'bold_red',
}
''' establish logger Recorder '''
logger = logging.getLogger('test')
# Output to console 
console_handler = logging.StreamHandler()
# output to a file 
path = "./log"
''' Get the current date as the log file name '''
fileName = str(datetime.datetime.now().year) + '-' + str(datetime.datetime.now().month) + '-' + str(
datetime.datetime.now().day) + '.log'
file_handler = logging.FileHandler(filename=path+'\\'+fileName, mode='a', encoding='utf8')
''' Log level settings '''
#logger Controls the lowest level of log output ( The highest priority )
logger.setLevel(logging.DEBUG)
#console_handler Set the console minimum output level log 
console_handler.setLevel(logging.DEBUG)
#console_handler Set the minimum output level log saved to the file 
file_handler.setLevel(logging.INFO)
# Log output format 
# Save the log format of the file 
file_formatter = logging.Formatter(
fmt='[%(asctime)s] %(filename)s -> %(funcName)s line:%(lineno)d [%(levelname)s] : %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Console log format 
console_formatter = colorlog.ColoredFormatter(
# Output that information , Time , file name , Function name and so on 
fmt='%(log_color)s[%(asctime)s] %(filename)s -> %(funcName)s line:%(lineno)d [%(levelname)s] : %(message)s',
# Time format 
datefmt='%Y-%m-%d %H:%M:%S',
log_colors=log_colors_config
)
console_handler.setFormatter(console_formatter)
file_handler.setFormatter(file_formatter)
# Duplicate log problem :
# Here's the judgment , If logger.handlers The list is empty. , Then add , otherwise , Write a log directly , Solve the problem of repeated printing 
if not logger.handlers:
logger.addHandler(console_handler)
logger.addHandler(file_handler)
console_handler.close()
file_handler.close()
if __name__ == '__main__':
logger.debug(u' Color ')
logger.info(u' Green test log saving path {}'.format(path+fileName))
logger.warning(u' Color ')
logger.error('error')
logger.critical('critical')

11、 ... and .yaml File configuration

1. Read and write , If the file does not exist, create a new file and read / write it

2. It's used here ’a’ Operation file , Additional

yaml_.py

import os
from ruamel import yaml
class YamlUsage:
def __init__(self,file_path):
self.file_path = file_path
def write_yaml(self,data,mode):
self.__check()
file = open(self.file_path, mode, encoding='utf-8')
yaml.dump(data, file, Dumper=yaml.RoundTripDumper)
file.close()
def read_yaml(self):
self.__check()
file = open(self.file_path, 'r', encoding='utf-8')
with file as doc:
content = yaml.load(doc, Loader=yaml.Loader)
return content
def __check(self):
if os.path.exists(self.file_path):
pass
else:
file = open(self.file_path, 'w')
file.close()
if __name__ == '__main__':
# The test is written here bill_search_test.py File data , You can also manually re yaml It's in the file 
yml = YamlUsage(r'F:\bm_api\data\bill_search_data.yaml')
data1 = {

"billCommonNo": "",
"status": None,
"makeBillStatus": None,
"projectNos": [],
"commonCompanyName": "",
"year": "2022",
"month": "07",
"billType": "UP",
"size": 20,
"page": 1
}
data2 = {

"billCommonNo": "",
"status": None,
"makeBillStatus": None,
"projectNos": ['320SF000206003'],
"commonCompanyName": "",
"year": "2022",
"month": "07",
"billType": "UP",
"size": 20,
"page": 1
}
data3 = {

"billCommonNo": "",
"status": 'RECONCILIATIONING',
"makeBillStatus": None,
"projectNos": [],
"commonCompanyName": "",
"year": "2022",
"month": "07",
"billType": "UP",
"size": 20,
"page": 1
}
dat = [data1,data2,data3]
yml.write_yaml(data,'a')
print(yml.read_yaml())

bill_search_data.yaml

- billCommonNo: ''
status:
makeBillStatus:
projectNos: []
commonCompanyName: ''
year: '2022'
month: '07'
billType: UP
size: 20
page: 1
- billCommonNo: ''
status:
makeBillStatus:
projectNos:
- 320SF000206003
commonCompanyName: ''
year: '2022'
month: '07'
billType: UP
size: 20
page: 1
- billCommonNo: ''
status: RECONCILIATIONING
makeBillStatus:
projectNos: []
commonCompanyName: ''
year: '2022'
month: '07'
billType: UP
size: 20
page: 1

Twelve .mysql File configuration

mysql.py

import pymysql
from data.config_data import MYSQL_CONFIG
class MysqlDb():
def __init__(self, host, port, user, password, db_name):
self.db = pymysql.connect(host=host,port=port,user=user,passwd=password,db=db_name)
self.cur = self.db.cursor(cursor=pymysql.cursors.DictCursor)
# Triggered when the object resource is released , The last action when an object is about to be deleted 
def __del__(self):
self.cur.close()
self.db.close()
def select_db(self, sql):
""" Inquire about """
self.cur.execute(sql)
data = self.cur.fetchall()
return data
def execute_db(self, sql):
""" to update / Insert / Delete """
try:
self.cur.execute(sql)
self.db.commit()
except Exception as e:
print(u" Operation error :{}".format(e))
self.db.rollback()
if __name__ == '__main__':
aa = '99' # A parameterized sql
mysql_db = MysqlDb(*MYSQL_CONFIG)
print(mysql_db.select_db('SELECT * FROM case_test'))
mysql_db.execute_db("INSERT INTO case_test( aa, `add`) VALUES ('1', '1');")
mysql_db.execute_db("UPDATE case_test SET aa = '2', `add` = {aa} WHERE id = 1;")
mysql_db.execute_db("delete from case_test where aa={aa}")
print(mysql_db.select_db('SELECT * FROM case_test'))
# Multiple tables associated query 
print(mysql_db.select_db("SELECT * FROM apitest_environment as e left join auth_user as u on e.user_id=u.id "))

13、 ... and .pytest.ini File configuration

pytest.ini

[pytest]
# Change the default run pytest command , Set the directory for reporting raw data 
addopts = -vs --alluredir ./report/allure_raw --durations=0 --alluredir ./report/allure_raw
# Set the run case Directory 
testpaths = case
# Set the label , The specified use case can be run according to the tag 
markers =
waybill : run waybill case -m waybill
bill :run bill case -m bill

fourteen . Examples of three

bill_search_test.py

''' Bill search interface '''
import allure
import pytest
import time
from tool.log import logger
from tool.requests_ import Requests
from tool.yaml_ import YamlUsage
# call Requests class , And instantiate the class ( call Requests Class )
request = Requests()
# call yaml_ Configuration information in the file 
yml = YamlUsage(r'F:\bm_api\data\bill_search_data.yaml')
# call YamlUsage Class read_yaml Method 
data_yml = yml.read_yaml()
@allure.description(u' Bill search ')
# Give parameters to each group of parameters 
@pytest.mark.parametrize('data', data_yml, ids=[u' Bill year / month search ',u' Item search ',u' Status search '])
def test_bill_search(data):
time.sleep(1) # If the search interface is too frequent, it will fail to access 
result = request.post('/bill/getBillCommonList', data=data).json()
logger.info(u' Address of the interface :/bill/getBillCommonList , Request parameters :{data}, Return results :{result}')
assert result['code'] == 0

15、 ... and .run Run the file

run.py

import pytest
import os
import shutil
if __name__ == '__main__':
try:
# Delete previous folders 
shutil.rmtree("report/allure_raw")
except:
print(u' The original report file has not been generated before ')
pytest.main([])
# Compile the original report file and start the report service 
os.system('allure serve report/allure_raw')

1.allure The report

Reprint :https://blog.csdn.net/aaaaaaaaanjjj/article/details/122487373


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved