pytest

pytest测试框架

单元测试框架定义:针对软件开发最小的单元(函数,方法)进行正确性位置测试

单元测试框架:java(junit,testing)python(unittest,pytest)

单元测试框架的作用

  1. 测试发现:从多个文件中找到测试用例
  2. 测试执行:按照一定的顺序和规则去执行并且生成结果
  3. 测试判断:通过断言判断预期结果和实际结果的差异
  4. 测试报告:统计测试的结果,耗时,通过率等生成美观的测试报告

自动化框架的内容:单元测试框架,pom设计模式,数据驱动,关键字驱动,全局配置文件的封装,日志监控,selenium和request二次封装,断言,报告邮件,更多...

pytest简介和安装

  1. pytest比unittest更灵活,容易上手
  2. pytest可以和selenium,reques,appnium结合实现web自动化,接口自动化,app自动化
  3. pytest可以实现测试用例的跳过和reruns失败用例重试
  4. pytest可以和allure生成测试报告
  5. pytest可以和jenkins持续集成
  6. pytest提供强大的插件

如下:建立requirements.txt 内容如下

pytest 本身
pytest-html 生成html格式化报告
pytest-xdist 测试用例分布式进行,多CPU分发
pytest-ordering 用于改变测试用例的执行顺序
pytest-rerunfailures 用例失败后重跑
allure-pytest 用于生成美观的测试报告

终端上输入安装:

 pip install -r requirements.txt

检验:

pytest --version

模块命名

  1. 以test_开头或者 _test结尾
  2. 测试类必须以Test开头并且不再有ini方法
  3. 测试方法必须以test开头

测试用例的运行方式

1.主函数运行方式

(1)运行所有:pytest.main()

class  TestLogin:
    def test_01_login(self):
        print("测试登录")

if __name__ == '__main__':
    pytest.main()

(2)运行指定模块:

pytest.main(['-vs','test_login.py'])

(3)指定目录:

pytest.main(['-vs','./test_other.py'])

(4)通过nodeid指定用例运行:nodeid由模块名,分隔符,类名,函数名组成

pytest.main(['-vs','./interface_testcase/test_interface.py::test_04_func'])

2.命令行模式

(1)运行所有:

pytest

(2)指定模块:

pytest -vs test_login.py

(3)指定目录:

pytest -vs ./test_other.py

(4)指定目录:

pytest -vs ./interface_testcase/test_interface.py::test_04_func

3.参数详解

-v 输出更详细的信息

-s 输出调试信息

-vs:这两个参数一起用

-n:支持多线程或者分布式运行测试用例

如:pytest -vs ./test_login.py -n 2

--returns NUM:失败用例重跑

-x:表示只要要一个用例报错,那么测试就停止

--maxfall=2 出现两个用例失败就停止

-k:根据测试用例的部分字符串指定测试用例

如:pytest -vs/testcase -k"ao"

class  TestLogin:
    def test_01_login(self):
        print("测试登录")

if __name__ == '__main__':
    pytest.main(['-s'])

案例:创建一个all.py文件运行指定的test用例和控制输出的信息

import  pytest

if __name__ == '__main__':
    pytest.main(['-vs','test_login.py'])

相当于终端输入

pytest -vs test_login.py

3.结合pytest.ini全局配置文件执行

pytest为整个pytest的核心配置文件

  1. 位置:一般放在项目的根目录

  2. 编码:必须是ANSI编码格式,可以使用notepad++修改编码格式

  3. 作用:改变pytest默认行为

  4. 运行规则:不管是主函数的模式运行,命令行模式运行,都会去读取ini文件

    1 [pytest]
    2 #命令行参数
    3 #常见:--html=./reports/report.html --reruns 2
    4 addopts = -vs -m "user_manager or smoke"
    5 #配置执行的用例位置
    6 testpaths = ./testcases
    7 #配置修改默认的模块规则
    8 python_files = test_*.py
    9 #配置修改默认的类规则
    10 python_classes = Test*
    11 #配置修改默认的用例规则
    12 python_functions = test_*
    13 #配置基础路径
    14 base_url = http://www.baidu.com
    15 #标记
    16 markers =
    17 smoke:冒烟测试用例
    18 product_manage:商品管理
    19 user_manager:用户管理
    

pytest 测试用例的顺序

区分

unittest:ascII的大小来绝对的执行顺序

