Pytest+Allure+Jenkins接口自動化項目實戰(一)

       經過一週多時間,基於python+pytest+excel+allure框架的接口自動化測試初版已基本實現,包括基本配置讀取、用例讀取、用例執行、sql讀取執行、前置數據準備、後置數據清理以及測試報告生成等,環境獨立運行、項目獨立運行、用例獨立運行、jenkins集成、郵件發送暫未實現,再後期版本會再次推出,現在把整個框架設計思路和想法分享給大家來參考和借鑑。希望大家也能提供更好的思路和方法幫助我進行優化改進。

       實戰項目是三端交互的項目,所以在設計思路上要考慮多項目如何交互,目前只寫了1個項目的,其它2個項目都預留了位置,後期直接添加就可以,思路一樣。

一、整個代碼目錄及介紹

common

request.py  封裝post、get請求方法,供所有地方調用

login.py 封裝各項目、各種方式的登錄方法,供所有地方調用

readFile.py 封裝讀取yaml中配置文件、excel中測試用例方法,供所有地方調用

execSql.py 封裝sql操作增、刪、改、查方法,供所有地方調用

prefixSqlData.py 封裝前置、後置公共sql,供所有地方調用

assertion.py 封裝斷言方法,供所有用例中調用

logs.py 封裝日誌方法,供所有地方調用

config

sql.yaml  前置數據sql語句、後置清理sql語句等

test.yaml  test環境數據庫配置、接口域名、測試賬號、登錄接口數據等

uat.yaml  uat環境數據庫配置、接口域名、測試賬號、登錄接口數據等

testcase.xls  測試用例等

testcase

項目1

    用例1、用例2

項目2

    用例1、用例2

testcase.xls  接口測試用例等

conftest.py  放置了登錄獲取token供所有用例調用

run_all_case.py 執行所有測試用例並生成測試報告

logs

每天的日誌數據

report

html  測試報告index.html

二、yaml文件基本配置

項目一:
   url: 'https://www.baidu.com/'
   headers:
        Content-Type: 'application/json'
        Authorization: 'token'
   account:
     a_account: '17900000000'
     b_account: '18000000000'
     c_account: '19900000000'
     c_account1: '19900000001'

   c_login:
     method: post
     url: '/user/login'
     param:
        type: "7"
        login_from: 7
        mobile: "18888888888"
        code: "123456"
   c_sms_code:
     method: post
     url: '/1.0/users/login'



   mysql:
     host: 127.0.0.1
     user: test
     pwd: test
     test_db: user



項目二:
  url: 'https://www.baidu.com/'

三、yaml文件sql配置

項目一:
  查id:
    - select_list
    - select id from A.B where create_mobile={}
  查用戶id:
    - select_list
    - select id from A.B where mobile={}
  查團隊id:
    - select_list
    - select id from A.B where company_user_id=(select id from A.B where mobile={})
  查C端用戶id:
    - select_list
    - select user_id from A.B where mobile in({})
  解除用戶:
    - update
    - update A.B set status=2 where mobile={}

項目二:
  查id:
    - select_list
    - select id from A.B where create_mobile={}
  查用戶id:
    - select_list
    - select id from A.B where mobile={}
  查團隊id:
    - select_list
    - select id from A.B where company_user_id=(select id from A.B where mobile={})
  查C端用戶id:
    - select_list
    - select user_id from A.B where mobile in({})
  解除用戶:
    - update
    - update A.B set status=2 where mobile={}

四、讀取yaml文件、excel用例文件

#!/usr/bin/env python
# _*_coding:utf-8_*_
import yaml,os,sys,xlwt,xlrd
from common.logs import Log

