pytest框架

目录


一、简介

1.1 pytest单元测试框架

1、什么是单元测试框架:

单元测试是指在软件开发当中,针对软件的最小单位(函数,方法)进行正确性的检查测试

2、单元测试框架:

        java:junit和testing

        python:unittest和pytest

3、单元测试框架主要做什么:

1.测试发现:从多个文件里面去找到我们测试用例

2.测试执行:按照一定的顺序和规则去执行,并生成结果

3.测试判断:通过断言判断预期结果和实际结果的差异

4.测试报告:统计测试进度、耗时、通过率,生成测试报告

1.2 单元测试框架和自动化测试框架有什么关系

1、什么是自动化测试框架

自动化测试组长针对一个项目开发的一个代码框架,这个框架封装了很多的基础模块,报告模块等等。

2、作用

1.提供测试效率,降低维护成本

2.减少人工干预,提高测试的准确性,增加代码的重用性

3.核心思想是让不懂代码的人也能够通过这个框架去实现自动化测试

3、pytest单元测试框架和自动化测试框架的关系

单元测试框架:只是自动化测试框架中的组成部分之一

pom设计模式:只是自动化测试框架中的组成部分之一

数据驱动…..

关键字驱动

全局配置文件的封装

日志监控

selenium、requests二次封装

断言

报告邮件

更多……..

1.3 pytest简介

1.pytest是一非常成熟的python单元框架,比unittest更灵活,容易上手

2.pytest可以和selenium、requests、appium结合实现web自动化,接口自动化,app自动化

3.pytest可以实现测试用例的跳过以及reruns失败用例重试

4.pytest可以和allure生成非常美观的测试报告

5.pytest可以和jenkins持续集成

6.pytest有很多非常强大的插件,并且这些插件能够实现很多的实用的操作

pytest

pytest-xdist 测试用例分布式执行,多cpu并发

pytest-ordering 用于改变测试用例的执行顺序

pytest-rerunfailures 用于失败后重跑

pytest-html (生成html格式的自动化测试报告)

allure-pytest 用于生成美观的测试报告

1.4 pytest安装

1.4.1 pycharm中插件安装:

在文件中新建一个requirements.txt文件,在Terminal中输入命令pip install -r requirements.txt,会在当前文件夹下安装该插件(非全局,仅当前文件夹可用,若想全局环境使用,则在dos(cmd)窗口安装)

文件内容:

pytest

pytest-xdist

pytest-ordering

pytest-rerunfailures

pytest-html

allure-pytest

1.4.2 dos(cmd)窗口安装:

pip install pytest

pip install pytest-xdist

pip install pytest-ordering

pip install pytest-rerunfailures

pip install pytest-html pip install allure-pytest

验证是否安装成功:pytest –version

1.5 使用pytest,默认的测试用例的规则

1.模块名必须以test_开头或以_test结尾 (.py文件)

2.测试类必须以Test开头,并且不能有__init__方法(class 定义的类名)

3.测试方法必须以test开头 (def 定义的函数名)

4.默认从当前目录下收集测试用例,当配置testpath时,从配置的路径下手机测试用例

注:修改配置文件后,模块名、测试类、测试方法应该与配置文件一致

二、基本使用

2.1 创建测试用例

2.1.1简单的函数

def test_01():
    print('sede')

2.1.2 编写测试用例类

class TestHaha():
    def test_01(self):
        assert 1==1
​
    def test_02(self):
        assert 1==2

2.2 执行测试用例

pytest有多种方式来执行用例(unittest框架代码也可通过pytest运行)

2.2.1 通过pytest命令执行测试用例

1.进入要运行的目录下

2.指定运行模块:pytest -vs test_login.py test_product.py         #test_login.py和test_product.py为指定模块名

3.指定运行目录:pytest -vs interface         #interface为指定目录

4.指定运行用例:pytest -vs interface/test_interface.py::test_04_func         #interface为指定目录,test_interface.py为指定模块,test_04_func为指定方法

2.2.2 通过Python代码执行(主函数)

import pytest
if __name__ == '__main__':
    pytest.main(['-vs','test_login.py','test_product.py']) #test_login.py,test_product.py为指定运行模块名

2.2.3 参数详解

-s:表示输出调试信息,包括print打印的信息

import pytest
if __name__ == '__main__':
    pytest.main(['-s']) #主函数模式
