pytest基础知识

pytest基础知识

1 用例设计

文件名和函数命名规范:

  • pytest会自动收集以test_开头或者_test结尾的python文件作为测试文件
  • 测试文件中的测试函数或方法也需要以test_开头
  • 测试类需要以Test开头,且测试方法也需要以test_开头。
  • 测试类不包含__init__方法

2 用例执行

用例执行的方式主要有以下几种:

2.1 主函数模式

  • 运行所有测试用例:pytest.main()
  • 运行执行模块的测试用例:pytest.main(['-vs','模块名.py'])
  • 运行指定目录的测试用例:pytest.main(['-vs','./目录名'])
  • 通过nodeID指定用例运行:pytest.main(['-vs','模块名.py::类名::方法名'])

2.2 命令行模式

  • 运行所有测试用例:在命令行直接输入pytest
  • 运行指定模块的测试用例:pytest -vs 模块名.py
  • 运行指定目录的测试用例:进入到对应目录,然后执行pytest,或者在上级目录执行pytest 目录名称/
  • 运行模块模块内的某个测试方法或测试类:pytest 模块名.py::函数名或类名

2.3 通过标记执行

使用pytest -m 标记名来执行具有特定标记的测试用例或测试类。适用于大量测试用例中快速筛选出需要执行的用例。

2.4 参数化执行

使用参数化装饰器@pytest.mark.parametrize来对测试用例进行参数化,从而执行具有不同输入参数的测试。

3 用例跳过

跳过测试用例主要有以下几种:

3.1 无条件跳过

  • 使用装饰器@pytest.mark.skip(reason="跳过原因")来标记整个测试方法、类或者模块,参数可选
  • 在测试方法内部,如果满足某个条件不想继续执行测试,可以使用pytest.skip("unsupported configuration")来跳过当前的测试

3.2 有条件跳过

  • 使用装饰器@pytest.mark.skipif(condition,reason="跳过原因"),其中的条件是一个布尔表达式,当为True时会跳过,原因参数可选

3.3 跳过整个模块

  • 设置pytestmark=pytest.mark.skip("跳过模块中所有测试")来无条件跳过模块中的所有测试
  • 同样,使用pytestmark=pytest.mark.skipif(condition)根据条件跳过整个模块

4 用例执行顺序

4.1 使用pytest_ordering插件

  • 通过pip install pytest_ordering命令进行安装
  • 使用@pytest.mark.run(order=x)装饰器来控制测试用例的执行顺序。这里的x是整数,数字越小越先执行,如果同时存在正数和负数,则正数优先级高于负数。即先正后负,先小后大。

4.2 规范测试用例命名

  • pytest模块会根据测试用例的名称按照ASCII码顺序进行自动排序

5 测试用例预期失败标记

5.1 使用pytest.mark.xfail装饰器

  • 当知道某个测试用例由于某些原因会失败时,可以使用装饰器来标记为预期失败,如果确实失败,pytest会将其标记为”xfailed”,而不是普通的失败,如果用例意外通过,会将其标记为”xpass”
  • 可以使用@pytest.mark.xfail(condition,reason=None)来根据条件判断是否标记测试用例为预期失败。当条件为True时会被标记为预期失败

5.2 使用pytest.xfail函数

  • 在测试用例函数内部,可以使用pytest.xfail(reason)来标记测试用例为预期失败。调用此函数后,测试用例的后续代码将不会执行。

6 测试用例参数化

可以使用@pytest.mark.parametrize装饰器来对测试用例进行参数化,从而运行多个具有不同输入参数的测试。该装饰器接收参数和列表,参数支持多个,解包进行赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pytest  

@pytest.mark.parametrize("input, expected, skip_reason", [
(1, 2, None),
(2, 5, "this case is known to fail"), # 预期失败的实例
(3, 6, None),
pytest.param(4, 8, marks=pytest.mark.skip(reason="skip this case")), # 跳过的实例
])
def test_multiplication(input, expected, skip_reason):
if skip_reason:
pytest.skip(skip_reason)
if expected == 5:
pytest.xfail("Known failure")
assert input * 2 == expected

在这个例子中,我们添加了一个额外的skip_reason参数到参数化列表中。如果skip_reason有值,我们使用pytest.skip()来跳过该实例。对于预期失败的实例,我们直接在测试函数内部使用pytest.xfail()。注意,你还可以使用pytest.param()marks参数来直接给特定参数化实例添加标记。

7 断言

使用assert

8 捕获异常

使用pytest.raises()进行异常捕获

9 固件

9.1 固件的基本知识

基本概念

fixture的主要目的是提供可重用的测试资源,如配置数据、网络连接、数据库连接等。通过在测试用例中使用fixture,你可以确保这些资源在测试执行前被正确设置,并在测试执行后被清理。