class ReadFile(object):
    log = Log()
    _instance=None
    def __new__(cls,*args,**kwargs):
        if cls._instance is None:
            cls._instance=super().__new__(cls)
        return cls._instance

    def __init__(self):
        self.excel_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config/testcase.xls')
        self.yaml_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config/test.yaml')
        self.sql_yaml_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config/sql.yaml')

    def read_yaml(self,path_type):
        """
        讀yaml文件
        :return:
        """
        try:
            if path_type=='yaml_path':
                file_path=self.yaml_path
            elif path_type=='sql_yaml_path':
                file_path=self.sql_yaml_path

            with open(file_path,'r',encoding='utf-8') as f:
                return yaml.load(f.read())
        except Exception as e:
            self.log.error("讀yaml文件報錯{}".format(e))

    def read_excel(self,sheet_name,function,casename=None):
        """
        讀取excel
        :param sheet_name:
        :param function:
        :return:
        """
        try:
            book=xlrd.open_workbook(self.excel_path)
            sheet=book.sheet_by_name(sheet_name)
            param=[]
            for i in range(0,sheet.nrows):
                if casename==None:
                    if sheet.row_values(i)[0]==function and sheet.row_values(i)[3]==1:
                        param.append(sheet.row_values(i))
                else:
                    if sheet.row_values(i)[0]==function and sheet.row_values(i)[1]==casename and sheet.row_values(i)[3]==1:
                        param.append(sheet.row_values(i))
            return param
        except Exception as e:
            self.log.error("讀取excel報錯{}".format(e))


if __name__ == '__main__':
    test=ReadFile()
    print(test.read_excel('lxk','我的','全部頁面'))

五、用例模板如下

根據每個sheet存放不同項目的測試用例,然後根據再根據sheet去讀取對應項目模塊的測試用例

Function模塊、CaseName測試用例名、Type請求類型、Run是否執行、URL接口地址、Headers請求頭、Param請求參數、SQL1、SQL2、SQL3測試中需用到的前置數據或後置數據、AssertType斷言類型,因爲接口返回的響應數據可能會多種多樣,所以這裏斷言分了幾種情況、Expect1預期結果1、Expect2預期結果2、Expect3預期結果3

六、request方法

#!/usr/bin/env python
# _*_coding:utf-8_*_
import requests,urllib3
from urllib3 import encode_multipart_formdata
from common.logs import Log

class RunMethod(object):
    """
    request
    """
    log = Log()
    urllib3.disable_warnings()


    def post_main(self,url,data,header,file=None):
        """
        post請求
        :param url:
        :param data:
        :param header:
        :param file:
        :return:
        """
        res=None
        if file!=None:
            res=requests.post(url=url,json=data,headers=header,verify=False)
        else:
            res = requests.post(url=url, json=data,headers=header, files=file, verify=False)
        return res.json()

    def get_main(self,url,header,param=None):
        """
        get請求
        :param url:
        :param header:
        :param param:
        :return:
        """
        res=None
        if param!=None:
            res=requests.get(url=url,headers=header,verify=False)
        else:
            res = requests.get(url=url, headers=header, json=param,verify=False)
        return res.json()

    def run_main(self,method,url,header,data=None,file=None):
        """
        被調用主request
        :param method:
        :param url:
        :param header:
        :param data:
        :param file:
        :return:
        """
        try:
            res=None
            if method=='post' or method=='POST' or method=='Post':
                res=self.post_main(url,data,header,file=None)
            elif method=='get' or method=='GET' or method=='Get':
                res=self.get_main(url,header,param=None)
            else:
                return "request傳參錯誤"
            return res
        except Exception as e:
            self.log.error("請求方法報錯{}".farmat(e))
if __name__ == '__main__':
    print(111)

七、登錄方法

#!/usr/bin/env python
# _*_coding:utf-8_*_
from common import request
from common.readFile import ReadFile
from common.logs import Log