​
pytest -s #命令行模式

-v:显示更详细的信息

import pytest
if __name__ == '__main__':
    pytest.main(['-v']) #主函数模式
​
pytest -v #命令行模式

-vs:v和s两个参数一起用

import pytest
if __name__ == '__main__':
    pytest.main(['-vs']) #主函数模式
​
pytest -vs #命令行模式

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

import pytest
if __name__ == '__main__':
    pytest.main(['-vs','-n=2']) #主函数模式
​
pytest -vs -n 2  #命令行模式

–reruns NUM:失败用例重跑,NUM为重跑次数

import pytest
if __name__ == '__main__':
    pytest.main(['-vs','--reruns=2']) #主函数模式
​
pytest --reruns 2 #命令行模式

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

import pytest
if __name__ == '__main__':
    pytest.main(['-vs','-x']) #主函数模式
​
pytest -x #命令行模式

–mainfaill=2 :出现两个用例失败就停止

import pytest
if __name__ == '__main__':
    pytest.main(['-vs','--maxfail=2'])#主函数模式
​
pytest --maxfail 2 #命令行模式

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

import pytest
if __name__ == '__main__':
    pytest.main(['-vs','-k=baili'])#主函数模式,若函数名包含baili则执行该用例
    
import pytest
if __name__ == '__main__':
    pytest.main(['-vs','-k=not baili'])#主函数模式,若函数名包含baili则执行该用例    
  
​
pytest -k 'baili'#命令行模式
pytest -k 'not baili'#命令行模式,不包含baili则执行

–html ./report/report.html:生成html的测试报告

在项目根目录下新建一个report包用于存储报告

import pytest
if __name__ == '__main__':
    pytest.main(['-vs','--html=./report/report.html'])  #主函数模式
​
#命令行模式
pytest --html ./report/report.html

更多具体参数信息参考官方文档:pytest:帮助你编写更好的程序 — pytest 文档

2.2.4 执行顺序

在模块级别采用模块名的ascii码顺序,在模块内部根据从上往下的定义顺序来执行。

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

class TestInterface:
    def test_03_interface(self):
        print('test03')
​
    @pytest.mark.run(order=2) #定义运行顺序
    def test_05_interface(self):
        print('test-05')
​
    @pytest.mark.run(order=1) #定义运行顺序
    def test_06_interface(self):
        print('test-06')
​
    def test_07_interface(self):
        print('test-07')
#执行顺序为6-5-3-7

2.3 断言

在pytest中只需使用python语言标准的断言语句 assert 来断言。

2.4 前后置断言

2.4.1 经典的xunit风格

xunit是一种经典且流行的方式,该方式可以基于模块/类/函数实现固定装置(设置和拆卸测试状态)。

1.模块级别的setup_module和teardown_module:

在整个模块调用前后执行一次:

def setup_module():
    '''
    模块级前置条件
    :return:
    '''
    print('我会在当前模块所有测试执行之前执行')
​
def teardown_module():
    '''
    模块级后置条件
    :return:
    '''
    print('我会在当前模块所有测试执行之后执行')
​
​
​
def test_01():
    print('sede')
​
class TestHaha():
    def test_01(self):
        print(234)
​
    def test_02(self):
        print(456)

2.类级别setup_class和teardown_class

在整个类调用前后执行一次:

class TestHaha():
​
    @classmethod
    def setup_class(cls):
        '''
        类级前置条件
        :return:
        '''
        print('我会在当前类所有测试执行之前执行')
​
    @classmethod
    def teardown_class(cls):
        '''
        类级后置条件
        :return:
        '''
        print('我会在当前类所有测试执行之后执行')
​
    def test_01(self):
        print(234)
​
    def test_02(self):
        print(456)

3.方法级别setup_method和teardown_method

在整个方法调用前后执行一次:

class TestHaha():
​
    @classmethod
    def setup_method(self):
        '''
        方法级前置条件
        :return:
        '''
        print('我会在当前类里每个测试方法执行之前执行')
​
    @classmethod
    def teardown_method(self):
        '''
        方法级后置条件
        :return:
        '''
        print('我会在当前类类每个测试方法执行之后执行')
​
    def test_01(self):
        print(234)
​
    def test_02(self):
        print(456)

4.方法级别setup_function和teardown_function

