Poetry で pytest

pytest の使い方がときどきよくわからなくなるので備忘録。

# ディレクトリ構成

- 単体テストは tests/test_hello.py に記述
- hello/hello.py で Hello Class を定義
- hello/main.py で Hello Class インスタンスを作成，say メソッドを実行した際に ``Hello, World!`` を print

```
├── hello
│   ├── __init__.py
│   ├── hello.py
│   └── main.py
├── poetry.lock
├── pyproject.toml
└── tests
    └── test_hello.py
```

# Poetry のインストール

[Poetry Installation](https://python-poetry.org/docs/#installation)


```
curl -sSL https://install.python-poetry.org | python3 -
```

# pyproject.toml

``poetry init`` を実行するか，エディタ等で pyproject.toml を作成します。

```toml:pyproject.toml
[tool.poetry]
name = "hello"
version = "0.1.0"
description = "getting started with pytest"
authors = ["Your Name <you@example.com>"]

[tool.poetry.dependencies]
python = "^3.11"
pytest = "^7.4.3"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.poetry.scripts]
hello = "hello.main:main"
```

``poetry init`` を使った場合は，``poetry add pytest`` も実行する必要があります。

poetry run hello で hello/main.py の main 関数を実行します。
``name = "hello"`` と hello ディレクトリで名前に不整合があると，以下の ``No module`` エラーが発生します。

```bash
    from hello.hello import Hello
E   ModuleNotFoundError: No module named 'hello'
```

# poetry install / update

poetry install を実行します。

```bash
$ poetry install
Updating dependencies
Resolving dependencies... (0.1s)

No dependencies to install or update

Writing lock file

Installing the current project: hello (0.1.0)
```

# テストコードを記述

Hello Class インスタンスを作成，greeting の返り値として ``Hello, World!`` が返されることを期待します。

```python:test_hello.py
import pytest
from hello.hello import Hello

def test_hello():
    hello = Hello()
    assert hello.greeting == "Hello, World!"
```


# pytest を実行

```shell
$ poetry run pytest -v
========================= test session starts ==========================
platform darwin -- Python 3.11.6, pytest-7.4.3, pluggy-1.3.0 -- /Users/foo/Library/Caches/pypoetry/virtualenvs/hello-7y5vTctl-py3.11/bin/python
cachedir: .pytest_cache
rootdir: /Users/foo/lab
collected 0 items / 1 error

================================ ERRORS ================================
_________________ ERROR collecting tests/test_hello.py _________________
ImportError while importing test module '/Users/foo/lab/tests/test_hello.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/opt/homebrew/Cellar/python@3.11/3.11.6_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/importlib/__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
tests/test_hello.py:2: in <module>
    from hello.hello import Hello
E   ImportError: cannot import name 'Hello' from 'hello.hello' (/Users/foo/lab/hello/hello.py)
======================= short test summary info ========================
ERROR tests/test_hello.py
!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!
=========================== 1 error in 0.03s ===========================
```

まだ何も Class を記述していないので当然テストに失敗します。

# Hello Class を記述

以下のように Hello Class を定義します。

```python:hello/hello.py
class Hello:
    def __init__(self):
        self.greeting = "Hello, World!"

    def say(self):
        print(self.greeting)
```

# テストを再実行

```shell
$ poetry run pytest -v
========================= test session starts ==========================
platform darwin -- Python 3.11.6, pytest-7.4.3, pluggy-1.3.0 -- /Users/foo/Library/Caches/pypoetry/virtualenvs/hello-7y5vTctl-py3.11/bin/python
cachedir: .pytest_cache
rootdir: /Users/foo/lab
collected 1 item

tests/test_hello.py::test_hello PASSED                           [100%]

========================== 1 passed in 0.00s ===========================
```

テストをパスしました。

# main.py を記述

メインのコードを記述します。お約束の ``Hello, World!`` を print するだけです。

```python:hello/main.py
from hello.hello import Hello

def main():
    greeting = Hello()
    greeting.say()
```

``poetry run`` で期待通りの結果が得られました。

```bash
$ poetry run hello
Hello, World!
```