目录
片状测试
为什么片状测试是个问题
当使用连续集成(CI)服务器时,片状测试尤其麻烦,因此在合并新代码更改之前必须通过所有测试。如果测试结果不是一个可靠的信号 - 测试失败意味着代码更改破坏了测试 - 开发人员可能会对测试结果产生不信任,这可能导致忽略真正的失败。它也是浪费时间的一个来源,因为开发人员必须重新运行测试套件并调查虚假故障。
潜在的根本原因
系统状态
从广义上讲,一个片状测试表明测试依赖于一些未被适当控制的系统状态 - 测试环境没有充分隔离。更高级别的测试更有可能是因为他们依赖更多的状态。
当测试套件并行运行时(例如使用pytest-xdist),有时会出现片状测试。这可以表明测试依赖于测试排序。
- 也许不同的测试是在自身之后无法清理并留下导致片状测试失败的数据。
- 片状测试依赖于先前测试的数据,该测试不会自行清理,并且并行运行以前的测试并不总是存在
- 修改全局状态的测试通常不能并行运行。
过于严格的断言
过于严格的断言可能会导致浮点比较以及时序问题。[pytest.approx在这里很有用。
Pytest特性
Xfail严格模式
pytest.mark.xfailwithstrict=False
可用于标记测试,以便其失败不会导致整个构建中断。这可以被视为手动隔离,永久使用是相当危险的。
PYTEST_CURRENT_TEST
PYTEST_CURRENT_TEST环境变量可用于确定“哪个测试卡住了”。
插件
重新运行任何失败的测试可以通过给予他们额外的机会来减轻片状测试的负面影响,这样整体构建就不会失败。几个pytest插件支持这个:
- 片状测试
- pytest-flakefinder
- pytest-rerunfailures
- pytest-replay:这个插件有助于重现CI运行期间观察到的局部崩溃或片状测试。
故意随机化测试的插件可以帮助公开测试状态问题:
- pytest随机顺序
- pytest随机
其他一般策略
拆分测试套件
将单个测试套件拆分为两个是常见的,例如单元与集成,并且仅将单元测试套件用作CI门。这也有助于保持构建时间的可管理性,因为高级别测试往往更慢。但是,这意味着打破构建的代码可能会合并,因此需要额外的警惕来监视集成测试结果。
失败的视频/截图
对于UI测试,这些对于了解测试失败时UI的状态非常重要。pytest-splinter可以与pytest-bdd这样的插件一起使用,并且可以在测试失败时保存屏幕截图,这有助于隔离原因。
删除或重写测试
如果其他测试涵盖了该函数,则可能会删除该测试。如果没有,也许它可以在较低的水平重写,这将消除片状或使其来源更明显。
隔离
Mark Lapierre在2018年的一篇文章中讨论了[隔离测试的优缺点。
在失败时重新运行的CI工具
Azure管道(Azure云CI / CD工具,以前称为Visual Studio Team Services或VSTS)具有[识别片状测试和重新运行失败测试的函数。
研究
这是一个有限的列表,请提交问题或拉取请求以扩展它!
- Gao,Zebao,Yalan Liang,Myra B. Cohen,Atif M. Memon和Zhen Wang。“使系统用户交互式测试可重复:何时以及我们应该控制什么?”在软件工程(ICSE),2015 IEEE / ACM第37届IEEE国际会议上,第一卷。1,pp.55-65。IEEE,2015年PDF
- Palomba,Fabio和Andy Zaidman。“测试气味的重构是否会导致固定片状测试?”在软件维护和演进(ICSME),2017 IEEE国际会议上,第1-12页。IEEE,2017.Google[Drive中的PDF
- Bell,Jonathan,Owolabi Legunsen,Michael Hilton,Lamyaa Eloussi,Tifany Yung和Darko Marinov。“DeFlaker:自动检测片状测试。”在2018年国际软件工程会议论文集中。2018.[PDF
资源
- 在
- 破碎的建筑:在持续集成测试中建立信任测试在2017年奥斯汀SeleniumConf上讲话(视频)
- 测试和代码播客:片状测试以及如何处理它们由Brian Okken和Anthony Shaw,2018年
- 微软:
- 我们如何通过测试VSTS来实现
- 博客和谈话(视频)
- 谷歌:
- 谷歌的片状测试和我们如何减轻他们的影响,约翰米科,2016年
- 谷歌的片状测试来自哪里?作者:Jeff Listfield,2017
以下是pytest可能需要更改sys.path
以导入测试模块或conftest.py
文件的方案列表。
Pytest导入机制和sys.path/PYTHONPATH
包中的测试模块及conftest.py文件
考虑这个文件和目录布局:
root/ |- foo/ - __init__.py - conftest.py - bar/ - __init__.py - tests/ - __init__.py - test_foo.py
执行时:
pytest root/
pytest会发现foo/bar/tests/test_foo.py
并意识到它是一个包的一部分,因为__init__.py
在同一个文件夹中有一个文件。然后它将向上搜索,直到它找到仍包含__init__.py
文件的最后一个文件夹,以便找到包根(在本例中foo/
)。要加载模块,它将插入root/
到前面sys.path(如果不存在),以便
test_foo.py作为*模块*加载
foo.bar.tests.test_foo`。
相同的逻辑适用于该conftest.py
文件:它将作为foo.conftest
模块导入。
当测试存在于包中以避免出现问题并允许测试模块具有重复的名称时,保留完整的包名称非常重要。在[Python测试发现的约定中也详细讨论了这一点。
独立测试模块及conftest.py文件
考虑这个文件和目录布局:
root/ |- foo/ - conftest.py - bar/ - tests/ - test_foo.py
执行时:
pytest root/
pytest会发现foo/bar/tests/test_foo.py
并意识到它不是包的一部分,因为__init__.py
同一个文件夹中没有文件。然后它将添加root/foo/bar/tests
到sys.path
以test_foo.py
作为模块导入test_foo
。conftest.py
通过添加root/foo
以sys.path
将其导入为文件,对文件执行相同操作conftest
。
因此,此布局不能包含具有相同名称的测试模块,因为它们都将导入全局导入命名空间。
在[Python测试发现的约定中也详细讨论了这一点。
调用通过python -m pytest调用pytest
运行pytest而不是产生几乎相同的行为,除了前一个调用将添加当前目录。另请参阅[通过python -m pytest调用pytest。python-mpytest[...]``pytest[...]``sys.path
以下是pytest可能需要更改sys.path
以导入测试模块或conftest.py
文件的方案列表。
运行pytest而不是产生几乎相同的行为,除了前一个调用将添加当前目录。另请参阅[通过python -m pytest调用pytest。python-mpytest[...]``pytest[...]``sys.path