class Login(object):
    """
    登錄
    """
    log = Log()
    request = request.RunMethod()

    def __init__(self):
        self.yaml_data = ReadFile().read_yaml('yaml_path')['lxk']
        self.header = self.yaml_data['headers']
        self.url = self.yaml_data['url']
        self.lxk_c_url = self.yaml_data['c_login']['url']
        self.lxk_c_method = self.yaml_data['c_login']['method']
        self.lxk_c_param = self.yaml_data['c_login']['param']


    def lxk_c_login(self,project,mobile):
        """
        藍薪卡C端登錄
        :param project:
        :param mobile:
        :return:
        """
        try:
            if project=='lxk_c':
                self.lxk_c_param['mobile']=mobile
                result=self.request.run_main(self.lxk_c_method, self.url+self.lxk_c_url, self.header, self.lxk_c_param)
            elif project=='lxk_a':
                pass
            elif project=='lxk_b':
                pass
            return result
        except Exception as e:
            self.log.error('登錄報錯{}'.format(e))

if __name__ == '__main__':
    test=Login()
    print(test.lxk_c_login('lxk_c','18221124104'))

八、操作sql方法

#!/usr/bin/env python
# _*_coding:utf-8_*_
from common.readFile import ReadFile
import pymysql
import sys
from common.logs import Log



class ExecSql(object):
    """
    執行sql語句類
    """
    log = Log()

    _instance=None
    def __new__(cls,*args,**kwargs):
        if cls._instance is None:
            cls._instance=super().__new__(cls)
        return cls._instance

    def __init__(self):
        """
        初始化mysql配置
        :param platform_name:
        """
        #self.sql_conf = self._get_sql_conf(platform_name)
        self.sql_conf=None

    def _get_sql_conf(self, project):
        """
        獲取mysql配置
        :param platform_name:
        :return:
        """
        try:
            return ReadFile().read_yaml('yaml_path')[project]['mysql']
        except:
            self.log.error("找不到對應項目:{0}".format(project))

    def connect_db(self):
        """
        連接mysql
        :return:
        """
        host = self.sql_conf['host']
        user = self.sql_conf['user']
        pwd = self.sql_conf['pwd']
        test_db = self.sql_conf['test_db']
        try:
            self.conn = pymysql.connect(host=host, user=user, password=pwd, db=test_db, port=3306, charset="utf8")
        except Exception as e:
            self.log.error("連接mysql失敗:{0}".format(e))

    def get_cursor(self):
        """
        獲取遊標
        :return:
        """
        self.cursor=self.conn.cursor()
        return self.cursor

    def exec_sql(self,project,sql_type,sql):
        """
        執行sql語句
        :param sql_type:
        :param sql:
        :return:
        """
        self.sql_conf = self._get_sql_conf(project)
        try:
            if sql_type == 'select_one':
                self.connect_db()
                cursor = self.get_cursor()
                cursor.execute(sql)
                result = cursor.fetchone()
            elif sql_type == 'select_list':
                self.connect_db()
                cursor = self.get_cursor()
                cursor.execute(sql)
                result = cursor.fetchall()
            elif sql_type == 'update' or sql_type == 'del' or sql_type == 'insert':
                self.connect_db()
                result = self.get_cursor().execute(sql)
            self.conn.commit()
            self.cursor.close()
            self.conn.close()
            return result
        except Exception as e:
            self.log.error("執行sql語句報錯:{0}".format(e))


if __name__ == '__main__':
    test = ExecSql()
    a=test.exec_sql('lxk',"select_list","sql)
    print(aaa)

九、日誌方法

#!/usr/bin/env python
# _*_coding:utf-8 _*_
import os, time, logging
log_path=os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'logs')
if not os.path.exists(log_path): os.mkdir(log_path)


