使用 Python 进行测试(3)pytest setup

2024-06-16 11:20

本文主要是介绍使用 Python 进行测试(3)pytest setup,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

总结

我们前进到更真实的项目:

less_basic_project
├── my_awesome_package
│   ├── calculation_engine.py
│   ├── __init__.py
│   ├── permissions.py
├── pyproject.toml
└── tests├── conftest.py├── test_calculation_engine.py└── test_permissions.py

使用pyproject.toml配置pytest:

[tool.pytest.ini_options] # mandatory section name
addopts = "-s --no-header --no-summary" # force cmd flags
testpaths = [ # what directories contain tests"tests",
]
pythonpath = [ # what to add to the python path"."
]

我们简化了 pytest 调用,以获得自动递归测试发现,解决导入问题并使输出更清晰。
我们还可以利用“conftest.py”来定义整个项目的夹具,并缓存它们的执行:

@pytest.fixture(scope="module") # run once for each test file
def a_fixture():...

即使没有众多的插件,我们也可以使用许多标志来微调 pytest 的行为,其中包括:

  • -x:第一次失败时停止
  • --pdb:失败时启动pdb调试
  • -k <filter>:仅发现与筛选器匹配的测试
  • --ff:从上次运行失败的测试开始
  • --nf:从新文件开始
  • --sw:从上一次运行停止的地方开始
  • --no-header/--no-summary:删除输出中的大块文本
  • --vevbosity=x:控制输出细粒度,从0级到3级。

再说一句就上路

我保证,下一部分我们将回答诸如“要测试什么”之类的实际问题。但在我们继续之前,我们需要再熟悉下我们的工具。
你看,pytest是一个很棒的工具,但目前为止,我只展示了玩具示例,所以当然一切都很好。不过,你会有一个大项目,包含大量文件、导入、配置等。因此,你需要知道如何再复杂地情况下使用pytest。

真实的项目布局

让我们创建一个更可能使用到的文件结构:

less_basic_project
├── my_awesome_package
│   ├── __init__.py
│   └── the_code_to_test.py
├── pyproject.toml
└── tests└── the_tests.py

现在我们有了一个less_basic_project的项目根目录,其中包含两个目录。"tests"目录包含项目的测试,“my_awesome_package"包含项目源代码。此外,__init__.py用于导入。最后,项目根目录下的"pyproject.toml”,是python项目的标准配置文件。

第一个ImportError

如果你走到项目的根目录下,像个好人一样运行pytest tests/the_tests.py,你会摔倒:

$ pytest tests/the_tests.py
...
tests/the_tests.py:4: in <module>from the_code_to_test import add
E   ModuleNotFoundError: No module named 'the_code_to_test'

事实上,the_code_to_test不再是正确的路径,它已经是包的一部分了,我们需要修复"the_tests.py"的import:

from the_code_to_test import add

改为

from my_awesome_package.the_code_to_test import add

现在应该没问题了,你知道的,sys.path会将当前目录添加进来,因此我们能找到my_awssome_package。但事实并非如此,再次运行pytest,再次崩溃:

$ pytest tests/the_tests.py
...
tests/the_tests.py:4: in <module>from my_awesome_package.the_code_to_test import add
E   ModuleNotFoundError: No module named 'my_awesome_package'

wtf
pytest可执行文件是个例外,当前目录没有被添加到sys.path中。如果你使用python -m pytest执行测试,就能正常运行:

python -m pytest tests/the_tests.py
================= test session starts ================
platform linux -- Python 3.10.13, pytest-7.3.0, pluggy-1.0.0
rootdir: /path/to/less_basic_project
plugins: django-4.5.2, clarity-1.0.1
collected 4 itemstests/the_tests.py ....================= 4 . in 0.01s  ================

这就是我推荐使用-m的原因。不幸的是,这会显得非常啰嗦:
python -m pytest -s test/the_tests.py

自动发现测试

pytest可以自动扫描符合命名规则的测试文件,默认的测试文件命是test_xxx格式。
让我们将the_code_to_test.py的功能明确,并重命名为calculation_engine.py,然后将其测试文件也重命名为test_calculation_engine.py,别忘了修改import!

我们得到

less_basic_project
├── my_awesome_package
│   ├── calculation_engine.py
│   ├── __init__.py
├── pyproject.toml
└── tests└── test_calculation_engine.py <- change imports here

现在我们可以运行:

 python -m pytest tests

pytest会自动寻找测试并执行。

下面我们新加一个功能,确保我们有一个安全系统:
my_awesome_package/permissions.py

def can_access(file_path):return True

以及相应的测试 tests/test_permissions.py:

from my_awesome_package.permissions import can_accessdef test_can_access():assert can_access("/")

再次运行:

 python -m pytest tests