作用域

  • function:默认的作用域,每个测试用例都会调用一次fixture。

  • class:每个测试类调用一次fixture,即使类中的每个方法都调用fixture,也只在第一个用例前执行一次。

  • module:每个.py文件调用一次fixture,针对一个文件下的所有用例只执行一次。

  • session:多个文件调用一次fixture,用于多个.py文件一起只调用一次的场景。这需要在目录下新建一个conftest.py文件来定义session级的fixture。

自动适配

使用@pytest.fixture(autouse=True)进行自动适配,当所有的测试都依赖于它时,可以设置为自动适配

9.2 固件的使用

一、设置测试环境

假设你正在编写针对某个Web API的测试,每次运行测试之前,你都需要确保API服务是可用的,并且具有一些预设的数据。你可以使用fixture来设置这样的测试环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests  
import pytest

@pytest.fixture(scope="module")
def api_session():
# 初始化API会话
session = requests.Session()
# 可能还包括设置会话的headers、cookies等
return session

def test_get_user(api_session):
# 使用fixture提供的API会话来发起请求
response = api_session.get('http://example.com/api/users/1')
assert response.status_code == 200
# 断言其他响应内容...

def test_create_user(api_session):
# 在另一个测试用例中再次使用fixture
data = {'name': 'John Doe', 'email': 'john@example.com'}
response = api_session.post('http://example.com/api/users', json=data)
assert response.status_code == 201
# 断言其他响应内容...

在这个例子中,api_session fixture在模块级别上被定义,意味着它会在模块中的所有测试用例之前被创建一次,并在所有测试用例执行完毕后被销毁。每个测试用例通过接受api_session作为参数来使用它,确保它们使用的是相同的API会话实例。

二、提供测试数据

另一个常见的使用场景是为测试用例提供数据。这些数据可以是静态的,也可以是根据某种条件动态生成的。

1
2
3
4
5
6
7
8
9
10
11
import pytest  

@pytest.fixture
def test_data():
# 返回一些静态的测试数据
return {'name': 'Test User', 'age': 30}

def test_user_data(test_data):
# 使用fixture提供的测试数据
assert test_data['name'] == 'Test User'
assert test_data['age'] == 30

在这个例子中,test_data fixture返回了一个字典,该字典包含了测试用例所需的测试数据。通过将test_data作为参数传递给test_user_data测试用例,你可以确保测试数据在每次运行测试时都是可用的。

三、清理测试资源

在测试完成后,有时需要清理创建的资源,如数据库记录、临时文件等。fixture也可以用来执行这些清理操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import os  
import pytest
import tempfile

@pytest.fixture
def temp_file():
# 创建一个临时文件
temp = tempfile.NamedTemporaryFile()
yield temp
# 清理临时文件
temp.close()

def test_write_to_temp_file(temp_file):
# 使用fixture提供的临时文件
temp_file.write(b'Hello, World!')
temp_file.flush()
assert os.path.getsize(temp_file.name) > 0

在这个例子中,temp_file fixture使用了yield语句来定义了一个生成器。在yield之前的代码块中,我们创建了一个临时文件。当测试用例开始执行时,它会从yield语句处继续执行,并且可以使用这个临时文件。当测试用例完成后,控制流会返回到fixture,并执行yield之后的代码块,即关闭临时文件,从而完成清理工作。

四、fixture的依赖关系

有时一个fixture可能依赖于另一个fixture。例如,你可能需要一个fixture来初始化数据库连接,而另一个fixture依赖于该连接来准备特定的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pytest  

@pytest.fixture
def db_connection():
# 初始化数据库连接
# ...
return connection

@pytest.fixture
def prepared_data(db_connection):
# 使用db_connection fixture提供的连接来准备数据
# ...
return data

def test_data_integrity(prepared_data):
# 使用prepared_data fixture提供的数据进行测试
# ...

在这个例子中,prepared_data fixture依赖于db_connection fixture。pytest会确保db_connection fixture在执行prepared_data fixture之前被创建和初始化。这样,你就可以在prepared_data fixture中使用数据库连接来准备测试所需的数据。

10 运行参数

-s:输出调试信息

-v:显示更详细的信息

-n=num:启动多线程或分布式运行测试用例。需要安装pytest-xdist插件

-k=value:用例的nodeid包含value值的测试用例被执行

-m=标签名:执行被@pytest.mark.标签名标记的用例

-x:只要有一个用例执行失败就停止当前线程的测试执行

-maxfail=num:自定义失败次数

-returns=num:失败用例重跑num次,需要安装pytest-rerunfailures

11 其他

在方法中添加标记,通过改变方法中的pytestmark属性来实现

1
functionName.pytestmark = pytest.mark.skip(reason="跳过")

在pytest的item中,可以通过调用item的add_marker方法来增加标记

1
item.add_marker(mark_name)
------------- End -------------