在模块每个函数调用前后执行一次:

def setup_function():
    '''
    函数级别的前置
    :return:
    '''
    print('我会在当前模块在每个测试函数执行之前执行')
​
def teardown_function():
    '''
    函数级别的后置
    :return:
    '''
    print('我会在当前模块在每个测试函数执行之后执行')
​
​
​
​
def test_01():
    print('sede')
​
​
def test_02():
    print('sdjf')

2.4.2 unittest风格

pytest支持unittest的夹具风格(可通过pytest的执行方式执行)

import unittest
​
class TestMay(unittest.TestCase):
​
    @classmethod
    def setUpClass(cls) -> None:
        '''
        类前置条件
        :return:
        '''
        print('我会在当前类所有测试执行之前执行')
​
    @classmethod
    def tearDownClass(cls) -> None:
        '''
        类后置条件
        :return:
        '''
        print('我会在当前类所有测试执行之后执行')
​
    def setUp(self) -> None:
        '''
        方法前置条件
        :return:
        '''
        print('会在当前类里所有方法测试执行之前执行')
​
    def tearDown(self) -> None:
        '''
        方法后置条件
        :return:
        '''
        print('会在当前类里所有方法测试执行之后执行')
​
    def test_one(self):
        print('test')
        self.assertEqual(1,1)
​
    def test_two(self):
        print('cde')

2.4.3 @pytest.fixture

pytest框架有一种通过装饰器实现的夹具机制

1.定义:

通过@pytest.fixture可以定义夹具

import pytest
​
@pytest.fixture
def fixture_func():   # 不能随便接受参数
    print('pytest.fixture的一个前置条件')
    yield  '托尔斯泰'  #yield所在行为返回值,yield上面的代码都是前置,后面的代码都是后置
    print('pytest.fixture的一个后置条件')

2.调用夹具:

1.通过装饰@pytest.mark.usefixtures(‘fixture_func’)

# 使用装饰器语法调用夹具
​
# 修饰函数
@pytest.mark.usefixtures('fixture_func')
def test_function():
    print('我是一个测试函数')
    
​
# 修饰整个类
@pytest.mark.usefixtures('fixture_func')
class TestSome():
​
    def test_one(self):
        print('one')
​
    def test_two(self):
        print('two')

2.通过在测试函数中定义与夹具函数名同名的参数

# 使用装饰器语法调用夹具
@pytest.mark.usefixtures('fixture_func')
def test_function():
    print('我是一个测试函数')

这种方式还可以接收夹具的返回值(夹具的返回值定义在yield所在行)

import pytest
​
@pytest.fixture
def fixture_func():   # 不能随便接受参数
    print('pytest.fixture的一个前置条件')
    yield   'test' #test为夹具的返回值
    print('pytest.fixture的一个后置条件')
​
​
# 使用函数传参的方法来调用前置后置
def test_two(fixture_func):
    print(fixture_func) #通过函数名调用夹具的返回值
    print(12345)
​
# 在类中使用夹具返回值
class TestSome():
​
    def test_one(self,fixture_func):
        print('one')
​
    def test_two(self):
        print('two')
@pytest.fixture
def db():
    import pymysql
    with pymysql.connnect() as conn:
        yield conn   #conn为yield返回值
        
        
@pytest.mark.usefixtures("fixture_func")
def testa(db):
    cursor = db.cursor()   #通过函数名调用夹具的返回值
    cursor.execute('select * from student')
    assert cursor.fetch_one()

3.夹具的作用范围

通过@pytest.fixture装饰器的参数scope可以指定夹具的作用范围

function 默认范围,函数范围,在测试完成后结束

class 在类中最后一共测试完成后结束

module 在模块中最后一个测试完成后结束

package 在包中的最后一个测试完成后结束

session 在一次会话中的最后一共测试完成后结束

修饰类:

import pytest
​
@pytest.mark.usefixtures('class_fixture')
class TestSome():
​
    def test_one(self):
        print('one')
​
    def test_two(self):
        print('two')
​
# 如果使用scope='class'修饰类,在整个类执行前开始,在整个类所有测试执行完之后结束        
@pytest.fixture(scope='class')
def class_fixture():
    print('我是一个类级前置')
    yield
    print('我是一个类级后置')
    
   
# 其他scope参数值作用范围同理