================= test session starts ================
platform linux -- Python 3.10.13, pytest-7.3.0, pluggy-1.0.0
rootdir: /path/to/less_basic_project
plugins: django-4.5.2, clarity-1.0.1
collected 5 itemstests/test_calculation_engine.py ....                                 [ 80%]
tests/test_permissions.py .                                           [100%]================= 5 . in 0.01s  ================

pytest会自动寻找测试并执行。

Pytest 配置

Pytest 可以从“pyproject.toml”文件配置,可以将所需的默认行为放在那里,命令行中的内容都会覆盖此默认配置。
下面是一个例子:

[tool.pytest.ini_options] # mandatory section name
addopts = "-s" # force a command line option
testpaths = [ # what directories contain tests"tests",
]
pythonpath = [ # what to add to the python path"."
]

有了这个,你就可以直接使用pytest了:

pytest
================= test session starts ================
platform linux -- Python 3.10.13, pytest-7.3.0, pluggy-1.0.0
rootdir: /path/to/less_basic_project
plugins: django-4.5.2, clarity-1.0.1
collected 5 items
tests/test_calculation_engine.py::test_add_integers
This is run before each test
.
We tested with 5This is run after each testtests/test_calculation_engine.py::test_add_strings
This is run before each test
.
This is run after each testtests/test_calculation_engine.py::test_add_floats .
tests/test_calculation_engine.py::test_add_mixed_types .
tests/test_permissions.py::test_can_access .

配置文件将当前目录(.)添加到sys.path,在"test"目录中查找测试,添加-s选项输出到命令行。

请注意,pythonpath选项在pytest的版本7中添加,如果你的Pytest版本较低,可能需要额外的插件。此外,其他工具可能也有类似的sys.path问题。最后,你可能想咬紧牙关,为整个项目完成PATHONPATH。但这是另一个话题,我们现在继续关注pytest。

还要考虑到在许多情况下,例如 CI、任务运行器(doit、nox…)、git 钩子,我仍然会使用 python -m pytest ,来避免其他问题。因此,虽然手动执行很方便,但请记住 -m 是我们的救世主。

更多配置

pytest 是充分可配置的,运行 pytest -h 会得到一面充满选项、标志和 env var 的文本墙,您可以使用它来调整其行为。一旦你确信你了解了这个库是如何工作的,你绝对应该探索这些。同时,我将首先指出几个值得您花时间的旋钮:

  • -x:第一次失败时停止
  • --pdb:失败时启动pdb调试
  • -k <filter>:仅发现与筛选器匹配的测试
  • --ff:从上次运行失败的测试开始
  • --nf:从新文件开始
  • --sw:从上一次运行停止的地方开始
  • --no-header/--no-summary:删除输出中的大块文本
  • --vevbosity=x:控制输出细粒度,从0级到3级。

事实上,对于该系列的其余部分,我将在“pyproject.toml”中设置它:
addopts = "-s --no-header --no-summary"
这将使测试运行更加清晰。

但是,这并不是配置 pytest 的唯一方法。您已经了解了另一个:命名约定。还有一些特定的调用约定。当您想要针对特定测试时,最有用的是在路径中使用“::”,因为您可以执行“pytest path/to/test_file.py::test_function”。
例如,我指向调用test_add_strings,我会使用pytest tests/test_calculation_engine.py::test_add_strings

我说更多配置!

Pytest 带有一个功能齐全的插件系统,每个插件系统都可以更改您的 pytest 配置,还可以添加您可以从命令行或“pyproject.toml”设置的全新设置。一旦它们安装在虚拟环境中,它们就会自动加载,除非您在配置中禁用它们。
例如,如果我 pip install pytest-sugarpytest-sugar插件将安装并在我的下一次测试运行时自动加载,为输出提供进度条:
pytest-sugar
它还将添加 pytest 选项 --force-sugar 。

虽然我觉得 pytest-sugar 很漂亮,但我个人并没有使用它,这只是插件工作原理的一个例子。有大量的插件,有些提供代码覆盖率,有些提供数据库设置,有些rest客户端,有些fake数据…生态系统丰富、强大且非常有用。
配置还有另一面:如何将测试与文本编辑器集成。这本身就是一篇完整的文章,而且有这么多的IDE,有这么多不同的偏好,我们不太可能帮助每个人。我可能至少在 VSCode 上写一篇文章,我还在思考。

更多多多多多配置!!!

是的,conf 在 pytest 中非常疯狂。这是最后一个,我发誓。
如果创建一个名为“conftest.py”的文件并将其放在“tests”目录下,则可以以编程方式影响pytest在python中的工作方式。
我们不会讨论你可以用 conftest.py 做的所有事情,因为它很快就会变得深奥。更不用说您实际上可以创建几个相互级联和覆盖的“conftest.py”。这是一个潘多拉魔盒。

在“conftest.py”中,您可以做的最重要的事情是共享夹具和范围。
想一想我们的夹具:

@pytest.fixture()
def random_number():yolo = random.randint(0, 10)yield yoloprint(f"\nWe tested with {yolo}")

