pytest の使い方

pytest の使い方

(同僚への紹介用に書いてみた。)

## 目標
以下のようにしてテストができるようにします。
```
cd test; pytest test*.py 
```

## 手順
#### pytest のインストール
```
python3 -m pip install pytest
```

(venv を用いている環境で使いたければ、source script/bin/activate としてvenv環境を立ち上げた状況で実施する)

これで、pytestを使う準備はできた。

#### テスト用のサンプルファイルをローカルのフォルダにおく。

```tesT_sample.py:
# content of test_sample.py
def inc(x):
    return x + 1


def test_answer():
    assert inc(3) == 5

```
上記の例の引用元　https://docs.pytest.org/en/stable/index.html

そのフォルダで
`pytest test*.py` を実行してみてください。

```
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item

test_sample.py F                                                     [100%]

================================= FAILURES =================================
_______________________________ test_answer ________________________________

    def test_answer():
>       assert inc(3) == 5
E       assert 4 == 5
E        +  where 4 = inc(3)

test_sample.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 4 == 5
============================ 1 failed in 0.12s =============================
```
のような結果を得るはずです。
上記の例の引用元　https://docs.pytest.org/en/stable/index.html

このようなやり方でpytestを開始することができます。

## あなたのリポジトリでの作業

#### あなたのリポジトリにtestのディレクトリを追加します。

- テストをモジュールの説明になるように開発する
- よりよいテストは、モジュールの動作の説明としてわかりやすいように実装する。
- 何かの理由により実装が壊れたときには、見つけられように書く。
    - 依存ライブラリの仕様変更
    - pythonのバージョンをあげた時に、対応済みの依存ライブラリがみつからない
    - 開発済みの内容にあなたが変更を加えなくても、簡単に実装は壊れてしまうのです。

- 動作テストと性能の評価は別物にする
    - pytestでの動作テストは、典型例での動作の検証にして、実施例の数を減らす。
    - 機械学習などの推論の性能に関する指標の評価はpytestには含めないようにする。

#### テストされるモジュールがモジュールとして呼び出し可能なことの確認
テストされるモジュールがモジュールとしてインストールされている状況をになっていることを確認します。
モジュールのフォルダには`__init__.py`ファイルがあること。
pyproject.toml(もしくはsetup.cfg)を書いてあって、
```
python3 -m pip install -e .
```
で、そのモジュールがインストールに成功していることを確認します。
(-e は --editable optionです。このオプションの指定なしに、pip install をすると、モジュールを改変したたびに、pip install をし直す必要を生じします。 -e を指定しておくと、インストール先には、リンクがおかれるようになるので、毎回pip install をする必要がなくなります。

#### pytest の始め方

##### pytestがすること
`pytest test_*.py` と実行することで、 `test_*.py` のパターンにマッチするファイル中のテストを全て実行します。
testされる関数あるいメソッドは 
`def test_foo()`　などのように`test_`で始まっていることを前提としています。
`test_*.py` ファイルには`if __name__ == "__main__":`の中で、test_foo()などの関数呼び出しは不要です。
pytestはテストに失敗する項目があっても、残りのテストを継続します。
通常のプログラムの場合には、assert　で失敗したときは、それ以降の作業がされないので、
網羅的なテストをすることはできません。

pytest の基本パターンの１つは、入力値に対する結果の期待値とを比較するものです。
[パラメータ化したテスト](https://rinatz.github.io/python-book/ch08-02-pytest/#:~:text=%E3%82%8C%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82-,%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E5%8C%96%E3%81%97%E3%81%9F%E3%83%86%E3%82%B9%E3%83%88,-%C2%B6)

```python:
import pytest

@pytest.mark.parametrize(
)
```
`pytest.mark.parametrize` というキーワードでwebで検索をかければ、それっぽい例題が見つかるはずです。

 
 #### pytestをいきなり書きにくい場合：
 test　ディレクトリでふつうに動くスクリプトを作ります。
 ```
 python3 ファイル.py
 ```
 モジュールが利用するデータファイルがあるときには、そのパスが正しくアクセスできるように
 モジュールのファイルを書き換えます。
 そのスクリプトから、
 モ\\ジュールの関数もしくはモジュールのクラスのメソッドに対応する部分を関数（あるいはメソッド）として切り出します。
 ```
 python3 ファイル.py
 ```
としてまだ動作するはずです。

関数としての抜き出しが全て終わったあとは
```python:
def test_foo():
  assert 1 + 1 == 2
def test_bar():
  assert 1 + 2 == 3
  
# もはやあとには、if __name__ == "__main__": のようなブロックはない。
```
のような状況になっていて 
```
pytest test_foo.py 
```
 
## 依存ライブラリのライセンスの有無によってテストの実行のありなしを制御したい。
pytest.mark.skip
pytest.mark.skipif
を調べてみてください。
 
## class のメソッドに対応するテストを書く
https://docs.pytest.org/en/stable/index.html
やweb上のpytestの記事を読んでください。

## テスト用の入力データファイル
- データファイルの数は少なくしましょう。
  - pytestが目指すのは、プログラムのロジックのテストです。機械学習の推論の精度の評価は含めません。
- git でそのまま管理するのは避けましょう。
- 大量のファイルである場合には、ローカルなコピーがなければ、ストレージからコピーするように書きましょう。
  - Google drive の個人のストレージにデータを置くのはやめましょう。
  - あなたがいなくなった時に、そのデータは消えてしまいます。
- 画像ファイルの場合, `git lfs track "*.jpg" "*.png"` などとしてLFS(large file system)を使っておくこともあります。


## 課題例：
string_list: List[str]
があります。同一の文字列を含んでいる場合があります。
出現頻度の一番高い文字列と、その出現数をカウントする実装を
モジュールファイルに関数として実装してください。

それをテストするpytestのファイルを書いてください。
 
## 課題 例
文字列の出現頻度が
frequency: Dict[str, int] 
のデータ形式で格納されています。
これから、出現頻度の高い順に上位ｎ個を返す関数を実装して、それが動作していることを確認するテストを書いてみてください。

### 課題
上記の課題での上位n個ごとの累積出現頻度を求める関数を実装し、テストを書いてみてください。


## 課題（例）
- 白背景上に、何か白くない物体があります。これをカメラ撮影した後に、背景を除去した、透過型png画像として保存するpythonスクリプトを作って、それが適切に動作していることを、pytestでテストを自動化してください。

#### 課題の分解
- 入力データ、出力データ、期待している内容を明確にする。
- どのようにテストされる内容をモジュール化するか考える。
- 期待される実装になっていないけば失敗することをリストする。
- assert actual == expected　のような内容を考える。

------
- [pytestのとりあえず知っておきたい使い方](https://qiita.com/kg1/items/4e2cae18e9bd39f014d4)
- [pytest で単体テストの方法まとめ](https://qiita.com/waterada/items/6143d80896eb9d89bf2f)