修饰模块:

# 模块级前后置,传入参数autouse=True运行时自动调用
​
@pytest.fixture(scope='module',autouse=True)
def moudle_fixture():
    print('我是一个模块级前置')
    yield
    print('我是一个模块级后置')

修饰包:

在测试用例包下新建conftest.py,内容如下:

import pytest
​
# 设置autouse=True,当pytest执行整个测试用例包内容时,自动执行
@pytest.fixture(scope='package',autouse=True)
def package_fixture():
    print('我在整个包测试开始之前执行')
    yield
    print('我在整个包测试执行完成之后执行')

修饰会话:

在测试用例包下新建conftest.py,内容如下:

import pytest
​
# 设置autouse=True,当pytest执行整个测试用例包内容时,自动执行
@pytest.fixture(scope='session',autouse=True)
def package_fixture():
    print('我在整个测试执行前执行')
    yield
    print('我在整个测试执行后执行')

4.共享夹具

如果一个夹具需要被多个文件使用,则将其定义在conftest.py文件中,通过函数名调用,不需要在测试中倒入,pytest框架会自动发现并执行。

在同一目录下,新增conftest.py和test_01.py文件(在执行时先在当前目录下查找conftest.py中内容,若不存在则查找上层目录、上上层中conftest.py,若不存在,则报错)。

conftest.py文件内容如下:

import pytest
​
@pytest.fixture(scope='class')
def class_fixture():
    print('我是一个类级前置')
    yield
    print('我是一个类级后置')

test_01.py文件内容如下:

import pytest
​
@pytest.mark.usefixtures('class_fixture')
class TestSome():
​
    def test_one(self):
        print('one')
​
    def test_two(self):
        print('two')

pytest运行test_01.py会自动运行执行引用的夹具

5.夹具的多层使用

import pytest
​
@pytest.fixture
def fixture1():
    print('>1111111')
    yield
    print('1111111<')
​
@pytest.fixture
def fixture2():
    print('>222222')
    yield
    print('222222<')
​
@pytest.fixture(scope='module')
def module_fixture():
    print('模块前置')
    yield
    print('模块后置')
​
# 如果有多重夹具,先执行作用范围大的
# 当作用范围相同时,执行先修饰的(参数的传入顺序)
def test_func(fixture1,fixture2,module_fixture):
    print('test_func')

6.夹具的继承

除了可以在测试函数中使用夹具外,夹具功能还可以使用其他的夹具。这有助于夹具的模块化设计,并允许在许多项目中重复使用特定的夹具。

注意夹具的使用只能是使用范围更广的夹具,反过来不行。模块级别的夹具不能使用类级别的夹具。

import pytest
@pytest.fixture
def fixture1():
    print('》11111')
    yield 1
    print('11111《')
​
# 夹具在继承的时候,被继承的夹具范围要大于等于继承夹具
@pytest.fixture
def fixture2(fixture1):   # 需要fixture1的结果
    fixture1 += 1
    print('》22222')
    yield fixture1
    print('22222《')
​
def test_func(fixture2):
    print(fixture2)
    print('test_func')

 

2.5 参数化

2.5.1 写法一

import pytest
​
@pytest.fixture(params=['avs','vsd','cd3'])
def haha(request):
    print('这是前置的方法')
    yield request.param   #return和yield都表示返回的意思,但是return的后面不能有代码,yield返回后后面可以接代码
    print('这是后置的方法')
​
class TestInterface:
​
    def test_03_interface(self):
        print('test03')
​
    def test_05_interface(self,haha):    #调用了haha方法,会将haha中传入的每个参数执行一次,一共执行三次
        print('test-05')
        print(str(haha))

参数传递过程:在@pytest.fixture(params=[‘avs’,’vsd’,’cd3′])中进行定义,通过request传入haha,request.param获取单个参数值,参数值通过调用函数时haha传入,此时haha=单个函数值

params=[‘avs’,’vsd’,’cd3′]这里params是参数名,有s

request.param这里是属性名,是没有s的

注:return和yield都表示返回的意思,但是return的后面不能有代码,yield返回后后面可以接代码

2.5.2 写法二

