pytest

hkt 发布于 2025-07-30 48 次阅读


单元测试框架(Unit Testing Framework)是一种为开发者提供的工具或库,用于自动化编写、运行和管理单元测试。它提供了一套标准化的结构和工具,帮助开发者高效验证代码的最小可测试单元(通常是函数、方法或类)的正确性。pytest是基于unittest的衍生产物,和unittest一样都是当前主流的python单元测试框架。三大主流python单元测试框架:unittest、pytest、nose。主流的java测试单元测试框架:junit、testng。

特点:

  1. 简单易学;
  2. 可以实现函数测试用例的和方法测试用例;
  3. pytest在标识符满足pytest的规则的前提下,就可以自动实现测试模块、测试类、测试用例的识别;
  4. pytest框架兼容python中的assert语句,可直接使用assert语句实现断言;
  5. 通过pytest的fixture装饰器,可以实现自定义固件(脚手架),来实现对象初始化、资源管理、参数化等操作;
  6. pytest框架可以兼容运行unittest、nose等框架;
  7. pytest支持丰富的插件。

pytest标识符命名规范

在pytest框架下,只要标识符的命名满足以下规范,就可以实现测试用例的自动识别:

  • 模块的命名要以test_开头或者_test.py结尾;
  • 类的命名要以Test开头;
  • 测试用例的命名要以test开头。
pytest是第三方的单元测试框架。pip install pytest
# 举例:
# 写一个函数测试用例
def test_1():
    print("我是测试用例1")

# 写一个方法测试用例
class TestClass:
    def test_2(self):
         print("我是测试用例2")

设置以pytest运行测试用例的方法

在File->Settings->Tools->Python Integrated Tools选择Default test runner为pytest,默认为unittest; 如果上述步骤不生效,可以通过配置当前运行文件指定模块或者测试用例以pytest框架进行运行,方法:Edit Configuration->Add New Configuration->Python tests->pytest->选择模块; 如果上述步骤还是不生效,可能是在pycharm记录了上一次的运行缓存,可以在Edit Configuration页面删除缓存记录; 如果以上方法都不生效,可以新建一个py文件,把代码拷到新建的文件中运行。

Pytest的用例执行:

  • 执行的顺序为:谁在前谁先执行
  • pytest.main()执行当前模块中的所有用例。

在dos下以pytest框架运行测试脚本

pytest 用例模块  【python -m pytest 用例模块】
参数:
            -v:展现用例执行的详细结果
            -s:展示用例的输出语句内容
            -x: 执行的用例脚本中发生错误,立即中断执行
            --maxfail=n: 行的用例脚本中可以发生n次错误
        指定运行某条用例:
            pytest [参数] 用例模块名称::用例函数名
            pytest [参数] 用例模块名称::用例类名::用例方法名
        . : 执行成功-passed
        E : 产生错误-error
        F : 断言失败-failed

pytest断言

在pytest中直接用assert语句来实现断言,pytest中断言失败之后,后续的用例会执行。
不像unittest那样需要继承assert方法来断言。常用的用法:

  • assert 实际结果==预期结果
  • assert 元素 in 可迭代对象
  • assert 对象 is 对象 # 比较的是两个对象是否是同一个对象(内存地址是否相同)
#举例
def test_1():
    assert 1+1==2 # 断言预期是否等于实际,相当于unittest的assertEqual
    assert 1+1!=3 # 断言预期是否不等于实际,相当于unittest的assertNotEqual
    assert 'a' in ['a','b'] # 断言预期是否在实际中,相当于unittest的assertIn
    assert 'c' not in ['a','b'] # 断言预期是否不在实际中,相当于unittest的assertNotIn
    assert 'hello' # 断言一个bool表达式
    assert 'hello' is 'hello' # 断言两个对象是否是同一个对象

pytest的固件

固件:用例执行之前和执行之后执行的动作

setup: 用例执行之前的动作
teardown: 用例执行之后的动作

pytest自带有四对固件:

  • 模块级别:setup_module/teardown_module--> 用例模块在执行之前和执行之后执行的动作
  • 函数级别:setup_function/teardown_function--> 每个用例函数在执行之前和执行之后执行的动作
  • 类级别:setup_class/teardown_class--> 用例类在执行之前和执行之后执行的动作--必须定义在类中
  • 方法级别:setup_method/teardown_method--> 每个用例方法在执行之前和执行之后执行的动作--必须定义在类中

函数用例执行时,固件的执行的顺序为:

方法用例执行时,固件的执行的顺序为:

注意:用例类中不能存在构造方法。用例类中是可以存在析构方法,但是一般不定义。pytest在较低的版本中会存在setup/teardown的固件,他的级别和setup_method/teardown_method一样的。如果setup_method/teardown_method存在,会覆盖setup/teardown的功能。

