pytest をさわりながらよく使いそうなものまとめてみた

# はじめに
pytestに触れてみたので本記事は備忘録を兼ねてその学習内容をまとめたものになります。



# 対象の方
とりあえずコピペで動かしながらざっくりpytestの書き方を把握されたい方


# インストール

pytestをインストールします。

```
pip install pytest
```

# 環境
- python：3.12.2
- pytest：8.2.2

# とりあえず動かしてみる

足し算を行うシンプルな関数を準備します。

```python:sample.py
def add(a, b):
    return a + b
```

足し算を行う上記の関数をテストするためのテストコードを作成します。


```python:test_sample.py
from sample import add

def test_add():
    assert add(1, 2) == 3
    assert add(-1, 1) == 0
    assert add(0, 0) == 0
```

:::note warn

テストコードのファイル名、メソッド名は `test～` で記載する。

https://docs.pytest.org/en/8.2.x/explanation/goodpractices.html#test-discovery

:::

下記のコマンドでテスト実行します。

```
pytest test_sample.py
```

テストを実行した結果は次の通りです。テストが1つ通っている事が確認できました。

```
test_sample.py .                                                                                                           [100%] 

======================================================= 1 passed in 0.01s =======================================================
```


:::note info

テストコード内でprint()出力の内容を見たい時には、以下のように「--capture=no」オプションを設定するとログの内容を確認可能。

```
pytest test_sample.py --capture=no
```

参考：https://qiita.com/yyyyy__/items/59507d041458829fb39a

:::

# よく使いそうなもの

##  1. assert文

以下に使用例を記載。`assert`の後にテストしたい条件を記述する。

```python:test_sample.py
def test_example():
    assert 1 == 1  # 成功するテスト
    assert 1 == 2  # 失敗するテスト
```

動かしてみると次のように `1==2` で失敗していることを確認。

```
    def test_example():
        assert 1 == 1  # 成功するテスト
>       assert 1 == 2  # 失敗するテスト
E       assert 1 == 2

test_sample.py:3: AssertionError
```


## 2. pytest.mark
### ◆ pytest.mark.parametrize

同じテストを異なるデータセットで複数回実行したい場合は `@pytest.mark.parametrize` を使用するとまとめてテストができて便利です。

```python:sample.py
def add(a, b):
    print('テスト開始')
    return a + b
```

```python:test_sample.py
import pytest
from sample import add

@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 3),
    (-1, 1, 0),
    (0, 0, 0),
])

def test_add(a, b, expected):
    assert add(a, b) == expected
```

動かしてみるとデータセットの回数実行されていることを確認。（今回は3回）

```
test_sample.py テスト開始
.テスト開始
.テスト開始
.

======================================================= 3 passed in 0.01s ======================================================= 
```

### ◆ pytest.mark.skip

一部の処理をスキップしたい場合に使用します。

```python:test_sample.py
import pytest

@pytest.mark.skip(reason="このテストはまだ実装されていません")
def test_not_implemented_yet():
    print('エラー')
    assert False

def test_ready_to_run():
    print('実行')
    assert True
```

実行するとテストが1件passしており、もう1件はスキップされている事を確認。


```
test_sample.py s実行
.

================================================= 1 passed, 1 skipped in 0.01s ==================================================
```

:::note info
実行結果の略称について
- s：テストがスキップされたことを示す
- .：テストがパスしたことを示す
:::



## 3. pytest.raises

意図的に例外を発生させてみます。エラーが発生すればテストはpass、エラーが発生しなければfailedになります。

```python:test_sample.py
import pytest

def test_raises_exception():
    with pytest.raises(ValueError):
        int("a")  #  エラーを発生させる
```

動かしてみるとテストがpassしており、想定通りエラーを起こせていたことを確認。

```
test_sample.py .

======================================================= 1 passed in 0.01s =======================================================
```

## 4. Fixtures

fixtureを利用する事でテストの前後に処理を設けることができます。

```python:test_sample.py
import pytest

@pytest.fixture
def temp_file(tmpdir):
    # テストの前処理:ファイルを一時的に作成
    print('テスト開始')
    temp_file = tmpdir.join("temp_file.txt")
    temp_file.write("Hello, World!")
    
    # テスト実行:テストには作成したファイルを渡す
    yield temp_file
    
    # テストの後処理:ファイルを削除
    temp_file.remove()
    print('テスト終了')

def test_temp_file(temp_file):
    # テスト: ファイルに書き込まれた文字の一致、ファイルの存在
    assert temp_file.read() == "Hello, World!"
    assert temp_file.check()
```

動かしてみるとテストがpassしている事を確認。

```
test_sample.py テスト開始
.テスト終了

======================================================= 1 passed in 0.02s =======================================================
```


# 参考

https://rinatz.github.io/python-book/ch08-02-pytest/

https://myenigma.hatenablog.com/entry/2021/01/31/212743

https://docs.pytest.org/en/8.2.x/contents.html

https://zenn.dev/onigiri_w2/articles/5e6cf4d3ba9ed5#%E5%9F%BA%E6%9C%AC%E4%B8%AD%E3%81%AE%E5%9F%BA%E6%9C%AC