Python pytest单元测试框架常见使用方法

    科技2025-12-02  15

    摘要1:https://blog.csdn.net/yxxxiao/article/details/94602614?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param#%E4%BA%8C%E3%80%81%E6%96%AD%E8%A8%80

    摘要2:https://blog.csdn.net/yxxxiao/article/details/94591174#%E5%9B%9B%E3%80%81pytest%20%E7%94%A8%E4%BE%8B%E8%A7%84%E5%88%99

    摘要3:https://www.jianshu.com/p/a754e3d47671

     

    一、pytest 断言

    断言是判断实际结果与预期结果的重要方法。pytest除了支持正常情况的断言,还支持异常断言。

    1、正常断言

    正常的断言在上一篇博客中已经有所体现,pytest使用最基本的python中的assert语句进行断言,下面我们再举一个例子

    # content of test_assert1.py def f(): return 3 def test_function(): assert f() == 4

     执行上面测试:

    $ py.test test_assert1.py =========================== test session starts ============================ platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 rootdir: /tmp/doc-exec-87, inifile: collected 1 items test_assert1.py F ================================= FAILURES ================================= ______________________________ test_function _______________________________ def test_function(): > assert f() == 4 E assert 3 == 4 E + where 3 = f() test_assert1.py:5: AssertionError ========================= 1 failed in 0.01 seconds =========================

    我们发现,该测试失败了。而且pytest帮我们打印出来了中间表达式f()的结果,这样我们就可以非常清晰的知道该测试为什么失败。但是,如果你像下面这样写assert语句,则我们得不到assert表达式中的子表达是的中间结果:

    assert a % 2 == 0, "value was odd, should be even"

    所以,我们在写assert语句的时候,子表达式最好是一个函数签名,这样我们就可以得到该函数的计算结果,以便我们知道测试为什么失败。我们将上面的语句改成下面这样就可以了:

    value = a % 2 assert value == 0, "value was odd, should be even"

    2、异常断言

    有些时候我们会对某些异常写断言语句,例如我们断言1除以0,将产生一个ZeroDivisionError类型的异常。针对这样的断言,pytest给我们提供了pytest.raise方法:

    有的时候,我们可能需要在测试中用到产生的异常中的某些信息,比如异常的类型type,异常的值value等等。下面我们修改下上面的测试:

    import pytest def test_recursion_depth(): with pytest.raises(ZeroDivisionError) as excinfo: 1/0 assert excinfo.type == 'RuntimeError'

    这个测试中,我们使用了测试的异常类型:excinfo.type。执行这个测试:

    C:\Users\liu.chunming\Desktop>py.test idlist.py ============================= test session starts ============================= platform win32 -- Python 2.7.10 -- py-1.4.28 -- pytest-2.7.1 rootdir: C:\Users\liu.chunming\Desktop, inifile: plugins: capturelog, instafail, pythonpath collected 1 items idlist.py F ================================== FAILURES =================================== ____________________________ test_recursion_depth _____________________________ def test_recursion_depth(): with pytest.raises(ZeroDivisionError) as excinfo: 1/0 > assert excinfo.type == 'RuntimeError' E assert <type 'exceptions.ZeroDivisionError'> == 'RuntimeError' E + where <type 'exceptions.ZeroDivisionError'> = <ExceptionInfo ZeroDivisi onError tblen=1>.type idlist.py:5: AssertionError ========================== 1 failed in 0.05 seconds ==========================

    因为该测试断言产生的异常类型是RuntimeError,而实际上产生的异常类型是ZeroDivisionError,所以测试失败了。在测试结果中,可以看到assert子表达式excinfo.type的值。

    二、pytest fixture

    pytest支持以xUnit格式型的测试模型(setup/teardown),但还与python自带的unittest还是有一点差别,如下

    模块形式----使用setup_module/teardown_module  函数形式----使用setup_function/teardown_function类形式----使用setup_class/teardown_class方法形式---使用setup_method/teardown_method

    注意:

    pytest也可以直接运行unittest模式的测试用例

    如果你在pytest模式中使用setupClass()函数是不行的,不会识别,但如果用例类继承之unittest.Testcase,还是可以识别的

    1、fixture scope的范围参数

    之前使用@pytest.fixture(scope='module')来定义框架,scope的参数有以下几种

     function   每一个用例都执行class        每个类执行module     每个模块执行(函数形式的用例)session     每个session只运行一次,在自动化测试时,登录步骤可以使用该session

    2、调用fixture的三种方法

    2.1函数或类里面方法直接传fixture的函数参数名称

    from __future__ import print_function import pytest @pytest.fixture(scope='module') def resource_a_setup(request): print('\nresources_a_setup()') def resource_a_teardown(): print('\nresources_a_teardown()') request.addfinalizer(resource_a_teardown) def test_1(resource_a_setup): print('test_1()') def test_2(): print('\ntest_2()') def test_3(resource_a_setup): print('\ntest_3()') 使用-s -v运行查看详情如下

    2.2使用装饰器@pytest.mark.usefixtures()修饰需要运行的用例

    import pytest # test_fixture1.py @pytest.fixture() def test1(): print('\n开始执行function') @pytest.mark.usefixtures('test1') def test_a(): print('---用例a执行---') @pytest.mark.usefixtures('test1') class TestCase: def test_b(self): print('---用例b执行---') def test_c(self): print('---用例c执行---') if __name__ == '__main__': pytest.main(['-s', 'test_fixture1.py']) 输出结果: platform win32 -- Python 3.7.0, pytest-4.0.2, py-1.7.0, pluggy-0.8.0 rootdir: C:\Program Files\PycharmProjects\exercise, inifile:collected 3 items test_fixture1.py 开始执行function .---用例a执行--- 开始执行function .---用例b执行--- 开始执行function .---用例c执行--- [100%] ========================== 3 passed in 0.06 seconds =========================== Process finished with exit code 0

    2.3叠加usefixtures

    如果一个方法或者一个class用例想要同时调用多个fixture,可以使用@pytest.mark.usefixture()进行叠加。注意叠加顺序,先执行的放底层,后执行的放上层

    import pytest # test_fixture1.py @pytest.fixture() def test1(): print('\n开始执行function1') @pytest.fixture() def test2(): print('\n开始执行function2') @pytest.mark.usefixtures('test1') @pytest.mark.usefixtures('test2') def test_a(): print('---用例a执行---') @pytest.mark.usefixtures('test2') @pytest.mark.usefixtures('test1') class TestCase: def test_b(self): print('---用例b执行---') def test_c(self): print('---用例c执行---') if __name__ == '__main__': pytest.main(['-s', 'test_fixture1.py']) 输出结果: ============================= test session starts ============================= platform win32 -- Python 3.7.0, pytest-4.0.2, py-1.7.0, pluggy-0.8.0 rootdir: C:\Program Files\PycharmProjects\exercise, inifile:collected 3 items test_fixture1.py 开始执行function2 开始执行function1 .---用例a执行--- 开始执行function1 开始执行function2 .---用例b执行--- 开始执行function1 开始执行function2 .---用例c执行--- [100%] ========================== 3 passed in 0.03 seconds =========================== Process finished with exit code 0

    3.usefixtures与传fixture区别

     如果fixture有返回值,那么usefixture就无法获取到返回值,这个是装饰器usefixture与用例直接传fixture参数的区别。

    当fixture需要用到return出来的参数时,只能讲参数名称直接当参数传入,不需要用到return出来的参数时,两种方式都可以。

    4.fixture自动使用autouse=True

    当用例很多的时候,每次都传这个参数,会很麻烦。fixture里面有个参数autouse,默认是False没开启的,可以设置为True开启自动使用fixture功能,这样用例就不用每次都去传参了

    autouse设置为True,自动调用fixture功能

    import pytest # test_fixture1.py @pytest.fixture(scope='module', autouse=True) def test1(): print('\n开始执行module') @pytest.fixture(scope='class', autouse=True) def test2(): print('\n开始执行class') @pytest.fixture(scope='function', autouse=True) def test3(): print('\n开始执行function') def test_a(): print('---用例a执行---') def test_d(): print('---用例d执行---') class TestCase: def test_b(self): print('---用例b执行---') def test_c(self): print('---用例c执行---') if __name__ == '__main__': pytest.main(['-s', 'test_fixture1.py']) 输出结果: ============================= test session starts ============================= platform win32 -- Python 3.7.0, pytest-4.0.2, py-1.7.0, pluggy-0.8.0 rootdir: C:\Program Files\PycharmProjects\exercise, inifile:collected 4 items test_fixture1.py 开始执行module 开始执行class 开始执行function .---用例a执行--- 开始执行class 开始执行function .---用例d执行--- 开始执行class 开始执行function .---用例b执行--- 开始执行function .---用例c执行---

    三、conftest.py的作用范围

    一个工程下可以建多个conftest.py的文件,一般在工程根目录下设置的conftest文件起到全局作用。在不同子目录下也可以放conftest.py的文件,作用范围只能在改层级以及以下目录生效。

    目录结构:

    1. conftest在不同的层级间的作用域不一样

    # conftest层级展示/conftest.py import pytest @pytest.fixture(scope='session', autouse=True) def login(): print('----准备登录----') # conftest层级展示/sougou_login/conftest import pytest @pytest.fixture(scope='session', autouse=True) def bai_du(): print('-----登录百度页面-----') # conftest层级展示/sougou_login/login_website import pytest class TestCase: def test_login(self): print('hhh,成功登录百度') if __name__ == '__main__': pytest.main(['-s', 'login_website.py']) 输出结果: ============================= test session starts ============================= platform win32 -- Python 3.7.0, pytest-4.0.2, py-1.7.0, pluggy-0.8.0 rootdir: C:\Program Files\PycharmProjects\conftest层级演示\sougou_login, inifile:collected 1 item login_website.py ----准备登录---- -----登录百度页面----- .hhh,成功登录百度 [100%] ========================== 1 passed in 0.03 seconds =========================== Process finished with exit code 0

    2. conftest是不能跨模块调用的(这里没有使用模块调用)

    # conftest层级演示/log/contfest.py import pytest @pytest.fixture(scope='function', autouse=True) def log_web(): print('打印页面日志成功') # conftest层级演示/log/log_website.py import pytest def test_web(): print('hhh,成功一次打印日志') def test_web1(): print('hhh,成功两次打印日志') if __name__ == '__main__': pytest.main(['-s', 'log_website.py']) 输出结果: ============================= test session starts ============================= platform win32 -- Python 3.7.0, pytest-4.0.2, py-1.7.0, pluggy-0.8.0 rootdir: C:\Program Files\PycharmProjects\conftest层级演示\log, inifile: collected 2 items log_website.py ----准备登录---- 打印页面日志成功 hhh,成功一次打印日志 .打印页面日志成功 hhh,成功两次打印日志 . ========================== 2 passed in 0.02 seconds ===========================

    四、pytest 命令行传参

    命令行参数是根据命令行选项将不同的值传递给测试函数,比如平常在cmd执行"pytest --html=report.html",这里面的”--html=report.html“就是从命令行传入的参数对应的参数名称是html,参数值是report.html,

    1. 总结:

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

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

    3.conftest.py文件所在目录必须存在__init__.py文件

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

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

    2. 步骤:

    2.1 首先需要在contetest.py添加命令行选项,命令行传入参数”—cmdopt“, 用例如果需要用到从命令行传入的参数,就调用cmdopt函数:

    # content of conftest.py import pytest def pytest_addoption(parser): parser.addoption( "--cmdopt", action="store", default="type1", help="my option: type1 or type2" ) @pytest.fixture def cmdopt(request): return request.config.getoption("--cmdopt")

    2.2 测试用例编写案例

    # content of test_sample.py import pytest def test_answer(cmdopt): if cmdopt == "type1": print("first") elif cmdopt == "type2": print("second") assert 0 # to see what was printed if __name__ == "__main__": pytest.main(["-s", "test_case1.py"])

    2.3 运行结果:

    3. 带参数启动

    如果不带参数执行,那么传默认的default=”type1”,接下来在命令行带上参数去执行

    $ pytest -s test_sample.py --cmdopt=type2

     

     

    Processed: 0.018, SQL: 9