Pythonトラックでのテスト

ExercismでPythonの演習をテストする方法を学びます。


ウェブサイトのテストランナーには、pytestを使っています。Pythonトラックの演習のテストをローカルでダウンロードして実行したい場合は、開発マシンにpytestをインストールする必要があります。次のpytestプラグインもインストールしておきましょう。

コードをリントするプログラムpylintの使用もおすすめします。pylintはウェブサイトの自動フィードバックの一部であり、非常に役立つ静的コード解析ツールです。使いやすさを考えると、pytest用のpytest-pylintプラグインを使えば、コマンドラインからpytest経由でpylintを実行できます。

Pylintの設定は少し大変に感じるかもしれません。始める際には、このpylint.readthedocs.ioのチュートリアルや、Real PythonのCode Quality: Tools and Best Practicesという概要が役立つでしょう。

pytestのインストール

pytestは、Pythonに標準で付属するユーティリティpipを使ってインストールしたり更新したりできます。

さらに詳しいヒントについては、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値をターミナルに出力します。以下は、失敗したテストの一般的な例です。

$(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フラグを使うと、テストは通常どおり実行されますが、最初のテスト失敗で実行が停止します。これは、一度に1つのタスクやテストの失敗をデバッグしたいときに便利です。

$(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でPythonデバッガー(PDB)を使う

「プロのようにデバッグする」をしたいときは、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に入ると、現在のスコープを見ながらコードを1行ずつ実行したり、変数の値やさまざまな関数のシグネチャを確認したりできます。PDBモジュールの詳細は、PDBに関するPythonドキュメントにあります。さらに、PDBに関するpytestのドキュメントとReal Pythonのこちらのガイドも非常に役立ちます。

IDEを拡張する

テストやコードの改善に役立つツールでIDEを拡張したい場合は、ツールのページをチェックしてみてください。そこでは、複数のIDEやエディター、そしてリンティングやデバッグに役立ついくつかの拡張機能を紹介しています。

補足情報

PATHにpythonを追加する

注意: WindowsでPythonをPSF Installer経由でインストールした場合、コマンドはpython3ではなくpyになります。

モジュールを実行するたびに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_を検索してEnterキーを押します。次に、Environment Variables...を押します。

青いボタンを押してください(笑)

次に、_User variables_の中からPath変数を見つけ、それを選択してEdit...をクリックします。

path変数を選択しているところ

そして、画像に示されているように新しい行を追加します。このとき、<python_directory>をPythonのインストールディレクトリに置き換えてください。

pathにpythonを追加する

MacOS/Linux

以下は、bashシェルを使っているほとんどのLinuxとMacOSの環境で動作するはずです。コマンドは、Linuxディストリビューションや、fishまたはzshシェルを使っているかどうかによって変わる場合があります。<python_directory>は、python3 -c "import os, sys; print(os.path.dirname(sys.executable))"の出力に置き換えてください。

export PATH="$PATH:<python_directory>"