import pytest
​
test_data=[
    {
        'num':1,
        'expect_data':1
    },
    {
        'num':-1,
        'expect_data':1
    },
    {
        'num':0,
        'expect_data':0
    }
]
​
# 将test_data解包为多个case
@pytest.mark.parametrize('case',test_data)
def test_abs(case):
    assert abs(case['num'])==case['expect_data']
import pytest
​
test_data2=[
    [1,1],
    [-1,1],
    [0,0]
]
​
# 将test_data2解包为多个数据,然后再次解包并赋值
@pytest.mark.parametrize('num,expect',test_data2)
def test_abs2(num,expect):
    assert abs(num)==expect

2.6 夹具别名

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

import pytest
​
@pytest.fixture(name='aaa')
def haha():
    print('这是前置的方法')
    yield
    print('这是后置的方法')
​
class TestInterface:
​
    def test_03_interface(self):
        print('test03')
​
    def test_05_interface(self,aaa):  #通过别名调用
        print('test-05')   

取了别名之后,函数原本的名称不可用

三、生成报告

3.1 生成测试报告

pytest生成测试报告需要安装插件,请按插件说明来操作官方文档

3.2 生成allure报告

allure是一个专门生成测试报告的框架,支持多种语言和测试框架。官方文档

3.2.1 下载、解压、配置path路径

allure下载地址

解压后运行bin目录下allure.bat文件

配置path路径:【计算机–属性–高级系统设置–环境变量–系统变量–path–编辑】(E:\test ruanjian\allure-commandline-2.14.0\allure-2.14.0\bin)

验证:打开dos窗口,输入命令allure –version

问题:dos可以验证但是pycharm验证失败,则重启pycharm

3.2.2 加入命令生产json格式的临时报告

在pytest命令中带上参数 –alluredir ,指定报告生成的路径

–alluredir ./temp

import pytest
if __name__ == '__main__':
    pytest.main(['-vs','--alluredir=./temp/my_allure_results'])   #主函数模式
    
pytest --alluredir ./temp/my_allure_results   #命令行模式
#也可在配置文件中进行修改

3.2.3 生成allure报告

查看报告需要通过命令行启动allure服务

allure serve ./tmp/my_allure_results

os.system('allure generate ./temp/my_allure_results -o ./report --clean')

allure generate #命令,固定的

./temp #临时的json格式报告的路径

-o #输出output

./report #生成的allure报告的路径

–clean #清空./report路径原来的报告

四、pytest.ini配置文件

pytest.ini这个文件是pytest单元测试框架的核心配置文件(配置文件中,因编码格式问题,需删除注释)

4.1存放位置

一般放在项目的根目录

4.2 编码格式

必须是ANSI,可以使用notpad++修改编码格式,或者新建txt文本,后另存文件为ANSI编码,修改文件名称

4.3 作用

改变pytest默认的行为/规则

4.4 规则明细

不管是主函数的模式运行,命令行模式运行,都会去读取这个文件

[pytest]
addopts=-vs                 #命令行的参数,可以输入多个,用空格分隔,主函数和命令行模式处直接执行
testpaths=./testcase        #测试用例的路径
python_files=test_*.py      #配置测试搜索模块名的规则
python_classes=Test         #配置测试搜索类名的规则
python_functions=test       #配置测试搜索方法名的规则(函数)
markers=                    #标记的模块
    smoke:冒烟用例
    uermanage:用户管理模块
    productmanage:商品管理模块

五、分组执行(冒烟、分模块执行,分接口和web执行)

1.在pytest.ini文件中配置标记的模块

 2.在用例处插入标记

3.通过主函数模式或命令行模式调用mark实现分组执行

#主函数模式
import pytest
if __name__ == '__main__':
    pytest.main(['-vs','-m=smoke'])
    
import pytest
if __name__ == '__main__':
    pytest.main(['-vs','-m=smoke or product'])
    
#命令行模式
pytest -m 'smoke'
pytest -m 'smoke or product'

六、pytets跳过用例

6.1 无条件跳过

无条件跳过,定义用例

@pytest.mark.skip(reason='跳过原因,可不输入')

6.2 规定条件跳过

有条件跳过,定义用例

@pytest.mark.skipif(age>18,reason='跳过原因') #age>18为筛选条件,满足则跳过

6.3 运行

运行:

import pytest
if __name__ == '__main__':
    pytest.main()  #主函数
​
pytest #命令行

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2023年11月8日
下一篇 2023年11月8日

相关推荐