Python自动化之pytest框架使用详解

摘要:
pytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:简单灵活,容易上手支持参数化能够支持简单的单元测试和复杂的功能测试,还可以用来做selenium/appnium等自动化测试、接口自动化测试(pytest+requests)pytest具有很多第三方插件,并且可以自定义扩展,比较好用的如pytest-selenium(集成selenium)、pytest-html(完美

pytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:

  • 简单灵活,容易上手
  • 支持参数化
  • 能够支持简单的单元测试和复杂的功能测试,还可以用来做selenium/appnium等自动化测试、接口自动化测试(pytest+requests)
  • pytest具有很多第三方插件,并且可以自定义扩展,比较好用的如pytest-selenium(集成selenium)、pytest-html(完美html测试报告生成)、pytest-rerunfailures(失败case重复执行)、pytest-xdist(多CPU分发)等
  • 测试用例的skip和xfail处理
  • 可以很好的和jenkins集成
  • report框架----allure 也支持了pytest
  1. 安装
  2.  pip install -U pytest

2.查看版本

pytest --version

3.用例编写规范

    • 测试文件以test_开头(以 _test结尾也可以)
    • 测试类以Test开头,并且不能带init方法
    • 测试函数以test_开头
    • 断言使用基本的assert即可

运行参数

  • 无参数
    • 读取路径下符合条件的所有类、函数、方法全部执行
  • -v
    • 打印详细运行日志
  • -s
    • 打印print输出
  • -k
    • 跳过运行某个或某些用例
    • pytest -k '类名'
    • pytest -k '方法名
    • pytest -k '类名 and not 方法名' #运行类里所有方法,不包含某个方法
  • -x
    • 遇到用例失败立即停止运行
  • --maxfail
    • 用例失败数达到某个设定的值停止运行
    • pytest --maxfail=[num]
  • -m
    • 运行所有@pytest.mark.[标记名] 标记的用例
框架结构

与unittest类似,执行前后会执行setup,teardown来增加用例的前置和后置条件。pytest框架使用setup,teardown更为灵活,按照用例运行级别可以分为以下几类

  • setup_module/teardown_module   模块级别,在模块始末调用
  • setup_function/teardown_function 函数级别,在函数始末调用(在类外部)
  • setup_class/teardown_class 类级别,每个类里面执行前后分别执行
  • setup_method/teardown_method 方法级别,在方法始末调用(在类中)
  • setup/teardown 方法级别,在方法始末调用(在类中)

调用顺序:setup_module>setup_class>setup_method>setup>teardown>teardown_method>teardown_class>teardown_module

for example:

#!/usr/bin/env python #encoding: utf-8 
'''@Auther:chenshifeng
@version: v1.0
@file: test_calc.py
@time: 2020/9/14 9:39 PM
'''
#测试文件
importsys, os

importpytest

sys.path.append(os.pardir)

from pythoncode.calc importCalculator

#模块级别,在模块始末调用
defsetup_module():
    print('模块级别setup')


defteardown_module():
    print('模块级别teardown')

#函数级别,在函数始末调用(在类外部)
defsetup_function():
    print('函数级别setup')


defteardown_function():
    print('函数级别teardown')


deftest_case1():
    print('testcase1')


classTestCalc:
    #setup_class,teardown_class 类级别每个类里面执行前后分别执行
    defsetup_class(self):
        self.cal =Calculator()
        print('类级别setup')

    defteardown_class(self):
        print('类级别teardown')

    #方法级别,每个方法里面的测试用例前后分别执行setup、teardown
    defsetup(self):
        #self.cal = Calculator()
        print('setup')

    defteardown(self):
        print('teardown')

    #方法级别,每个方法里面的测试用例前后分别执行setup、teardown
    defsetup_method(self):
        #self.cal = Calculator()
        print('方法级别setup')

    defteardown_method(self):
        print('方法级别teardown')


    @pytest.mark.add
    deftest_add1(self):
        #cal = Calculator()
        assert 3 == self.cal.add(1, 2)

    @pytest.mark.div
    deftest_div(self):
        #cal = Calculator()
        assert 1 == self.cal.div(1, 1)

运行结果如下

Testing started at 11:05下午 ...
/usr/local/bin/python3.6 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_calc.py
Launching pytest with arguments /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_calc.py in /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode

============================= test session starts ==============================platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 3items

test_calc.py::test_case1 模块级别setup
函数级别setup
PASSED                                          [ 33%]testcase1
函数级别teardown

test_calc.py::TestCalc::test_add1 类级别setup
方法级别setup
setup
PASSED                                 [ 66%]teardown
方法级别teardown

test_calc.py::TestCalc::test_div 方法级别setup
setup
PASSED                                  [100%]teardown
方法级别teardown
类级别teardown
模块级别teardown


============================== 3 passed in 0.02s ===============================
Process finished with exit code 0
pytest参数化Pytest是使用@pytest.mark.parametrize装饰器来实现数据驱动测试的

for example:

importpytest

@pytest.mark.parametrize('a,b,result', [
    (1, 1, 2),
    (2, 3, 5),
    (100, 200, 300)
])
deftest_add(a, b, result):
    cal =Calculator()
    assert cal.add(a, b) == result

结果:

Testing started at 11:22...
"D:Program FilesPythonpython.exe" "D:Program FilesJetBrainsPyCharm Community Edition 2020.2.1pluginspython-cehelperspycharm\_jb_pytest_runner.py" --target test_calc.py::test_add
Launching pytest with arguments test_calc.py::test_add inD:chenshifengmycodePython	est_pytest	esting

============================= test session starts =============================platform win32 -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 --D:Program FilesPythonpython.exe
cachedir: .pytest_cache
rootdir: D:chenshifengmycodePython, configfile: pytest.ini
collecting ... collected 3items

test_calc.py::test_add[1-1-2] PASSED                                     [ 33%]
test_calc.py::test_add[2-3-5] PASSED                                     [ 66%]
test_calc.py::test_add[100-200-300] PASSED                               [100%]

============================== 3 passed in 0.03s ==============================
Process finished with exit code 0

修改结果显示名称

通过上面的运行结果,我们可以看到,为了区分参数化的运行结果,在结果中都会显示数据组合而成的名称。

数据短小还好说,如果数据比较长而复杂的话,那么就会很难看。

@pytest.mark.parametrize() 还提供了第三个 ids 参数来自定义显示结果。

importpytest

#参数化
@pytest.mark.parametrize('a,b,result', [
    (1, 1, 2),
    (2, 3, 5),
    (100, 200, 300)
], ids=['int0', 'int1', 'int2'])  #修改结果显示名称
deftest_add(a, b, result):
    cal =Calculator()
    assert cal.add(a, b) == result

结果:

============================= test session starts =============================platform win32 -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 --D:Program FilesPythonpython.exe
cachedir: .pytest_cache
rootdir: D:chenshifengmycodePython, configfile: pytest.ini
collecting ... collected 3items

test_calc.py::test_add[int0] PASSED                                      [ 33%]
test_calc.py::test_add[int1] PASSED                                      [ 66%]
test_calc.py::test_add[int2] PASSED                                      [100%]

============================== 3 passed in 0.03s ==============================
Process finished with exit code 0
pytest fixtures

fixture用途

pytest fixture 与setup,teardown功能一样,但比之更加灵活,完全可以代替setup,teardown

1.做测试前后的初始化设置,如测试数据准备,链接数据库,打开浏览器等这些操作都可以使用fixture来实现

2.测试用例的前置条件可以使用fixture实现

3.支持经典的xunit fixture ,像unittest使用的setup和teardown

4.fixture可以实现unittest不能实现的功能,比如unittest中的测试用例和测试用例之间是无法传递参数和数据的,但是fixture却可以解决这个问题

fixture定义

fixture通过@pytest.fixture()装饰器装饰一个函数,那么这个函数就是一个fixture

#!/usr/bin/python#-*- coding: UTF-8 -*-
"""@author:chenshifeng
@file:test_login.py
@time:2020/09/15
"""
importpytest


@pytest.fixture()
deflogin():
    print('登陆方法')
    yield   #激活fixture teardown方法
    print('teardown')

#测试用例之前,先执行login方法
deftest_case1(login):
    print('case1')

deftest_case2():
    print('case2')

deftest_case3():
    print('case3')

运行结果如下:

test_login.py::test_case2 
test_login.py::test_case3 

============================== 3 passed in 0.02s ===============================
Process finished with exit code 0
登陆方法
PASSED                                         [ 33%]case1
teardown
PASSED                                         [ 66%]case2
PASSED                                         [100%]case3

fixture作用范围(scope)

fixture里面有个scope参数可以控制fixture的作用范围:session > module > class > function
-function每一个函数或方法都会调用,默认为function
-class 每一个类调用一次,一个类可以有多个方法
-module,每一个.py文件调用一次,该文件内又有多个function和class
-session是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module
  • cope='function'
#!/usr/bin/python#-*- coding: UTF-8 -*-
"""@author:chenshifeng
@file:test_login.py
@time:2020/09/15
"""
importpytest


@pytest.fixture()   #默认为scope='function'
deflogin():
    print('登陆方法')
    yield ['username','passwd']  #激活fixture teardown方法
    print('teardown')

#测试用例之前,先执行login方法
deftest_case1(login):
    print(f'case1 login={login}')

deftest_case2(login):
    print('case2')

deftest_case3(login):
    print('case3')

运行结果如下:

Testing started at 12:02上午 ...
/usr/local/bin/python3.6 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py
Launching pytest with arguments /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py in /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode

============================= test session starts ==============================platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 3items

test_login.py::test_case1 
test_login.py::test_case2 
test_login.py::test_case3 

============================== 3 passed in 0.02s ===============================
Process finished with exit code 0
登陆方法
PASSED                                         [ 33%]case1 login=['username', 'passwd']
teardown
登陆方法
PASSED                                         [ 66%]case2
teardown
登陆方法
PASSED                                         [100%]case3
teardown
  • scope='module'
#!/usr/bin/python#-*- coding: UTF-8 -*-
"""@author:chenshifeng
@file:test_login.py
@time:2020/09/15
"""
importpytest


@pytest.fixture(scope='module')   #默认为scope='function'
deflogin():
    print('登陆方法')
    yield ['username','passwd']  #激活fixture teardown方法
    print('teardown')

#测试用例之前,先执行login方法
deftest_case1(login):
    print(f'case1 login={login}')

deftest_case2(login):
    print('case2')

deftest_case3(login):
    print('case3')
结果:
Testing started at 12:08上午 ...
/usr/local/bin/python3.6 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py
Launching pytest with arguments /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py in /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode

============================= test session starts ==============================platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 3items

test_login.py::test_case1 
test_login.py::test_case2 
test_login.py::test_case3 

============================== 3 passed in 0.02s ===============================
Process finished with exit code 0
登陆方法
PASSED                                         [ 33%]case1 login=['username', 'passwd']
PASSED                                         [ 66%]case2
PASSED                                         [100%]case3
teardown

fixture自动调用(autouse=True)

autouse设置为True,自动调用fixture功能,无需额外继承

#!/usr/bin/python#-*- coding: UTF-8 -*-
"""@author:chenshifeng
@file:test_search.py
@time:2020/09/16
"""

importpytest


@pytest.fixture(autouse=True)  #默认为scope='function'
deflogin():
    print('登陆方法')
    yield ['username', 'passwd']  #激活fixture teardown方法
    print('teardown')
    
deftest_search1():  #无需继承login
    print('搜索用例1')

deftest_search2():
    print('搜索用例2')

结果:

============================= test session starts ==============================platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 2items

test_search.py::test_search1 登陆方法
PASSED                                      [ 50%]搜索用例1
teardown

test_search.py::test_search2 登陆方法
PASSED                                      [100%]搜索用例2
teardown


============================== 2 passed in 0.01s ===============================
Process finished with exit code 0

fixture参数化(params)

@pytest.fixture有一个params参数,接受一个列表,列表中每个数据都可以作为用例的输入。也就说有多少数据,就会形成多少用例。可以通过'''request.param'''来获取该次调用的参数
#!/usr/bin/python#-*- coding: UTF-8 -*-
"""@author:chenshifeng
@file:test_login.py
@time:2020/09/15
"""
importpytest


@pytest.fixture(params=['user1', 'user2', 'user3'])
deflogin(request):
    print('登陆方法')
    print('传入的参数为:'+request.param)  #获取params参数
    yield ['username', 'passwd']  #激活fixture teardown方法
    print('teardown')

#测试用例之前,先执行login方法
deftest_case1(login):
    print(f'case1 login={login}')

deftest_case2(login):
    print('case2')

deftest_case3(login):
    print('case3')

结果:

============================= test session starts ==============================platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 9items

test_login.py::test_case1[user1] 
test_login.py::test_case1[user2] 
test_login.py::test_case1[user3] 
test_login.py::test_case2[user1] 
test_login.py::test_case2[user2] 
test_login.py::test_case2[user3] 
test_login.py::test_case3[user1] 登陆方法
传入的参数为:user1
PASSED                                  [ 11%]case1 login=['username', 'passwd']
teardown
登陆方法
传入的参数为:user2
PASSED                                  [ 22%]case1 login=['username', 'passwd']
teardown
登陆方法
传入的参数为:user3
PASSED                                  [ 33%]case1 login=['username', 'passwd']
teardown
登陆方法
传入的参数为:user1
PASSED                                  [ 44%]case2
teardown
登陆方法
传入的参数为:user2
PASSED                                  [ 55%]case2
teardown
登陆方法
传入的参数为:user3
PASSED                                  [ 66%]case2
teardown
登陆方法
传入的参数为:user1
PASSED                                  [ 77%]case3
teardown

test_login.py::test_case3[user2] 登陆方法
传入的参数为:user2
PASSED                                  [ 88%]case3
teardown

test_login.py::test_case3[user3] 登陆方法
传入的参数为:user3
PASSED                                  [100%]case3
teardown


============================== 9 passed in 0.06s ===============================
Process finished with exit code 0

参数化与fixture结合(indirect=True)

#!/usr/bin/python#-*- coding: UTF-8 -*-
"""@author:chenshifeng
@file:test_cart.py
@time:2020/09/16
"""
importpytest

@pytest.fixture(params=['user1', 'user2', 'user3'])
deflogin(request):
    print('登陆方法')
    print('传入的参数为:' + str(request.param))  #获取params参数
    yield ['username', 'passwd']  #激活fixture teardown方法
    print('teardown')


#参数化结合fixture使用#情况一:传入值和数据#情况二:传入一个fixture方法,将数据传入到fixture方法中,fixture使用request参数来接受这组数据,在方法体中使用request.param来接受这个数据
@pytest.mark.parametrize('login', [
    ('username1', 'passwd1'),
    ('username2', 'passwd2')
], indirect=True)
deftest_cart3(login):
    print('购物车用例3')

结果:

============================= test session starts ==============================platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 2items

test_cart.py::test_cart3[login0] 
test_cart.py::test_cart3[login1] 

============================== 2 passed in 0.02s ===============================
Process finished with exit code 0
登陆方法
传入的参数为:('username1', 'passwd1')
PASSED                                  [ 50%]购物车用例3
teardown
登陆方法
传入的参数为:('username2', 'passwd2')
PASSED                                  [100%]购物车用例3
teardown
conftest.py

1.conftest.py文件名字是固定的,不可以做任何修改

2.文件和用例文件在同一个目录下,那么conftest.py作用于整个目录

3.conftest.py文件不能被其他文件导入

4.所有同目录测试文件运行前都会执行conftest.py文件

#!/usr/bin/python#-*- coding: UTF-8 -*-
"""@author:chenshifeng
@file:conftest.py
@time:2020/09/15
"""

importpytest


@pytest.fixture(params=['user1', 'user2', 'user3'])
deflogin(request):
    print('登陆方法')
    print('传入的参数为:'+str(request.param))  #获取params参数
    yield ['username', 'passwd']  #激活fixture teardown方法
    print('teardown')
#!/usr/bin/python#-*- coding: UTF-8 -*-
"""@author:chenshifeng
@file:test_login.py
@time:2020/09/15
"""


#测试用例之前,先执行login方法
importpytest


deftest_case1(login):
    print(f'case1 login={login}')

@pytest.mark.usefixtures('login')
deftest_case2():
    print('case2')
    #print(f'case1 login={login}')  #该方法无法获取返回值


deftest_case3(login):
    print('case3')

运行test_login.py文件,结果如下

============================= test session starts ==============================platform darwin -- Python 3.9.0, pytest-6.1.1, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.9cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
plugins: allure-pytest-2.8.18collecting ... collected 9items

test_login.py::test_case1[user1] 
test_login.py::test_case1[user2] 
test_login.py::test_case1[user3] 
test_login.py::test_case2[user1] 
test_login.py::test_case2[user2] 
test_login.py::test_case2[user3] 
test_login.py::test_case3[user1] 登陆方法
传入的参数为:user1
PASSED                                  [ 11%]case1 login=['username', 'passwd']
teardown
登陆方法
传入的参数为:user2
PASSED                                  [ 22%]case1 login=['username', 'passwd']
teardown
登陆方法
传入的参数为:user3
PASSED                                  [ 33%]case1 login=['username', 'passwd']
teardown
登陆方法
传入的参数为:user1
PASSED                                  [ 44%]case2
teardown
登陆方法
传入的参数为:user2
PASSED                                  [ 55%]case2
teardown
登陆方法
传入的参数为:user3
PASSED                                  [ 66%]case2
teardown
登陆方法
传入的参数为:user1
PASSED                                  [ 77%]case3
teardown

test_login.py::test_case3[user2] 登陆方法
传入的参数为:user2
PASSED                                  [ 88%]case3
teardown

test_login.py::test_case3[user3] 登陆方法
传入的参数为:user3
PASSED                                  [100%]case3
teardown


============================== 9 passed in 0.04s ===============================
Process finished with exit code 0

end

免责声明:文章转载自《Python自动化之pytest框架使用详解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇在 Ubuntu 上安装 Webminjava部署web service的方式下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

python之SQLAlchemy

ORM介绍 orm英文全称object relational mapping,就是对象映射关系程序,简单来说我们类似python这种面向对象的程序来说一切皆对象,但是我们使用的数据库却都是关系型的,为了保证一致的使用习惯,通过orm将编程语言的对象模型和数据库的关系模型建立映射关系,这样我们在使用编程语言对数据库进行操作的时候可以直接使用编程语言的对象模型...

python函数基础用法

一、函数的定义,调用和返回值   1.1 语法 def 函数(参数一,参数二...): ''' 文档注释 ''' 代码逻辑一 代码逻辑二 .... return 返回值   1.2 定义函数的三种形式     说明:定义阶段,只检测语法,不执行代码。 # 定义函数方式一:无参函数 def foo():...

精确控制windows全局音量(Python)

话不多说,直接上代码: 1 import ctypes,time 2 import comtypes 3 from ctypes import wintypes 4 5 MMDeviceApiLib = comtypes.GUID( 6 '{2FDAAFA3-7523-4F66-9957-9D5E7FE698F6}')...

如何使用python移除/删除非空文件夹?

移除/删除非空文件夹/目录的最有效方法是什么? 1.标准库参考:shutil.rmtree。 根据设计,rmtree在包含只读文件的文件夹树上失败。如果要删除文件夹,不管它是否包含只读文件,请使用 import shutil shutil.rmtree('/folder_name', ignore_errors=True) 2.从os.walk()上的p...

python线程互斥锁Lock(29)

在前一篇文章python线程创建和传参中我们介绍了关于python线程的一些简单函数使用和线程的参数传递,使用多线程可以同时执行多个任务,提高开发效率,但是在实际开发中往往我们会碰到线程同步问题,假如有这样一个场景:对全局变量累加1000000次,为了提高效率,我们可以使用多线程完成,示例代码如下: #!usr/bin/env python#-*- cod...

python 爬虫_PyQuery详细用法

总结:语法和JQuery一样 1. 初始化 from pyquery import PyQuery as pq #用pq代替pyquery doc=pq(html) print(doc('li')) #字符串的初始化 from pyquery import PyQuery as pq...