pytest:默认从上到下

改变默认的执行顺序,使用mark标记

添加注释,其中1是执行的顺序

@pytest.mark.run(order=1)

如何分组执行(冒烟、分模块执行、分接口和web执行)

smoke:冒烟用例,分布在各个模块内

添加冒烟标记在.py文件

@pytest.mark.smoke

注意要在pytest.ini文件打一下标记

 #标记
 markers =
smoke:冒烟测试用例
product_manage:商品管理
user_manager:用户管理

在终端输入

pytest -vs -m "smoke"

想执行其他模块

pytest -vs -m "smoke and 其他模块的名称"

跳过执行的用例

  1. 无条件跳过

    @pytest.mark,skipif(reason='跳过原因')
    
  2. 有条件跳过

    @pytest.mark,skipif(条件,reason='跳过原因')
    

例如

@pytest.mark.skipif(age>=18,reason='已成年')

pytest框架的一些前后置(固件,夹具)处理,常用的三种

前后置:setup_class、setup、teardown、teardown_class

python 复制代码
class TestPosition:
    def setUp_class(self):
        print('\n 在每个类执行前的准备工作,例如:创建日志对象、数据库链接、接口请求对象')
    def setup(self):
        print('\n 执行用例之前的开始动作,比如打开浏览器')



    def test_01(self):
        print('\n 测试用例')

    def teardown(self):
        print('\n 执行用例之前的扫尾动作,比如关闭浏览器')
    def teardown_class(self):
        print('\n 在每个类执行前的准备工作,例如:销毁日志对象、数据库链接、接口请求对象')

使用@pytest.fixture()装饰器实现部分用例的前后置

装饰器:

