pytest 中的分组测试:类 与普通函数
Grouping tests in pytest: Classes vs plain functions
我正在使用 pytest 来测试我的应用程序。
pytest 支持 2 种编写测试的方法(据我所知):
- 在classes:
test_feature.py -> class TestFeature -> def test_feature_sanity
- 在函数中:
test_feature.py -> def test_feature_sanity
是否需要在 class 中对测试进行分组的方法?是否允许向后移植 unittest 内置模块?
您认为哪种方法更好,为什么?
与 classes 相比,没有关于将测试组织到模块中的严格规则。这是个人喜好的问题。最初我尝试将测试组织成 classes,一段时间后我意识到我没有用到另一个级别的组织。现在我只是将测试功能收集到模块(文件)中。
我可以看到一个有效的用例,当一些测试可以在逻辑上组织到同一个文件中,但仍然有额外的组织级别进入 classes(例如利用 class 范围夹具)。但这也可以通过拆分成多个模块来完成。
通常在单元测试中,我们测试的对象是单个函数。也就是说,单个功能会产生多个测试。在阅读测试代码时,以某种方式将单个单元的测试组合在一起是很有用的(这也允许我们例如 运行 特定功能的所有测试),因此这给我们留下了两个选择:
- 将每个功能的所有测试放在专用模块中
- 将每个函数的所有测试放在 class
中
在第一种方法中,我们仍然对以某种方式对与源模块相关的所有测试(例如 utils.py
)进行分组感兴趣。现在,由于我们已经在使用模块对 function 的测试进行分组,这意味着我们应该使用 package 对 a 的测试进行分组源模块。
结果是一个源函数映射到一个测试模块,一个源模块 映射到一个测试 package.
在第二种方法中,我们将一个源函数映射到一个测试 class(例如 my_function()
-> TestMyFunction
),一个源模块映射到一个测试模块(例如 utils.py
-> test_utils.py
)。
这可能取决于具体情况,但第二种方法,即对您正在测试的每个功能进行 class 测试,对我来说似乎更清楚。此外,如果我们正在测试源 classes/methods,那么我们可以简单地使用测试 classes 的继承层次结构,并且仍然保留一个源模块-> 一个测试模块映射。
最后,与仅包含多个函数测试的平面文件相比,这两种方法的另一个好处是,classes/modules 已经确定了正在测试的函数,您可以为实际测试使用更好的名称,例如test_does_x
和 test_handles_y
而不是 test_my_function_does_x
和 test_my_function_handles_y
。
这个答案展示了 pytest 中 TestClass 的两个引人注目的用例:
- 属于给定 class 的多个测试方法的联合参数化。
- 通过子class继承重用测试数据和测试逻辑
属于给定 class 的多个测试方法的联合参数化。
pytest 参数化装饰器 @pytest.mark.parametrize
可用于使输入可用于 class 中的多个方法。在下面的代码中,输入 param1
和 param2
可用于每个方法 TestGroup.test_one
和 TestGroup.test_two
.
"""test_class_parametrization.py"""
import pytest
@pytest.mark.parametrize(
"param1,param2",
[
("a", "b"),
("c", "d"),
],
)
class TestGroup:
"""A class with common parameters, `param1` and `param2`."""
@pytest.fixture
def fixt(self):
"""This fixture will only be available within the scope of TestGroup"""
return 123
def test_one(self, param1, param2, fixt):
print("\ntest_one", param1, param2, fixt)
def test_two(self, param1, param2):
print("\ntest_two", param1, param2)
$ pytest -s test_class_parametrization.py
================================================================== test session starts ==================================================================
platform linux -- Python 3.8.6, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
rootdir: /home/jbss
plugins: pylint-0.18.0
collected 4 items
test_class_parametrization.py
test_one a b 123
.
test_one c d 123
.
test_two a b
.
test_two c d
.
=================================================================== 4 passed in 0.01s ===================================================================
通过子class继承重用测试数据和测试逻辑
我将使用取自 的代码的修改版本来演示从 TestClass
继承 class attributes/methods 到 TestSubclass
的有用性:
# in file `test_example.py`
class TestClass:
VAR = 3
DATA = 4
def test_var_positive(self):
assert self.VAR >= 0
class TestSubclass(TestClass):
VAR = 8
def test_var_even(self):
assert self.VAR % 2 == 0
def test_data(self):
assert self.DATA == 4
此文件上的 运行 pytest
导致 四个 测试为 运行:
$ pytest -v test_example.py
=========== test session starts ===========
platform linux -- Python 3.8.2, pytest-5.4.2, py-1.8.1
collected 4 items
test_example.py::TestClass::test_var_positive PASSED
test_example.py::TestSubclass::test_var_positive PASSED
test_example.py::TestSubclass::test_var_even PASSED
test_example.py::TestSubclass::test_data PASSED
在subclass中,继承的test_var_positive
方法是运行使用更新值self.VAR == 8
,新定义的test_data
方法是运行 反对继承的属性 self.DATA == 4
。这种方法和属性继承提供了一种灵活的方式来重用或修改不同组的测试用例之间的共享功能。
在 JavaScript jasmine 中,测试是用 describe 和 it 方法构建的:
What is the difference between describe and it in Jest?
这是该概念的 Python 实现:
https://pypi.org/project/pytest-describe/
缺点可能是对 IDE 的支持滞后。
我想为了找到测试方法,他们的名字必须
以“test_”而不是“it_”开头
def describe_list():
@pytest.fixture
def list():
return []
def describe_append():
def adds_to_end_of_list(list):
list.append('foo')
list.append('bar')
assert list == ['foo', 'bar']
def describe_remove():
@pytest.fixture
def list():
return ['foo', 'bar']
def removes_item_from_list(list):
list.remove('foo')
assert list == ['bar']
我正在使用 pytest 来测试我的应用程序。 pytest 支持 2 种编写测试的方法(据我所知):
- 在classes:
test_feature.py -> class TestFeature -> def test_feature_sanity
- 在函数中:
test_feature.py -> def test_feature_sanity
是否需要在 class 中对测试进行分组的方法?是否允许向后移植 unittest 内置模块? 您认为哪种方法更好,为什么?
与 classes 相比,没有关于将测试组织到模块中的严格规则。这是个人喜好的问题。最初我尝试将测试组织成 classes,一段时间后我意识到我没有用到另一个级别的组织。现在我只是将测试功能收集到模块(文件)中。
我可以看到一个有效的用例,当一些测试可以在逻辑上组织到同一个文件中,但仍然有额外的组织级别进入 classes(例如利用 class 范围夹具)。但这也可以通过拆分成多个模块来完成。
通常在单元测试中,我们测试的对象是单个函数。也就是说,单个功能会产生多个测试。在阅读测试代码时,以某种方式将单个单元的测试组合在一起是很有用的(这也允许我们例如 运行 特定功能的所有测试),因此这给我们留下了两个选择:
- 将每个功能的所有测试放在专用模块中
- 将每个函数的所有测试放在 class 中
在第一种方法中,我们仍然对以某种方式对与源模块相关的所有测试(例如 utils.py
)进行分组感兴趣。现在,由于我们已经在使用模块对 function 的测试进行分组,这意味着我们应该使用 package 对 a 的测试进行分组源模块。
结果是一个源函数映射到一个测试模块,一个源模块 映射到一个测试 package.
在第二种方法中,我们将一个源函数映射到一个测试 class(例如 my_function()
-> TestMyFunction
),一个源模块映射到一个测试模块(例如 utils.py
-> test_utils.py
)。
这可能取决于具体情况,但第二种方法,即对您正在测试的每个功能进行 class 测试,对我来说似乎更清楚。此外,如果我们正在测试源 classes/methods,那么我们可以简单地使用测试 classes 的继承层次结构,并且仍然保留一个源模块-> 一个测试模块映射。
最后,与仅包含多个函数测试的平面文件相比,这两种方法的另一个好处是,classes/modules 已经确定了正在测试的函数,您可以为实际测试使用更好的名称,例如test_does_x
和 test_handles_y
而不是 test_my_function_does_x
和 test_my_function_handles_y
。
这个答案展示了 pytest 中 TestClass 的两个引人注目的用例:
- 属于给定 class 的多个测试方法的联合参数化。
- 通过子class继承重用测试数据和测试逻辑
属于给定 class 的多个测试方法的联合参数化。
pytest 参数化装饰器 @pytest.mark.parametrize
可用于使输入可用于 class 中的多个方法。在下面的代码中,输入 param1
和 param2
可用于每个方法 TestGroup.test_one
和 TestGroup.test_two
.
"""test_class_parametrization.py"""
import pytest
@pytest.mark.parametrize(
"param1,param2",
[
("a", "b"),
("c", "d"),
],
)
class TestGroup:
"""A class with common parameters, `param1` and `param2`."""
@pytest.fixture
def fixt(self):
"""This fixture will only be available within the scope of TestGroup"""
return 123
def test_one(self, param1, param2, fixt):
print("\ntest_one", param1, param2, fixt)
def test_two(self, param1, param2):
print("\ntest_two", param1, param2)
$ pytest -s test_class_parametrization.py
================================================================== test session starts ==================================================================
platform linux -- Python 3.8.6, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
rootdir: /home/jbss
plugins: pylint-0.18.0
collected 4 items
test_class_parametrization.py
test_one a b 123
.
test_one c d 123
.
test_two a b
.
test_two c d
.
=================================================================== 4 passed in 0.01s ===================================================================
通过子class继承重用测试数据和测试逻辑
我将使用取自 TestClass
继承 class attributes/methods 到 TestSubclass
的有用性:
# in file `test_example.py`
class TestClass:
VAR = 3
DATA = 4
def test_var_positive(self):
assert self.VAR >= 0
class TestSubclass(TestClass):
VAR = 8
def test_var_even(self):
assert self.VAR % 2 == 0
def test_data(self):
assert self.DATA == 4
此文件上的 运行 pytest
导致 四个 测试为 运行:
$ pytest -v test_example.py
=========== test session starts ===========
platform linux -- Python 3.8.2, pytest-5.4.2, py-1.8.1
collected 4 items
test_example.py::TestClass::test_var_positive PASSED
test_example.py::TestSubclass::test_var_positive PASSED
test_example.py::TestSubclass::test_var_even PASSED
test_example.py::TestSubclass::test_data PASSED
在subclass中,继承的test_var_positive
方法是运行使用更新值self.VAR == 8
,新定义的test_data
方法是运行 反对继承的属性 self.DATA == 4
。这种方法和属性继承提供了一种灵活的方式来重用或修改不同组的测试用例之间的共享功能。
在 JavaScript jasmine 中,测试是用 describe 和 it 方法构建的: What is the difference between describe and it in Jest?
这是该概念的 Python 实现:
https://pypi.org/project/pytest-describe/
缺点可能是对 IDE 的支持滞后。 我想为了找到测试方法,他们的名字必须 以“test_”而不是“it_”开头
def describe_list():
@pytest.fixture
def list():
return []
def describe_append():
def adds_to_end_of_list(list):
list.append('foo')
list.append('bar')
assert list == ['foo', 'bar']
def describe_remove():
@pytest.fixture
def list():
return ['foo', 'bar']
def removes_item_from_list(list):
list.remove('foo')
assert list == ['bar']