class Log(object):
    """
    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]-%(filename)s]-%(levelname)s:%(message)s')

    def __console(self, level, message):
        fh=logging.FileHandler(self.logname, 'a', 'utf-8')
        fh.setLevel(logging.DEBUG)
        fh.setFormatter(self.formatter)
        self.logger.addHandler(fh)
        ch = logging.StreamHandler()
        ch.setLevel(logging.INFO)
        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(ch)
        self.logger.removeHandler(fh)
        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("---測試---")

十、斷言方法

#!/usr/bin/env python
# _*_coding:utf-8_*_
from common.execSql import ExecSql
from common.logs import Log

class Assertion(object):
    log=Log()
    sql_values_list = []
    response_values = []

    def __init__(self):
        self.test=ExecSql().exec_sql

    def get_sql_data(self,project,sql_type,sql):
        '''
        查詢sql數據組合list
        :param project:
        :param sql_type:
        :param sql:
        :return:
        '''
        try:
            sql_values=self.test(project,sql_type,sql)
            for i in sql_values:
                for j in i:
                    self.sql_values_list.append(j)
        except Exception as e:
            self.log.error("查詢sql數據組合list報錯{}".format(e))


    def get_response_data(self,response_data, keys=[]):
        '''
        獲取接口響應數據組合list
        :param response_data:
        :param keys:
        :return:
        '''
        try:
            if isinstance(response_data, list):
                for value in response_data:
                    if isinstance(value, list) or isinstance(value, dict):
                        self.get_response_data(value, keys)
            elif isinstance(response_data, dict):
                for i, j in sorted(response_data.items()):
                    if i in keys:
                        self.response_values.append(j)
                    else:
                        self.get_response_data(j, keys)
            else:
                pass
        except Exception as e:
            self.log.error("獲取接口響應數據組合list報錯{}".format(e))

    def asser(self,function,casename,expect,response_data,assert_type=None):
        '''
        斷言
        :param assert_type:
        :param expect:
        :param response_data:
        :return:
        '''
        try:
            if assert_type=='type1':
                assert self.sql_values_list==self.response_values
                self.log.info("查詢sql數據組合list爲{}".format(self.sql_values_list))
                self.log.info("接口響應數據組合list爲{}".format(self.response_values))
            assert eval(expect)['code'] == response_data['code']
            assert eval(expect)['msg'] == response_data['msg']
            self.log.info("{}——{}【PASS】".format(function,casename))
        except Exception as e:
            self.log.error("{}——{}【PASS】{}".format(function,casename,e))

if __name__ == '__main__':
    # sql="sql"
    # test=Assertion()
    # test.get_sql_data(self,project,sql_type,sql)
    self.log.error("查詢sql數據組合list報錯{}".format(e))

十一、conftest登錄獲取token

#!/usr/bin/env python
# _*_coding:utf-8_*_
import pytest,os,yaml,requests
from common.readFile import ReadFile
from common.login import Login

yaml_data=ReadFile().read_yaml('yaml_path')

@pytest.fixture(scope='session')
def get_lxk_c_headers():
    """
    登錄獲取token更新headers
    :return:
    """
    headers=yaml_data['lxk']['headers']
    token=Login().lxk_c_login('lxk_c',yaml_data['lxk']['account']['c_account'])['data']['token']
    headers['Authorization']=token
    return headers

十二、測試用例方法

#!/usr/bin/env python
# _*_coding:utf-8_*_

import pytest
from common.readFile import ReadFile
from common.request import RunMethod
from common.assertion import Assertion
from common.execSql import ExecSql
from common.prefixSqlData import MakeSqlData
import allure

data = ReadFile().read_excel('lxk', '我的報名')

@pytest.mark.parametrize('function,casename,type,run,url,hearders,param,sql1,sql2,sql3,asserttype,expect1,expect2,expect3',data)
class Test(object):
    '''我的報名'''

    request = RunMethod().run_main
    assertion = Assertion()
    exec_sql = ExecSql().exec_sql
    yaml_data = ReadFile().read_yaml('yaml_path')['lxk']
    sql_yaml_data = ReadFile().read_yaml('sql_yaml_path')['lxk']
    prefix_sql_data = MakeSqlData('lxk').make_sql_data


    def setup_class(self):
        '''
        數據初始化
        :return:
        '''
        prefix_data=self.prefix_sql_data(self.yaml_data['account']['b_account'], self.yaml_data['account']['c_account'])
        company_id = self.exec_sql('lxk', self.sql_yaml_data['查企業id'][0], self.sql_yaml_data['查企業id'][1].format(self.yaml_data['account']['b_account']))[0][0]
        task_id = self.exec_sql('lxk', self.sql_yaml_data['查任務id'][0], self.sql_yaml_data['查任務id'][1].format(self.yaml_data['account']['b_account']))[0][0]
        self.exec_sql('lxk', self.sql_yaml_data['報名任務'][0], self.sql_yaml_data['報名任務'][1].format(prefix_data['user_id'], task_id, company_id,self.yaml_data['account']['c_account'],1))
        self.exec_sql('lxk', self.sql_yaml_data['報名任務'][0], self.sql_yaml_data['報名任務'][1].format(prefix_data['user_id'], task_id, company_id,self.yaml_data['account']['c_account'],2))
        self.exec_sql('lxk', self.sql_yaml_data['報名任務'][0], self.sql_yaml_data['報名任務'][1].format(prefix_data['user_id'], task_id, company_id,self.yaml_data['account']['c_account'],3))
        self.exec_sql('lxk', self.sql_yaml_data['報名任務'][0], self.sql_yaml_data['報名任務'][1].format(prefix_data['user_id'], task_id, company_id,self.yaml_data['account']['c_account'],4))
        self.exec_sql('lxk', self.sql_yaml_data['報名任務'][0], self.sql_yaml_data['報名任務'][1].format(prefix_data['user_id'], task_id, company_id,self.yaml_data['account']['c_account'],5))
        self.exec_sql('lxk', self.sql_yaml_data['報名任務'][0], self.sql_yaml_data['報名任務'][1].format(prefix_data['user_id'], task_id, company_id,self.yaml_data['account']['c_account'],7))
        self.exec_sql('lxk', self.sql_yaml_data['報名任務'][0], self.sql_yaml_data['報名任務'][1].format(prefix_data['user_id'], task_id, company_id,self.yaml_data['account']['c_account'], 8))

    def teardown_class(self):
        '''
        數據清理
        :return:
        '''
        self.exec_sql('lxk', self.sql_yaml_data['刪除已報名任務'][0], self.sql_yaml_data['刪除已報名任務'][1].format(self.yaml_data['account']['c_account']))
        self.exec_sql('lxk', self.sql_yaml_data['解除用戶團隊'][0], self.sql_yaml_data['解除用戶團隊'][1].format(self.yaml_data['account']['c_account']))

    #@allure.feature('藍薪卡')
    @allure.story('lxk_我的報名')
    def test_apply_task(self,get_lxk_c_headers,function,casename,type,run,url,hearders,param,sql1,sql2,sql3,asserttype,expect1,expect2,expect3):
        '''
        我的報名
        :param get_lxk_c_headers:
        :param function:
        :param casename:
        :param type:
        :param run:
        :param url:
        :param hearders:
        :param param:
        :param sql1:
        :param sql2:
        :param sql3:
        :param asserttype:
        :param expect1:
        :param expect2:
        :param expect3:
        :return:
        '''
        response_data = self.request(type,self.yaml_data['url']+url,get_lxk_c_headers,eval(param))
        self.assertion.get_sql_data('lxk',eval(sql1)[0],eval(sql1)[1].format(self.yaml_data['account']['c_account']))
        self.assertion.get_response_data(response_data,eval(expect2))
        self.assertion.asser(function,casename,expect1,response_data,asserttype)



if __name__ == "__main__":
    pytest.main(["-s", "test_001_applyTask.py"])

十三、run_all_case主程序執行入口

#!/usr/bin/env python
# _*_coding:utf-8_*_
import pytest,os,allure
if __name__ == "__main__":

    pytest.main(['-s',''])
    #生成測試報告json
    pytest.main(["-s", "-q", '--alluredir', 'C:/Users/wangli/PycharmProjects/PytestAutomation/report/result'])
    #將測試報告轉爲html格式
    split='allure '+'generate '+'C:/Users/wangli/PycharmProjects/PytestAutomation/report/result '+'-o '+'C:/Users/wangli/PycharmProjects/PytestAutomation/report/html '+'--clean'
    os.system('cd C:/Users/wangli/PycharmProjects/PytestAutomation/report')
    os.system(split)
    print(split)

十四、測試報告如下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章