Python 学习路径中的测试

学习如何在 Exercism 上测试你的 Python 练习。


我们使用 pytest 作为网站的测试运行器。 如果你想在本地下载并运行 Python 赛道的练习测试,就需要在自己的开发机上安装pytest。 另外,你还应该安装以下pytest插件:

我们还推荐使用代码检查工具 pylint,它是我们网站上自动化反馈的一部分,也是非常实用的静态代码分析工具。 为了方便,pytest的 pytest-pylint 插件可以让你在命令行里通过pytest运行pylint。

Pylint 的配置可能有点繁琐,所以这篇来自 pylint.readthedocs.io 的教程对入门很有帮助,Real Python 的这篇代码质量:工具与最佳实践概览也一样。

安装 pytest

可以使用 Python 内置的工具 pip 来安装和更新 Pytest。

想获得更多技巧的话,Brett Cannon 写了一篇不错的如何为 Python 安装软件包的快速指南,还详细讲解了为什么要用 python -m pip。 想进一步了解 Python 的命令行参数,可以看 Python 文档中的命令行与环境。

注意:Python3和py在你的系统上不一定是 Python 的别名。 请相应地调整下面的安装命令。 要在虚拟环境中安装pytest,请确保在执行命令前环境已激活。 否则,pytest会被安装到全局环境。

Windows

PS C:\Users\foobar> py -m pip install pytest pytest-cache pytest-subtests pytest-pylint
Successfully installed pytest-8.3.3 ...

Linux / MacOS

$ python3 -m pip install pytest pytest-cache pytest-subtests pytest-pylint
Successfully installed pytest-8.3.3 ...

检查安装是否成功:

$ python3 -m pytest --version
pytest 8.3.3

运行测试

要运行测试,请在终端里用cd进入存放练习的文件夹(把下面的<exercise-folder-location>替换成你自己的路径)。

$ cd <exercise-folder-location>

Note

<exercise-folder-location>或尖括号里的大多数内容表示一个**占位符值**。 正常的路径或文件名应该_不带_任何括号书写。

例如:/Users/janedoe/exercism/python/exercises/concept/chaitanas-colossal-coaster(在 *nix 系统上)、C:\Users\janedoe\exercism\python\exercises\practice\hello-world\(在 Windows 上)、myFolder或my_file.py。


你要运行的文件通常以_test.py结尾。 这个文件包含练习解答的测试,和网站上提交解答时运行的测试是同一套。 接下来,在终端中运行下面的命令,把<exercise_test.py>替换成测试文件的位置或名称:

$ python3 -m pytest -o markers=task <exercise_test.py>
==================== 7 passed in 0.08s ====================

处理警告

如果你在上面的命令中没有用pytest -o markers=task,那么在运行使用我们_新_语法的测试时,可能会看到关于“unknown markers”的warnings。

为了避免每次运行测试都要输入pytest -o markers=task,你可以使用pytest.ini配置文件,它可以从 Python 赛道目录的顶层下载:pytest.ini。

你也可以自己创建一个pytest.ini文件,内容如下:

[pytest]
markers =
    task: A concept exercise task.

把这个文件放在 Exercism 练习的_根_目录或_工作_目录中,就能注册这些标记并消除警告。 想了解关于 pytest 标记的更多信息,可以看pytest文档中关于标记测试函数的部分,以及pytest文档中关于使用自定义标记的部分。

想了解如何自定义 pytest 配置,可以看 pytest 文档中关于配置文件格式的部分

测试失败

测试失败时,pytest会把每个失败测试的文本,以及每个测试期望的return值和实际return值一起打印到终端。 下面是一个失败测试的通用示例:

$(my_venv) python3 -m pytest -o markers=task <exercise_test.py>

=================== FAILURES ====================
______________ name_of_failed_test ______________
# Test code inside of <exercise_test.py> that failed.
...
E   TypeOfError: ReturnedValue != ExpectedValue

exercise_test.py:<line_of_failed_test>: TypeOfError
============ short test summary info ============
FAILED exercise_test.py::ExerciseTest::name_of_failed_test
========== 1 failed, 2 passed in 0.13s ==========

额外参数

如果你真的想精确控制 pytest 在屏幕上输出的内容,这里有一些方便的命令行参数,可以用来配置它的行为。

返回所有详细信息 [-v]

加上-v(verbose)标志,除了测试失败之外,还会返回环境信息和测试摘要:

$(my_venv)  python3 -m pytest -o markers=task -v exercises/<exercise_name>/<test_file_test.py>

======================================== test session starts ===========================================
platform darwin -- Python 3.9.0, pytest-6.2.5, -- /usr/local/envs/my_env/bin/python3
cachedir: .pytest_cache
metadata: {'Python': '3.9.0', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '6.2.1'}, 'Plugins': {'subtests': '0.5.0', 'pylint': '0.18.0'}
rootdir: /Users/<user>/exercism/python, configfile: pytest.ini
plugins: subtests-0.5.0, pylint-0.18.0

