简介
以下定义适用于本章。
- 脚本
包含 Python 代码的单个文件,旨在直接从 Python 中运行,例如 python my_script.py。
- 可导入的脚本
导入时不执行代码的脚本。代码只有在直接运行时才会被执行。
- 应用程序
指的是在 requirements.txt 文件中定义了外部依赖的软件包或脚本。Cards 项目也是一个应用程序,但它是用 pip 安装的。Cards的外部依赖在其pyproject.toml文件中定义,并在pip安装时被拉入。在这一章中,我们将特别关注那些不能或选择不使用pip的应用程序。
我们将从测试一个脚本开始。然后我们将修改该脚本,以便我们可以导入它进行测试。然后,我们将添加一个外部依赖,并看看测试应用程序。
在测试脚本和应用程序时,经常会出现一些问题。
- 如何从测试中运行一个脚本?
- 如何从脚本中捕获输出?
- 把源模块或包导入我的测试中。如果测试和代码在不同的目录下,我如何使之工作?
- 如果没有包可以构建,我如何使用 tox?
- 如何让Tox从requirements.txt文件中引入外部依赖?
测试简单的Python脚本
让我们从经典的编码例子开始,Hello World!。
ch12/script/hello.py
print("Hello, World!")
对于hello.py脚本,我们面临的挑战是:(1)如何从测试中运行它,以及(2)如何捕获输出。作为 Python 标准库的一部分,subprocess 模块 有一个 run() 方法,可以很好地解决这两个问题。
ch12/script/test_hello.py
print("Hello, World!")
执行
$ pytest -v test_hello.py
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0 -- D:\ProgramData\Anaconda3\python.exe
cachedir: .pytest_cache
rootdir: D:\code\pytest_quick\ch12\script, configfile: tox.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0
collecting ... collected 1 item
test_hello.py::test_hello PASSED [100%]
============================== 1 passed in 0.13s ==============================
用subprocess.run()测试小的脚本效果还可以,但它确实有缺点。我们可能想分别测试较大的脚本的各个部分。这是不可能的,除非我们把功能分成几个函数。
测试可导入的Python脚本
ch12/script_importable/hello.py
def main():
print("Hello, World!")
if __name__ == "__main__":
main()
ch12/script_importable/test_hello.py
import hello
def test_main(capsys):
hello.main()
output = capsys.readouterr().out
assert output == "Hello, World!\n"
参考资料
- 本文相关海量书籍下载
分离代码和测试代码
script_src
├── src
│ └── hello.py
├── tests
│ └── test_hello.py
└── pytest.ini
需要把我们要导入的源代码的目录添加到 sys.path 中。pytest 有选项可以帮助我们,即 pythonpath。该选项是为pytest 7引入的。如果你需要使用pytest 6.2,你可以使用pytest-srcpaths插件,在pytest 6.2.x中加入这个选项。
首先我们需要修改我们的pytest.ini,将pythonpath设置为src。
ch12/script_src/pytest.ini
[pytest]
addopts = -ra
testpaths = tests
pythonpath = src