自定义固件

通过装饰器@pytest.fixture来装饰一个函数,这个函数就变成了一个固件函数对象,可以在测试用例的函数或者方法中以参数的形式传入固件函数对象来实现自定义固件的调用。 通过@pytest.fixture装饰器一方面可以实现自定义固件,另外也可以实现固件的参数化,以及设置固件的作用域等。

装饰器:在不改变源代码的情况下,丰富被装饰对象的功能。

import pytest

# 声明自定义固件,方法是使用pytest.fixture装饰器来装饰函数
@pytest.fixture()
def my_fixture_1():
    print("我是自定义固件my_fixture_1")

# 通过将固件函数对象作为参数传入到测试用例中来调用自定义固件
def test_1(my_fixture_1):
    print("我是函数用例1")

# 在方法用例中调用固件函数对象
class TestClassDemo:
    def test_3(self,my_fixture_1):
        print("我是方法用例1")

自定义固件的调用:

  • 将自定义固件的名称,放在用例函数或者用例方法的形参中,就实现了自定义固件的调用。
  • 使用装饰器@pytest.mark.usefixtures("自定义固件名称")装饰用例,调用自定义固件,从装饰器中调用自定义固件,无法拿取返回值。
  • 将装饰器@pytest.fixture的形参autouse设置为True,所有的用例,都会默认调用该自定义固件。
注意:
1:使用第一种方式调用自定义固件时,在用例体中可以使用自定义固件的名称,自定义固件名称的值就是自定义固件的返回值。
2:自定义固件中返回值之前的代码为setup功能,返回值之后的代码为teardown的功能。
3:如果自定义固件使用return返回值,这样自定义固件就没有teardown的功能。
如果自定义固件使用yield返回值,自定义固件中只能有一个yield,如果有多个yield,会报错。
4:可以使用装饰器@pytest.fixture的scope形参设置自定义固件的级别(作用范围)
5: 自定义固件,还可以在自定义固件的形参中调用其他的自定义固件。---慎用
6:自定义固件在使用时,尽量以fix开头或结尾。

自定义固件的级别和作用域

  • session-会话级别:测试会话的级别,最高级别。---只要被调用,前置和后置只执行一次。
  • package-包级别:测试包级别,比用例模块要高。慎用(测试阶段)---只要被调用,前置和后置只执行一次。
  • module-模块级别:用例模块级别。---只要被调用,前置和后置只执行一次。
  • class-类级别:可以作用每个函数用例---作用每个函数用例时,前置和后置动作都会执行
    可以作用方法用例---只要被调用,前置和后置只执行一次。
  • function-函数级别:可以作用每个函数用例和每个方法用例,也是默认的级别。---用例执行时,前置和后置动作都会执行

通过scope参数设置自定义固件的级别

import pytest

@pytest.fixture(scope="session")
def scope_session():
    print("我是固件1:scope_session")

@pytest.fixture(scope="module")
def scope_module():
    print("我是固件2:scope_module")

@pytest.fixture(scope="class")
def scope_class():
    print("我是固件3:scope_class")

@pytest.fixture(scope="function")
def scope_function():
    print("我是固件4:scope_function")

@pytest.fixture()
def scope_default():
    print("我是固件5:scope_default")

def test_1(scope_session,scope_module,scope_class,scope_function,scope_default):
    print("我是函数测试用例1:test_1")

def test_2(scope_session,scope_module,scope_class,scope_function,scope_default):
    print("我是函数测试用例2:test_2")

class TestFixture():
    def test_3(self,scope_session,scope_module,scope_class,scope_function,scope_default):
        print("我是类方法用例1:test_3")

    def test_4(self,scope_session,scope_module,scope_class,scope_function,scope_default):
        print("我是类方法用例2:test_4")

conftest.py

我们可以把定义的固件函数写到conftest.py文件中,文件名不能变,在测试用例中,不需要去导入conftest.py文件,pytest框架在运行用例的时候会根据用例中传入的函数对象名称自动地去conftest.py文件中查找固件函数。

一般我们把conftest.py文件放到项目的根目录,这样项目中需要引用固件函数对象的用例才能找到它,如果放到子目录,可能会导致不在该子目录下的测试模块引用不到它。

# conftest.py
import pytest

@pytest.fixture()
def my_fixture_1():
    print("setup:my_fixture_1") 
    driver = webdriver.Chrome()
    yield driver
    print("teardown:my_fixture_1")
    driver.quit()

# 另外一个文件中
def test_01(my_fixture_1):
    my_fixture_1.get("https://www.baidu.com")

pytest参数化

pytest框架自带参数化模块,不需要借助第三方模块进行参数化,pytest可以实现以下两种参数化:

此作者没有提供个人介绍。
最后更新于 2025-09-06