概略
ただの箇条書きです。
一番重要なのは、from ... import ...
が出来るかどうかです。
自作モジュールのディレクトリと、
テストモジュールのディレクトリ階層がどちらも深いときに。
3階層以上で挙動が変わるみたいです。
ストーリー
以下の構造のように階層が深い時のサンプルコードが、
ググっても見つからなかったので記事にしました。
.
├tests
│├mod
││└test_module.py
├src
│├mod
││└module.py
以下のようなものは多く見かけました。
.
├tests
│└test_module.py
├src
│└module.py
ただ、「階層が深いから動かない」という理由もよくわからないです。
とりあえずこうすれば動く、ということは分かりました。
公式ドキュメントの文章が読みづらくて苦労してたどり着きました。
なんか上手くいかない
やりたいことは、
pytest でモジュールコードとテストコードをディレクトリで分離して unittest を実行するには?
なのですが、ここに書かれた方法だけでは、モジュール実行オプションが無いとimportできませんでした。
本記事はPython標準ライブラリのunittestとは無関係です。
launch.json
に、モジュールの数だけの全パターンのデバッグ構成を記述する必要があります。
チュートリアル程度の小さなプロジェクトなら問題なさそうですが、
プロジェクトが大きくなるほどナンセンスな方法と言えます。
たどり着いたディレクトリ構造
__init__.py
は不要です。__init__.py
はpypiにライブラリを公開するときに考えるくらいで良さそう。
__init__.py
が無くても正常に動作するし、インテリセンスも効きます。
今回は業務で自動化ツールを早いとこ作るためにテスト駆動するだけなので無視しても平気でしょう。(きっと)
※大事なのは.env
だけです。
Python 3.3以降では、PEP420で定義された名前空間パッケージ(namespace packages)の導入により、このファイルがなくてもディレクトリがパッケージとして認識されるようになりました。
__init__.py
ファイルは以下のような目的で使用されますが、必須ではありません:
- パッケージの初期化コードを実行する。
- パッケージレベルでの変数を定義する。
- パッケージ内のモジュールを特定の方法でインポートするためのショートカットを提供する。
しかし、__init__.py
がない場合でも、Pythonはそのディレクトリをパッケージとして認識し、その中のモジュールやサブパッケージをインポートすることができます。これにより、より柔軟なパッケージ構造が可能となります。もちろん、引き続き__init__.py
を使用してパッケージの初期化を制御したり、必要な情報を提供することもできます。
テスト
実際に行ったテストです。
import pytest
from src.animal.mammal import human
from src.star import satellite
def test_human_name():
target=human("Jane Doe")
ans=target.name
assert ans=="Jane Doe"
def test_satellite_name():
target=satellite("lua")
ans=target.name
assert ans=="lua"
def test_human_foot_count():
ans=human.howmanyfoot()
assert ans==2
if __name__ == "__main__":
pass
テスト対象モジュール
class satellite():
def __init__(self, name:str):
self.name=name
def name(self) -> str:
return self.name
class planet():
def __init__(self, name:str):
self.name=name
def name(self) -> str:
return self.name
class human():
def __init__(self, name:str):
self.name=name
@classmethod
def name(self) -> str:
return self.name
@staticmethod
def howmanyfoot() -> int:
return 2
パスの設定
「Currrent file デバッグ時」のfrom import
の処理をするときのエラー回避策です。
これを置いてあげることで、「Pythonのパス」や「他の階層の自作モジュール」を認識してくれるみたいです。
PYTHONPATH=./
condaなどの仮想環境を使用していても認識してくれます。
以下のようなスクリプトを実行すると、
環境が認識できているか把握できます。
import sys
print(sys.path)
上記のimport_test.py
の実行結果として、
./src/
とかが入っていればOKです。
デバッグ構成
3種類のデバッグ方法を定義しています。
上から順に、
- test_name モジュールをモジュールデバッグ
- mammalモジュールをモジュールデバッグ
- vscodeでカーソルを置いているファイルをデバッグ
私個人の見解ですが、モジュールデバッグは、
メインモジュールを指定する場合などの用途に限定されます。
多くの場合、編集中のタブでF5
キーを押して、カレントファイルをデバッグする→テスト実行の流れです。
{
// IntelliSense を使用して利用可能な属性を学べます。
// 既存の属性の説明をホバーして表示します。
// 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: モジュール test_name",
"type": "python",
"request": "launch",
"cwd": "${workspaceFolder}",
"module": "tests.test_mod.test_name"
},{
"name": "Python: モジュール mammal",
"type": "python",
"request": "launch",
"cwd": "${workspaceFolder}",
"module": "src.animal.mammal"
},
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
}
]
}
ワークスペース設定
- テストモジュールを置いているフォルダ
- どのテスト用フレームワークを使用するか
- python環境はどれを使用するのか記述したファイルを指定
- condaなどの仮想環境を使用していてもこれで認識してくれます。
{
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.nosetestsEnabled": false,
"python.testing.pytestEnabled": true,
"python.envFile": "${workspaceFolder}/.env"
}
よくよく調べると、デフォルト設定で.env
の場所は指定されているんですね。
テスト結果
環境
- Windows 10 Latest
- Python 3 Latest
- Miniconda Latest
- Pip Latest
- pytest Latest
※conda
を利用しているのは、「仮想環境の切り分け」・「仮想環境へのPythonインストール」までです。
仮想環境内でライブラリのインストールはpip
を利用しています。
まとめ
pyhonムズイ。
C系のnamespaceが恋しいです。
続き
こちらの記事のほうが後に書いたのでもしかしたら参考になるかもです。
ぜひご覧ください。
Excelsior!