它在在“test_calculation_engine.py”中,我们能在“test_permissions.py”中使用random_number吗?
显然不行。但是,如果您将其移动到“conftest.py”,那么突然间,同层的所有测试都可以访问夹具。

less_basic_project
├── my_awesome_package
│   ├── calculation_engine.py
│   ├── __init__.py
│   ├── permissions.py
├── pyproject.toml
└── tests├── conftest.py├── test_calculation_engine.py└── test_permissions.py

conftest.py中包含:

import random
import pytest@pytest.fixture()
def random_number():yolo = random.randint(0, 10)yield yoloprint(f"\nWe tested with {yolo}")

我们从“test_calculation_engine.py”中删除了该夹具。

这引出了范围(scope),默认情况下,每个使用夹具的测试都会调用一次夹具。
但是,对于某些测试,例如数据库设置、网络连接、文件创建、数据生成等,您可能希望夹具在每组测试中实际运行一次,或者在整个测试运行中实际运行一次。
您可以使用 scope 参数执行此操作:

@pytest.fixture(scope="module") # run once for each test file
def random_number():yolo = random.randint(0, 10)yield yoloprint(f"\nWe tested with {yolo}")

scoped的值可设置为 “function” (默认值)、 “module” (一个 py 文件)、 “package” (整个目录)或 “session” (一次运行)。

如果您不需要 yield 的值,但想要副作用,您还可以autouse强制夹具运行为所有测试运行,即使测试没有使用名为 random_number 的参数:@pytest.fixture(autouse=True)
scope 并且 autouse 可以一起使用,谨慎使用,这很容易过度使用。

关于缓存

出于教学原因,我向你展示了一个干净的项目树,但实际上,如果你现在看一眼你的项目树,它看起来更像是这样(YMMV):

less_basic_project
├── my_awesome_package
│   ├── calculation_engine.py
│   ├── __init__.py
│   ├── permissions.py
│   └── __pycache__
│       ├── calculation_engine.cpython-310.pyc
│       ├── __init__.cpython-310.pyc
│       └── permissions.cpython-310.pyc
├── pyproject.toml
├── .pytest_cache
│   ├── CACHEDIR.TAG
│   ├── .gitignore
│   ├── README.md
│   └── v
│       └── cache
│           ├── lastfailed
│           ├── nodeids
│           └── stepwise
└── tests├── conftest.py├── __pycache__│   ├── conftest.cpython-310-pytest-8.1.1.pyc│   ├── test_calculation_engine.cpython-310-pytest-8.1.1.pyc│   └── test_permissions.cpython-310-pytest-8.1.1.pyc├── test_calculation_engine.py└── test_permissions.py

这是因为 python 和 pytest 都有自己的缓存机制。这些文件不是您应该提交到 VCS 的内容(例如 git),可以删除它们。缓存可以让测试运行得更快,并确保一些功能如--ff可用。

这篇关于使用 Python 进行测试(3)pytest setup的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1066330

相关文章

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

springboot中使用okhttp3的小结

《springboot中使用okhttp3的小结》OkHttp3是一个JavaHTTP客户端,可以处理各种请求类型,比如GET、POST、PUT等,并且支持高效的HTTP连接池、请求和响应缓存、以及异... 在 Spring Boot 项目中使用 OkHttp3 进行 HTTP 请求是一个高效且流行的方式。

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

python获取指定名字的程序的文件路径的两种方法

《python获取指定名字的程序的文件路径的两种方法》本文主要介绍了python获取指定名字的程序的文件路径的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 最近在做项目,需要用到给定一个程序名字就可以自动获取到这个程序在Windows系统下的绝对路径,以下

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Python实现批量CSV转Excel的高性能处理方案

《Python实现批量CSV转Excel的高性能处理方案》在日常办公中,我们经常需要将CSV格式的数据转换为Excel文件,本文将介绍一个基于Python的高性能解决方案,感兴趣的小伙伴可以跟随小编一... 目录一、场景需求二、技术方案三、核心代码四、批量处理方案五、性能优化六、使用示例完整代码七、小结一、

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e

Java使用jar命令配置服务器端口的完整指南

《Java使用jar命令配置服务器端口的完整指南》本文将详细介绍如何使用java-jar命令启动应用,并重点讲解如何配置服务器端口,同时提供一个实用的Web工具来简化这一过程,希望对大家有所帮助... 目录1. Java Jar文件简介1.1 什么是Jar文件1.2 创建可执行Jar文件2. 使用java

C#使用Spire.Doc for .NET实现HTML转Word的高效方案

《C#使用Spire.Docfor.NET实现HTML转Word的高效方案》在Web开发中,HTML内容的生成与处理是高频需求,然而,当用户需要将HTML页面或动态生成的HTML字符串转换为Wor... 目录引言一、html转Word的典型场景与挑战二、用 Spire.Doc 实现 HTML 转 Word1