collected 5 items

exercises/exercise-name/exercise_file_test.py::ExerciseNameTest::test_one FAILED                          [ 20%]
exercises/exercise-name/exercise_file_test.py::ExerciseNameTest::test_two FAILED
exercises/exercise-name/exercise_file_test.py::ExerciseNameTest::test_three PASSED                        [ 40%]
exercises/concept/exercise-name/exercise_file_test.py::ExerciseNameTest::test_four FAILED
exercises/concept/exercise-name/exercise_file_test.py::ExerciseNameTest::test_five PASSED                 [ 60%]
exercises/concept/exercise-name/exercise_file_test.py::ExerciseNameTest::test_six FAILED
exercises/concept/exercise-name/exercise_file_test.py::ExerciseNameTest::test_seven PASSED                [ 80%]
exercises/concept/exercise-name/exercise_file_test.py::ExerciseNameTest::test_eight FAILED
exercises/concept/exercise-name/exercise_file_test.py::ExerciseNameTest::test_nine PASSED                 [100%]

================================================ FAILURES ======================================================
# Failed tests are then individually printed out below

.......

首次失败后停止 [-x]

使用-x标志会正常运行测试,但在第一个测试失败时停止运行。 当你想一次调试一个任务或一个测试失败时,这很有帮助:

$(my_venv) python3 -m pytest -o markers=task -x exercises/<exercise_name>/<test_file_test.py>

=================== FAILURES ====================
_______________ example_test_foo ________________
...
...
============ short test summary info ============
FAILED example_test.py::ExampleTest::example_test_foo
!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!
========== 1 failed, 5 passed in 0.28s ==========

先运行失败的测试 [--ff]

pytest-cache插件会记住你上次运行pytest时哪些测试失败了,所以用--ff标志会告诉pytest先运行之前失败的测试,然后再继续运行其余的测试。 如果你针对某个任务或某组输入做很多小修改,这可能会加快测试速度。

$(my_venv) python3 -m pytest -o markers=task --ff <example_file_test.py>
==================== 7 passed in 503s ====================

推荐工作流

我们推荐使用下面的命令,让你的调试更轻松,(也许)更快:

首先,把工作目录切换到你要测试的练习所在的目录:

$(my_venv) cd path/to/exercise

然后,结合前面说过的参数-x和--ff运行测试:

$(my_venv) python3 -m pytest -o markers=task -x --ff <example_file_test.py>

这样就会测试你的解答。 当pytest遇到失败的测试时,程序会停下来,并告诉你哪个测试失败了。 当你修改后再次运行测试时,pytest会先运行上次失败的测试,然后再继续运行剩下的测试。

结合 pytest 使用 PDB(Python 调试器)

如果你想“像专业人士一样调试”,可以在pytest命令后面加上--pdb参数,进入内置的Python 调试器,也就是PDB。

$(my_venv) python3 -m pytest -o markers=task -x --ff --pdb <example_file_test.py>
=============== 4 passed in 0.15s ===============

当测试失败时,进入PDB后,你可以单步执行代码,查看当前作用域,还可以检查变量的值和不同函数的签名。 关于PDB模块的更多细节,可以在Python 文档中关于 PDB 的部分中找到。 此外,pytest 关于 PDB 的文档和这篇来自 Real Python 的指南也非常有帮助。

扩展你的 IDE

如果你想用一些工具来扩展你的 IDE,帮助你测试和改进代码,可以看看工具页面。 我们在那里介绍了多种 IDE、编辑器和一些用于代码检查和调试的实用扩展。

更多信息

把 python 添加到 PATH

注意: 如果你是通过 PSF 安装程序在 Windows 上安装 Python 的,那么命令是py,而不是python3。

每次想运行模块都要输入python3 -m,可能会有点烦。 为了避免这种情况,你可以把 Python 安装目录下的Scripts文件夹添加到 PATH 中。 如果你不知道自己把 Python 安装在哪里,可以在终端中运行下面的命令:

$ python3 -c "import os, sys; print(os.path.dirname(sys.executable))"
<python_directory>

_返回的_目录就是你当前启用的 Python 版本安装的位置,在本节中称为<python_directory>。

Windows

点击Windows Start按钮,查找 Edit the system environment variables,然后按回车。 接着点击Environment Variables...:

点击那个蓝色按钮,哈哈

然后在_User variables_中找到Path变量,选中它,点击Edit...:

选中 path 变量

然后像图中那样添加一行,把<python_directory>替换成你安装 Python 的目录:

把 python 添加到 path

MacOS/Linux

下面的方法适用于大多数使用bashshell 的 Linux 和 MacOS 发行版。 命令可能因 Linux 发行版而异,也会因你用的是fish还是zsh而不同。 把<python_directory>替换成python3 -c "import os, sys; print(os.path.dirname(sys.executable))"的输出

export PATH="$PATH:<python_directory>"