(2024.03.09 Sat @KLN)
对比Pytest和Unittest
pytest的优势在于简单容易实现,不需要太多test fixture,构建方便,执行测试时会在项目路径(子目录)下自动发现和运行名如test_*.py
或*_test.py
的文件,并在terminal中输入pytest
即可运行。
- 语法和简洁性:pytest提供了比unittest更加简洁(concise)和可读性高的语法,方便写测试和理解
- 测试发现(test discovery):pytest不需要提供显式和明确的子类和命名规则(naming convention)就可以自动发现测试文件和测试用例;unittest遵循严格的发现机制
- 固定件(fixtures)和插件(fixtures&plugins):pytest提供了功能强大的fixture mechanism,简化了测试设定和teardown。fixture可用于定义重复使用的设定和code cleanup,提升测试的组织性减少代码重写(duplication)。pytest还提供了大量插件用于扩展功能,诸如test coverage,mocking和参数化(parameterisation)
- 测试参数化(parameterisation):pytest内置了对参数化的支援,可便于开发者在相同的测试中使用不同的输入和参数,特别适用于重复代码在不同的场景中的情况。unittest则需要更多的手工设定实现参数化。
- 声明控制(assertion handling):pytest提供了更富有表达力(expressive)和灵活的assertion handling,提供更多的failure message,便于诊断问题
- 兼容与生态:pytest需要预先安装单提供了丰富的插件生态和社区支持;unittest作为python内置标准库兼容旧版python
(2024.03.10 Sun @KLN)
Pytest的特征
Fixture, mark
Fixture - Managing States and Dependencies
pytest固定件(后文统称fixture)为测试提供了数据,翻倍(test doubles),和状态初始化设定。fixtures作为函数可返回一系列值(a wide range of values)。每个依赖于fixture的测试必须显式的接受fixture作为输入变量。
使用fixture的典型场景:test-driven development (TTD)
试想一个格式转换的案例,函数format_transfer_1
讲一个输入JSON转换成list,输入的JSON有三个变量,family_name
,given_name
和job_title
,输出的list中的每个元素是一个string,格式如<given_name> <family_name>: <job_title>
。以TTD的模式开发,可写成如下模式:
# format_transfer.py
def format_transfer_1(people):
... # function body
测试代码如:
# test_format_transfer.py
def test_format_transfer_1():
c
assert format_transfer_1_list(people) == ["john tompson: professor",
"dave laurenson: professor"]
之后又开发一个新函数,功能是将同样的JSON数据转换成CSV格式,代码如
# format_transfer.py
def format_transfer_1_list(people):
... # function body
def format_transfer_2_csv(people):
... # function body
测试代码如
# test_format_transfer.py
def test_format_transfer_1():
...
def test_format_transfer_2():
people = [{"given_name": "john", "family_name": "thompson", "job_title": "professor"},
{"given_name": "dave", "family_name": "laurenson", "job_title": "professor"}]
assert format_transfer_2_csv(people) ==
"given_name, family_name, job_title
john, thompson, professor
dave, laurenson, professor"
注意到不同的test cases都用到了相同的测试数据people
。在这种情况下,pytest fixture就有了用武之地。测试用数据people
置于单独一个函数中,并用@pytest.fixture
装饰,在test cases中调用即可。
# test_format_transfer.py
@pytest.fixture
def example_people():
return people = [{"given_name": "john", "family_name": "thompson", "job_title": "professor"},
{"given_name": "dave", "family_name": "laurenson", "job_title": "professor"}]
# ...
在test cases中使用时只需要将函数名(function reference)作为参数传入测试函数,注意到传递的是fixture function reference,而非调用fixture function。
# test_format_transfer.py
# ...
def test_format_transfer_1(example_people):
assert format_transfer_1_list(example_people) ==
["john tompson: professor", "dave laurenson: professor"]
def test_format_transfer_2(example_people):
assert format_transfer_2_csv(example_people) ==
"given_name, family_name, job_title
john, thompson, professor
dave, laurenson, professor"
这种调用方式,类似class
中@property
的调用方式。
fixture的使用减少了代码量,很难不过度使用,下面介绍在何种情况下避免使用fixture。
何时避免使用Fixture
不同的测试用例需要对数据做微小修改,这种情况使用fixture未必能节省代码。如果对数据加入不同的层级未必有效。
成规模使用fixture (use fixture at scale)
随着测试中会用的fixture越来越多,fixture的抽象会提升效率。在pytest中,fixture可以像包(modular)一样被导入,也可以导入其他包,其他包也可以来fixture。这个特性使开发和可以为test cases编写合适的fixture抽象集。
(2024.03.14 Thur)
比如两个在独立文件中的fixtures使用共同的依赖,这种情况就可以转移fixtures到一个更通用的fixture相关的包中,之后在测试文件中引入fixtures。
另有方法可以在整个项目中使用fixture而不引入(import),设置一个设置包conftest.py
可实现该功能。
pytest
在每个路径中搜索conftest.py
包,将通用目的(general-purpose)的fixtures加入到一个conftest.py
包中,可在该包母目录(parent directory)和子目录下调用而无需引入,这极大的方便了fixtures的使用。
Fixtures和conftest.py
的另一个应用是保护对资源的使用(guarding access to resources)。一个处理API调用的测试集(test suite)中,开发者要确保该测试集不会接入真实网络调用。
网友评论