51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 927|回复: 2
打印 上一主题 下一主题

Jenkins+python+pytest+requests+allure接口测试

[复制链接]
  • TA的每日心情
    无聊
    前天 09:05
  • 签到天数: 1050 天

    连续签到: 1 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2024-1-10 10:57:54 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    最近在这整理知识,发现在pytest的知识文档缺少系统性,这里整理一下,方便后续回忆

    在python中,大家比较熟悉的两个框架是unittest和pytest:

    l  Unittest是Python标准库中自带的单元测试框架,Unittest有时候也被称为PyUnit,就像JUnit是Java语言的标准单元测试框架一样,Unittest则是Python语言的标准单元测试框架。
    l  Pytest是Python的另一个第三方单元测试库。它的目的是让单元测试变得更容易,并且也能扩展到支持应用层面复杂的功能测试。

    两者之间的区别如下:
      
      
      unittest
      
      pytest
      
      用例编写规则
      
      

    1)测试文件必须先import  unittest

      2)测试类必须继承unittest.TestCase
      3)测试方法必须以“test_”开头
      4)测试类必须要有unittest.main()方法
      
      1)测试文件必须以“test_”开头或者“_test”结尾(如:test_ab.py)
      2)测试方法必须以“test_”开头
      3)测试类命名以“Test”开头
      
      用例分类执行
      
      默认执行全部用例,也可以通过加载testsult,执行部分用例
      
      可以通过@pytest.mark来标记类和方法,pytest.main加入参数(“.m”)可以只运行标记的类和方法
      
      用例前置和后置
      
      提供了setUp/tearDown,只能针对所有用例
      
      Pytestzhong fixture显然更加灵活。可以任意自定义方法函数,只要加上@pytest.fixture()这个装饰器,那么被装饰的方法就可以被使用
      
      参数化
      
      需依赖ddt
      
      使用@pytest.parametrize装饰器
      
      断言
      
      很多断言格式(assertEqualassertInassertTrueassertFalse
      
      只有assert一个表达式,用起来比较方便
      
      报告
      
      使用HTMLTestRunnerNew
      
      pytest-HTMLallure插件
      
      失败重跑
      
      无此功能
      
      Pytest支持用例执行失败重跑,pytest-rerunfailures插件
      

    这里试用的pytest框架,加上request来实现接口自动化的测试,整个框架考虑到使用数据驱动的方式,将数据维护在Excel文档中。
    1、下载安装allure下载地址https://github.com/allure-framework/allure2/releases
    https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/
    选择需要的版本下载,这里我下载的是2.13.2版本

    下载好后,解压到你需要存放的路目录,并配置环境变量




    检查是否配置成功,执行cmd,输入命令 allure,出现如下图,则表示安装成功



    2、下载安装python
    下载好后,安装并配置环境变量,具体流程可以网络查找
    3、python安装依赖包
    cmd命令执行,也可以通过项目中的requirements.txt来安装,安装步骤后面再说
    1. <font size="3" face="微软雅黑">pip3 install allure-pytest
    2. pip3 install pytest
    3. pip3 install pytest_html
    4. pip3 install request
    5. </font>
    复制代码
    4、下载并安装pycharm工具,查看网络教程5、在pycharm,新建项目及编码
    项目目录如图:
    base:存放一些最底层的方法封装,协议,请求发送等。
    common:存放一些公共方法。
    config:存放配置文件。
    testData:存放测试数据。
    log:存放日志。
    report:存放报告。
    testCase:存放用例。
    utils:存放公共类。
    readme:用于说明文档。
    requirements.txt:用于记录所有依赖包极其版本号,便于环境部署,可以通过pip命令自动生成和安装

    这里采用数据驱动的方式,数据通过读取excel文件来执行测试,所以这里需要封装读取excel的方法,使用xlrd来操作读取
    1. <font size="3" face="微软雅黑"># operationExcel.py
    2. import json

    3. from common.contentsManage import filePath
    4. import xlrd, xlwt
    5. class OperationExcel:
    6.     # 获取shell表
    7.     def getSheet(self, index=0):
    8.         book = xlrd.open_workbook(filePath())
    9.         return book.sheet_by_index(index) #根据索引获取到sheet表
    10. </font>
    复制代码
    excel 文件内容如图

    封装用例
    1. <font size="3" face="微软雅黑"># test_api_all.py
    2. # 参数化运用所有用例
    3. import json
    4. import pytest

    5. from utils.operationExcel import OperationExcel, ExcelVarles
    6. from base.method import ApiRequest
    7. from common.log import logger

    8. opExcel = OperationExcel()
    9. apiRequest = ApiRequest()

    10. @pytest.mark.parametrize('data', opExcel.getExcelData()) # 装饰器进行封装用例

    11. def test_api(data, login_token=None):
    12.     if data[ExcelVarles.case_isRun] == "N" :
    13.         logger.info("跳过执行用例")
    14.         return

    15.     # 请求头作为空处理并添加token
    16.     headers = data[ExcelVarles.case_headers]
    17.     if len(str(headers).split()) == 0:
    18.         pass
    19.     elif len(str(headers).split()) >= 0:
    20.         headers = json.loads(headers) # 转换为字典
    21.     #     headers['Authorization'] = login_token # 获取登录返回的token并添加到读取出来的headers里面
    22.         headers = headers

    23.     # 对请求参数做为空处理
    24.     params = data[ExcelVarles.case_data]
    25.     if len(str(params).split()) == 0:
    26.         pass
    27.     elif len(str(params).split()) == 0:
    28.         params = params

    29.     url = data[ExcelVarles.case_server] + data[ExcelVarles.case_url] + "?" + params
    30.     r = apiRequest.all_method( data[ExcelVarles.case_method] ,url, headers=headers)
    31.     logger.info(f"响应结果{r}")
    32.     responseResult = json.loads(r)

    33.     case_result_assert(data[ExcelVarles.case_code], responseResult['code'])

    34. # 断言封装
    35. def case_result_assert(expectedResult, actualReuls) :
    36.     '''
    37.     断言封装
    38.     :param expectedResult: 预期结果
    39.     :param actualReuls: 实际结果
    40.     :return:
    41.     '''
    42.     assert expectedResult == actualReuls # 状态码
    43. </font>
    复制代码
    封装日志文件
    1. <font size="3" face="微软雅黑"># log.py
    2. #!/usr/bin/python
    3. # -*- coding: utf-8 -*-
    4. import logging
    5. import time
    6. import os

    7. from common.contentsManage import logDir

    8. # BASE_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
    9. # # 定义日志文件路径
    10. # LOG_PATH = os.path.join(BASE_PATH, "log")
    11. # if not os.path.exists(LOG_PATH):
    12. #     os.mkdir(LOG_PATH)

    13. # 方法1
    14. # 封装自己的logging
    15. class MyLogger:
    16.     def __init__(self):
    17.         self._logName = os.path.join(logDir(), "{}.log".format(time.strftime("%Y%m%d")))
    18.         self._logger = logging.getLogger("logger")
    19.         self._logger.setLevel(logging.DEBUG)
    20.         self._formatter = logging.Formatter('[%(asctime)s][%(filename)s %(lineno)d][%(levelname)s]:%(message)s')
    21.         self._streamHandler = logging.StreamHandler()
    22.         self._fileHandler = logging.FileHandler(self._logName, mode='a', encoding="utf-8")
    23.         self._streamHandler.setFormatter(self._formatter)
    24.         self._fileHandler.setFormatter(self._formatter)
    25.         self._logger.addHandler(self._streamHandler)
    26.         self._logger.addHandler(self._fileHandler)

    27.     # 获取logger日志记录器
    28.     def get_logger(self):
    29.         return self._logger

    30. logger = MyLogger().get_logger()

    31. </font>
    复制代码
    封装请求方法
    1. <font size="3" face="微软雅黑"># method.py
    2. import json
    3. import requests
    4. from common.log import logger
    5. from utils.commonUtils import isJson

    6. class ApiRequest(object):
    7.     # ---- 第一种请求方式封装requests库,调用可根据实际情况传参 ----
    8.     # def send_requests(self, method, url, data=None, params=None, headers=None,
    9.     #                   cookies=None,json=None,files=None,auth=None,timeout=None,
    10.     #                   proxies=None,verify=None,cert=None):
    11.     #     self.res = requestes.request(method=method, url= url, headers=headers,data=data,
    12.     #                                params=params, cookies=cookies,json = json,files=files,
    13.     #                                auth=auth, timeout= timeout, proxies=proxies,verify=verify,
    14.     #                                cert=cert)
    15.     #     return self.res

    16.     # 第二种封装方法
    17.     def get(self, url, data=None, headers=None, payload=None):
    18.         if headers is not None:
    19.             res = requests.get(url=url, data=data,headers=headers)
    20.         else:
    21.             res = requests.get(url=url, data=data)

    22.         return res

    23.     def post(self, url, data, headers, payload:dict, files=None):
    24.         if headers is not None:
    25.     res = requests.post(url=url, data=data, headers=headers)
    26.         else :
    27.             res = requests.post(url=url, data=data)

    28.         if str(res) == "<Response [200]>" :
    29.             return res.json()
    30.         else :
    31.             return res.text

    32.     def put(self,url,data,headers, payload:dict, files=None):
    33.         if headers is not None :
    34.             res = requests.put(url=url,data=data,headers=headers)
    35.         else:
    36.             res = requests.put(url=url,data=data)
    37.         return res

    38.     def delete(self,url,data,headers, payload:dict):
    39.         if headers is not None :
    40.             res = requests.delete(url=url,data=data,headers=headers)
    41.         else:
    42.             res = requests.delete(url=url,data=data)

    43.         return res

    44.     def all_method(self, method, url, data=None, headers=None, payload=None, files=None):
    45.         logger.info(f"请求方法是{method}, 请求地址{url}")
    46.         if headers == None:
    47.             headers = {}

    48.         if method.upper()=='GET':
    49.             res = self.get(url,data,headers, payload)
    50.         elif method.upper()=='POST':
    51.             res = self.post(url, data, headers, payload, files)
    52.         elif method.upper() == 'PUT':
    53.             res = self.put(url, data, headers, payload, files)
    54.         elif method.upper() == 'DELETE':
    55.             res = self.delete(url, data, headers, payload)
    56.         else :
    57.             res = f'请求{method}方式不支持,或者不正确'

    58.         return json.dumps(res, ensure_ascii=False, indent=4, sort_keys=True, separators=(',',':'))
    59. </font>
    复制代码
    运行
    1. <font size="3" face="微软雅黑"># run.py
    2. import shutil
    3. import pytest
    4. import os

    5. from common.log import logger
    6. import subprocess # 通过标准库中的subprocess包来fork一个子进程,并运行一个外部的程序
    7. from common.contentsManage import htmlDir, resultDir

    8. if __name__ == '__main__':
    9.     htmlPath = htmlDir()
    10.     resultPath = resultDir()
    11.     if os.path.exists(resultPath) and os.path.isdir(resultPath):
    12.         logger.info("清理上一次执行的结果")
    13.         shutil.rmtree(resultPath, True)
    14. logger.info("开始测试")
    15.     pytest.main(["-s", "-v", "--alluredir", resultPath]) #运行输出并在resport/result目录下生成json文件
    16.     logger.info("结束测试")

    17.     # 如果是代码单独执行,需要立马看到报告,可以执行下面语句,如果配合Jenkins使用,则可以不需要执行,Jenkins自带的插件allure会操作
    18.     # logger.info("生成报告")
    19.     # subprocess.call('allure generate ' + resultPath + ' -o '+ htmlPath +' --clean', shell=True) # 读取json文件并生成html报告,--clean诺目录存在则先清楚
    20.     # logger.info("查看报告")
    21.     # subprocess.call('allure open -h 127.0.0.1 -p 9999 '+htmlPath+'', shell=True) #生成一个本地的服务并自动打开html报告
    22. </font>
    复制代码
    依赖包安装,可以执行命令pip3 install -r requirements.txt,来安装
    1. <font size="3" face="微软雅黑"># requirements.txt
    2. pytest==7.4.3
    3. pytest-html==4.1.1
    4. pytest-xdist==3.5.0
    5. pytest-ordering==0.6
    6. pytest-rerunfailures==13.0
    7. allure-pytest==2.13.2
    8. xlrd==1.2.0
    9. requests==2.31.0
    10. </font>
    复制代码
    至此,项目的代码框架就基本结束了
    6、安装并配置Jenkins
    Jenkins的安装,看你需要在Windows还是Linux下安装,具体教程可以网络查找
    Jenkins安装allure插件

    Jenkins安装并登录后,可以创建任务



    添加构建步骤,根据你安装环境的不同,选择不同的构建


    添加构建后操作,选择 allure Report

    配置代码执行的结果地址

    运行测试后,可以在任务中查看allure生成的报告


    至此,jenkins+python+pytest+requests+allure的接口自动化测试就记录到这里,刚兴趣的可以去看看pytest的官方文档,了解更多知识


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏1
    回复

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:05
  • 签到天数: 1050 天

    连续签到: 1 天

    [LV.10]测试总司令

    2#
     楼主| 发表于 2024-1-12 10:43:44 | 只看该作者
    源码链接:https://github.com/tangoliver/pytest_request_demo.git
    可以使用git clone下载
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    郁闷
    2024-4-17 11:07
  • 签到天数: 1 天

    连续签到: 1 天

    [LV.1]测试小兵

    3#
    发表于 2024-4-24 12:30:03 | 只看该作者
    版主,这个数据驱动文件代码不全吧operationExcel.py
    回复 支持 反对

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-23 02:38 , Processed in 0.077083 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表