学习如何在 Exercism 上测试你的 Python 练习。
我们使用 pytest 作为网站的测试运行器。
如果你想在本地下载并运行 Python 赛道的练习测试,就需要在自己的开发机上安装pytest。
另外,你还应该安装以下pytest插件:
我们还推荐使用代码检查工具 pylint,它是我们网站上自动化反馈的一部分,也是非常实用的静态代码分析工具。
为了方便,pytest的 pytest-pylint 插件可以让你在命令行里通过pytest运行pylint。
Pylint 的配置可能有点繁琐,所以这篇来自 pylint.readthedocs.io 的教程对入门很有帮助,Real Python 的这篇代码质量:工具与最佳实践概览也一样。
可以使用 Python 内置的工具 pip 来安装和更新 Pytest。
想获得更多技巧的话,Brett Cannon 写了一篇不错的如何为 Python 安装软件包的快速指南,还详细讲解了为什么要用 python -m pip。
想进一步了解 Python 的命令行参数,可以看 Python 文档中的命令行与环境。
注意:Python3和py在你的系统上不一定是 Python 的别名。
请相应地调整下面的安装命令。
要在虚拟环境中安装pytest,请确保在执行命令前环境已激活。
否则,pytest会被安装到全局环境。
PS C:\Users\foobar> py -m pip install pytest pytest-cache pytest-subtests pytest-pylint
Successfully installed pytest-8.3.3 ...
$ 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>
<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 调试器,也就是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、编辑器和一些用于代码检查和调试的实用扩展。
注意: 如果你是通过 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 Start按钮,查找 Edit the system environment variables,然后按回车。
接着点击Environment Variables...:

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

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

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