LoginSignup
13

pythonでpytestを使ってテスト駆動開発するときのディレクトリ構造

Last updated at Posted at 2020-10-14

概略

ただの箇条書きです。

一番重要なのは、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が無くても正常に動作するし、インテリセンスも効きます。
今回は業務で自動化ツールを早いとこ作るためにテスト駆動するだけなので無視しても平気でしょう。(きっと)
image.png

※大事なのは.envだけです。

Python 3.3以降では、PEP420で定義された名前空間パッケージ(namespace packages)の導入により、このファイルがなくてもディレクトリがパッケージとして認識されるようになりました。

__init__.pyファイルは以下のような目的で使用されますが、必須ではありません:

  • パッケージの初期化コードを実行する。
  • パッケージレベルでの変数を定義する。
  • パッケージ内のモジュールを特定の方法でインポートするためのショートカットを提供する。

しかし、__init__.pyがない場合でも、Pythonはそのディレクトリをパッケージとして認識し、その中のモジュールやサブパッケージをインポートすることができます。これにより、より柔軟なパッケージ構造が可能となります。もちろん、引き続き__init__.pyを使用してパッケージの初期化を制御したり、必要な情報を提供することもできます。

テスト

実際に行ったテストです。

test_name.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

テスト対象モジュール

star.py
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
mammal.py
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のパス」や「他の階層の自作モジュール」を認識してくれるみたいです。

.env
PYTHONPATH=./

condaなどの仮想環境を使用していても認識してくれます。

以下のようなスクリプトを実行すると、
環境が認識できているか把握できます。

import_test.py
import sys
print(sys.path)

上記のimport_test.pyの実行結果として、
./src/とかが入っていればOKです。

デバッグ構成

3種類のデバッグ方法を定義しています。
上から順に、

  • test_name モジュールをモジュールデバッグ
  • mammalモジュールをモジュールデバッグ
  • vscodeでカーソルを置いているファイルをデバッグ

私個人の見解ですが、モジュールデバッグは、
メインモジュールを指定する場合などの用途に限定されます。

多くの場合、編集中のタブでF5キーを押して、カレントファイルをデバッグする→テスト実行の流れです。

launch.json
{
    // 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などの仮想環境を使用していてもこれで認識してくれます。
settings.json
{
    "python.testing.pytestArgs": [
        "tests"
    ],
    "python.testing.unittestEnabled": false,
    "python.testing.nosetestsEnabled": false,
    "python.testing.pytestEnabled": true,
    "python.envFile": "${workspaceFolder}/.env"
}

よくよく調べると、デフォルト設定で.envの場所は指定されているんですね。

image.png

テスト結果

image.png

環境

  • Windows 10 Latest
  • Python 3 Latest
  • Miniconda Latest
  • Pip Latest
  • pytest Latest

condaを利用しているのは、「仮想環境の切り分け」・「仮想環境へのPythonインストール」までです。
仮想環境内でライブラリのインストールはpipを利用しています。

まとめ

pyhonムズイ。
C系のnamespaceが恋しいです。

続き

こちらの記事のほうが後に書いたのでもしかしたら参考になるかもです。
ぜひご覧ください。

Excelsior!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13