@pytest.fixtrue(scope="作用范围",autouse="自动执行",params="参数化",ids="参数别

名",name="固件别名")

案例:

@pytest.fixture(scope="",params="", autouse="",ids="",name="")
def my_fixture():
    print('这是前置的方法,可以实现部分以及全部用例的前置')
    yield
    print('这是后置方法,可以实现部分以及全部用例的后置')

def test_02(self,my_fixture):
    print('\n 执行fixture')

用法:

  1. scope表示的是被@pytest.fixture标记的方法的作用域。function(默认),class,moudle,package/session

  2. params:参数化(支持,列表[],元组(),字典列表[{},{},{}],字典元组({},{},{})

    import pytest

    @pytest.fixture(scope='function',params=['参数1','参数2','参数3'])
    def my_fixture(request):
    print('前置')
    yield request.param
    # 注意yield和return都表示返回的以上,但是return后面不能跟代码,yield可以
    print('后置')

    def test_02(self,my_fixture):
    print('\n 执行fixture')

注意:params和yield不能一起用但是可以和前置一起用。

  1. autouse=True:自动化使用,默认False

  2. ids:当使用params参数化时,给每一个值设置一个变量名,意义不大

  3. name:给表示的是被@pytest.fixture标记的方法取一个别名

    当取了别名之后,那么原来的名就用不了了

通过conftest.py和@pytest.fixture()结合使用实现全局的前置应用(如:项目的全局登录,模块的全局处理)

  1. conftest.py文件是单独存放的一个夹具的配置文件,名称不能更改

  2. 用处可以在不同的py文件中使用同一个fixture函数

  3. 原则上conftest.py需要和运行的用例放在统一层,并且不需要做任何的import导入的操作

    案例

    全局拥有全局前置,各个子模块下面有一个前置,对于用例编写为

    def test_01_testconftest(self,all_fixtuture,user_fixture)
    
    print("test用例")
    
    print(user_fixture)
    
    print(all_fixture)
    

    这样全局前置包含了子模块的前置

setup/teardown,setup_class/teardown_class 它是作用于所有用例或者所有的类

@pytest.fixture() 它的作用是局部的也是全局的

conftest.py和@pytest_fixture()结合使用,作用于全局的前后置

断言

assert

pytest结合allure+pytest插件生成allure测试报告

以前是使用的是pytest_html生成,现在可以使用allure+pytest结合生成美观的报告

1.下载、解压、配置path路径

https://github.com/allure-framework/allure2/releases

配置环境变量如:

D:\allure-2.30.0\bin

验证

allure --version

2.加入命令到pytest.ini文件,生成json格式的临时报告

addopts = -vs --alluredir ./temp

3.生成allure报告

allure generate 命令,固定的

./temp 临时的json格式的报告

-o 输出output

./report 生成allure报告路径

.clear 清空./report路径原来的报告

if __name__ == '__main__':
    time.sleep(3)
    # pytest.main(['-vs','test_login.py'])
    pytest.main()
    # allure generate 命令,固定的;./temp 临时的json格式的报告 -o 输出output ./report 生成allure报告路径 .clear 清空./report路径原来的报告
    os.system('allure generate ./temp -o ./report --clear')

yaml接口自动化实战

1,断言的封装

2,allure报告的定制

3,关键字驱动和数据驱动结合实现接口自动化测试

4,python的反射

正常:先初始化对象,再调方法

反射:通过对象得到类对象,然后通过类对象调用方法

5,jenkins的持续集成和allure报告的集成,并且根据项目的自动化的报告错误率发送电子邮件

pytest接口自动化-基础篇

接口测试

发展背景:

目前市面上主流的接口测试工具:(适用于中小型项目)

  1. postman+newman+git+jenkins
  2. jmeter+ant+git+jenkins

目前主流的接口自动化测试技术:(适用于大小型的项目)

requests库

安装

requests用来发送http请求以及接受http响应的python第三方库,主要用于接口自动化测试

pip install requests

requests库常用的方法

requests.get() url是接口的地址,param用于传参

requests.post() url是接口地址,data用于传参,json也适用传参

​ data和json传参的区别:主要是通过请求头Content-Type来区分

​ Content-Type:作用是服务器要求传入的报文的内容类型

​ 请求:请求方式,请求路径,请求头,请求正文

requests.pull()

requests.delete()

requests.request() 可以发送所有类型的请求:get,post,put,delete

拓展:postman的四种传参的方式对应的Content-Type的值如下:

from-data:Content-Type:multipart/form-data、boundary=

x-www-from-urlencoded:Content-Type:applic.ation/x-www-form-urlencoded

raw:

​ text:Content-Type:text/plain

​ javascript:Content-Type:application/javascript

​ json:Content-Type:application/json

​ html:Content-Type:application/xml

binary:Content-Type:application/binary

data和json传参以及Content-Type的关系如下:

1.data传参:

​ data传参:报文时dict类型,那么默认Content-Type application/x-www-form-urlencoded

​ data传参:报文是str类型,那么默认Content-Type:text/plain

2.json传参:

​ 报文可以是dict类型,那么默认Content-Type application/json

注意:

  1. json是字典类型的字符串显示。

  2. json.loads() 把json字符串转化为dict格式,也可以传str格式(如果是嵌套字典那么就需要使用json.dumps()把嵌套字典转化为json字符串)

    json.dumps() 把dict格式转换为json字符串(包括嵌套的dict)

    不管是get,post,put,delete,都是调用的requests.request方法。而requests.reques方法调用的是session.reques方法

    method 请求方式

    url 请求路径

    params=None get方式传参

    data=None post方式传参

    json=None post方式传参

    headers=None 请求头

    cookie=None 请求cookie

    files=None 文件上传

requests模块返回的response对象详解

  1. res.json() 获取返回的字典格式的数据
  2. res.text 获得返回的字符串格式的数据
  3. res.content 获得返回的bytes字节类型的数据
  4. res_status_code 返回状态码
  5. res.reason 返回状态信息
  6. res.cookies 返回cookie信息
  7. res.headers 返回响应头
  8. res.request.xxx 返回需求的数据,如:需求头,请求参数

请求必须带有请求头的接口,以及需要cookie鉴权和session鉴权的接口

<input type="hidden" name="csrf_token" value="e7202bf558a7eb34"/>

90%以上的基于web的接口都有cookie鉴权

两种解决方式:

  1. 使用cookie关联

    案例注释:接口关联变量,在第一个接口取值,在第二个接口传值,第三个接口文件上传,访问首页的接口,解决鉴权

    import requests
    
    
    class TestRequest:
        #全局变量,类变量,通过类名调用
        access_token=""
        csrf_token=""
        php_cookie=""
    
        def test_get_token(self):
            url:"https://api.weixin.qq.com/cgi-bin/token"
            data:{
                "grant_type":"client_credential",
                "appid":"wx74a8627810cfa300",
                "secret":"e40a02f9cf9d79df497e6aaf93ab80"
            }
            res=requests.request(method="get",url=url,params=data)
            print(res.json())
            TestRequest access_token =  res.json()['access_token']
    
        # post请求:编辑标签接口
        def test_edit_flag(self):
            url:"https://api.weixin.qq.com/cgi-bin/tags/updat?access token="+TestRequest.access_token
            data=[
                "tag":
                   {
                       "id":134,
                        "name":"广东人"
                    }
            ]
            str_data=json.dumps(data)
             res=requests.request(method="post",url=url,data=str_data)
            print(res.json())
    
    
    #         文件上传
    def test_file_upload(self):
        url:"https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token="+TestRequest.access_token"+TestRequest.access_token
        data={
            "media":open(r"E:\shu.png","rb")
        }
        res=requests.request(method="post",url=url,files=data)
        print(res.json())
    
    
    # 访问首页的接口
    def test_start(self):
        url:"https://47.107.116.139/phpwind"
        res=requests.request(method="get",url=url)
        print(res.text)
    # 正则提取token
    obj=re.search('name="csrf_token" value="(.*?)"',res.text)
    # print(obj.group(1))
    TestRequest.csrf_token=obj.group(1)
    # 解决鉴权的方式一:提取cookie
    TestRequest.php_cookie=res.cookies
    
    #     登录接口
    def test_login(self):
        url:"https://47.107.116.139/phpwind/index.php?m=u&c=login&a=dorun"
        data={
            "username":"msxy",
            "password":"msxy",
            "csrf_token":TestRequest.csrf_token,
            "backurl":"http://47.107.116.139/phpwind/",
            "invite":""
        }
        # 请求头
        headers={
            "Accept":"application/json, text/javascript, /; q=0.01",
            "X-Requested-with":"XMLHttpRequest"
        }
        res=requests.request(method="post",url=url,data=data,headers=headers,cookies=TestRequest.php_cookie)
        print(res.json())
    # 解决鉴权的方式一:提取cookie
    TestRequest.php_cookie=res.cookies
    
    
    if __name__ == '__main__':
        TestRequest().test_get_token()
        TestRequest().test_edit_flag()
        TestRequest().test_file_upload()
        TestRequest().test_start()
        TestRequest().test_login()
    
  2. 使用session关联

    import requests
    
    
    class TestRequest:
        #全局变量,类变量,通过类名调用
        access_token=""
        csrf_token=""
        # php_cookie=""
        sess=requests.session()
    
        def test_get_token(self):
            url:"https://api.weixin.qq.com/cgi-bin/token"
            data:{
                "grant_type":"client_credential",
                "appid":"wx74a8627810cfa300",
                "secret":"e40a02f9cf9d79df497e6aaf93ab80"
            }
            res=requests.request(method="get",url=url,params=data)
            print(res.json())
            TestRequest access_token =  res.json()['access_token']
    
        # post请求:编辑标签接口
        def test_edit_flag(self):
            url:"https://api.weixin.qq.com/cgi-bin/tags/updat?access token="+TestRequest.access_token
            data=[
                "tag":
                   {
                       "id":134,
                        "name":"广东人"
                    }
            ]
            str_data=json.dumps(data)
            res=requests.request(method="post",url=url,data=str_data)
            print(res.json())
    
    
    #         文件上传
    def test_file_upload(self):
        url:"https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token="+TestRequest.access_token"+TestRequest.access_token
        data={
            "media":open(r"E:\shu.png","rb")
        }
        res=requests.request(method="post",url=url,files=data)
        print(res.json())
    
    
    # 访问首页的接口
    def test_start(self):
        url:"https://47.107.116.139/phpwind"
        res=TestRequest.sess.request(method="get",url=url)
    
        # print(res.text)
    # 正则提取token
    obj=re.search('name="csrf_token" value="(.*?)"',res.text)
    # print(obj.group(1))
    TestRequest.csrf_token=obj.group(1)
    
    #     登录接口
    def test_login(self):
        url:"https://47.107.116.139/phpwind/index.php?m=u&c=login&a=dorun"
        data={
            "username":"msxy",
            "password":"msxy",
            "csrf_token":TestRequest.csrf_token,
            "backurl":"http://47.107.116.139/phpwind/",
            "invite":""
        }
        # 请求头
        headers={
            "Accept":"application/json, text/javascript, /; q=0.01",
            "X-Requested-with":"XMLHttpRequest"
        }
        res=TestRequest.sess.request(method="post",url=url,data=data,headers=headers)
        print(res.json())
    
    
    
    if __name__ == '__main__':
        TestRequest().test_get_token()
        TestRequest().test_edit_flag()
        TestRequest().test_file_upload()
        TestRequest().test_start()
        TestRequest().test_login()
    

pytest接口自动化-进阶篇

目标:python+requests+pytest+allure+jenkins

接口自动化测试框架关于接口关联的封装

策略:去掉全局变量,用yaml文件代替保存下载pyyaml的包(搜搜)

import os
import yaml



# 读取
def read_yaml():
    with open(os.getcwd()+'/extract.yaml',mode='r',encoding='utf-8') as f:
        value = yaml.load(stream=f, Loader=yaml.FullLoader)
        return value


# 写入
def write_yaml(data):
    with open(os.getcwd()+'/extract.yaml', mode='a',encoding='utf-8') as f:
        yaml.dump(data, stream=f,allow_unicode=True)

# 清空
def clear_yaml(data):
    with open(os.getcwd()+'/extract.yaml', mode='w',encoding='utf-8') as f:
        f.truncate()

关于接口自动化框架之统一请求封装

如果两个测试用例要使用同一个session怎么办

  1. 通过同一个session发送请求

  2. 方便统计用例。日志封装

    import requests

    关于接口自动化框架之统一请求封装

    class RequestUtil:
    # 全局变量 类变量 通过类名调用
    sess=requests.session()

     def send_request(self, url, method, datas=None, **kwargs):
         method=str(method).lower()
         res=None
         if method=='get':
             res=RequestUtil.sess.request(method=method,url=url,params=datas,**kwargs)
         elif method=='post':
             if datas and isinstance(datas,dict):
                 datas=json.dumps(datas)
    
             res=RequestUtil.sess.request(method=method,url=url,datas=datas,**kwargs)
         else:
             pass
         return  res
    

P18接口自动化框架封装之Yaml数据驱动封装

步骤

1.将接口的数据的格式按照这种方式书写:

-
  name:
    request:
      method: get
      url: https://api.weixin.qq.com/cgi-bin/token
      data:
        grant_type: client_crediential
        appid: wx6b11b3efd1cdc290
        secret: 106a9c61571dfdf55f6255cdgs5622
  validate: None

2.在yaml_util新增一个读取测试用例的方法

# 读取测试用例的方法
def read_testcase(yaml_name):
    with open(os.getcwd()+'/testcases/'+yaml_case,mode='r',encoding='utf-8') as f:
        value = yaml.load(stream=f, Loader=yaml.FullLoader)
        return value

3.在test_api.py 通过装饰器读取字典

如:

@pytest.mark.smoke
@pytest.mark.parametrize("args_name",read_testcase('get_token.yaml'))
def test_get_token(self,args_name):
	url=args_name['request']['url']
	data=args_name['request']['data']
	method=args_name['request']['method']
	res=RequestUtil().send_request(method=method,url=url,datas=data )
相关推荐
qq_433716953 天前
Selenium+Pytest自动化测试框架 ------ 禅道实战
自动化测试·软件测试·selenium·单元测试·pytest·接口测试·压力测试
blues_C3 天前
Pytest-Bdd-Playwright 系列教程(11):场景快捷方式
自动化测试·pytest·bdd·playwright
幸运的星竹3 天前
pytest结合allure做接口自动化
自动化·pytest
blues_C4 天前
Pytest-Bdd-Playwright 系列教程(12):步骤参数 & parsers参数解析
自动化测试·pytest·bdd·playwright
胜天半月子6 天前
pytest | 框架的简单使用
运维·pytest
blues_C6 天前
Pytest-Bdd-Playwright 系列教程(10):配置功能文件路径 & 优化场景定义
自动化测试·软件测试·pytest·bdd·playwright
bigbig猩猩7 天前
pytest中的断言:深入解析与实践
pytest
CSXB997 天前
三十八、Python(pytest框架-上)
python·功能测试·测试工具·单元测试·pytest
漫漫人生路^_^8 天前
unittest和pytest
pytest
_可乐无糖8 天前
使用--log-file保存pytest的运行日志
pytest