æŠèŠ
æ¡ä»¶ã®äžã§åäœãã¹ããè¡ãéã«pytestã䜿çšããæ©äŒãããããã®éã«åŠãã ããšã䜿çšããæ©èœã«ã€ããŠãŸãšããããšæããã®ããŒãã§äœæããŸããã
pytestãšã¯äœããã©ã®ãããªæ©èœããããç¥ããã£ããã«ããŠããã ãããå¬ããã§ãã
pytestãšã¯
pytestãšã¯ãPythonã§æžããããã¹ããã¬ãŒã ã¯ãŒã¯ã®äžã€ã§ãã
Pythonã®ãã¹ããã¬ãŒã ã¯ãŒã¯ã¯ãä»ã«ãæšæºãã¬ãŒã ã¯ãŒã¯ã§ããunittestãå€éšãã¬ãŒã ã¯ãŒã¯ã®noseãªã©ããããŸãã
pytestã®ç¹åŸŽãšããŠã¯ã以äžã®ãããªç¹ãæããããŸãã
- ã·ã³ãã«ãªãã¹ãã®äœæ
- assertã¹ããŒãã¡ã³ãã䜿çšããŠãã¹ãã³ãŒããæ¯èŒçç°¡åã«èšè¿°ã§ããã
- 詳现ãªã¢ãµãŒã·ã§ã³ãšã©ãŒã¡ãã»ãŒãž
- 倱æããã¢ãµãŒã·ã§ã³ã«å¯ŸããŠè©³çްãªãšã©ãŒã¡ãã»ãŒãžãæäŸãããããã°ã容æã«ãªãã
- ãã©ã¡ãŒã¿åãã¹ã
- pytest.mark.parametrize ãã³ã¬ãŒã¿ã䜿çšããŠãåããã¹ããç°ãªããã©ã¡ãŒã¿ã§ç¹°ãè¿ãå®è¡ããããšãã§ããã
- ãã£ã¯ã¹ãã£
- ãã£ã¯ã¹ãã£ã䜿çšãããšããã¹ãã®ã»ããã¢ãããšã¯ãªãŒã³ã¢ããã®ããžãã¯ãåå©çšã§ããã
- è±å¯ãªã³ãã³ãã©ã€ã³ãªãã·ã§ã³
- ãã¹ãã®å®è¡æ¹æ³ã现ããå¶åŸ¡ããããã®å€æ°ã®ã³ãã³ãã©ã€ã³ãªãã·ã§ã³ãæäŸããŠããã
- å€ãã®ãããžã§ã¯ããšã®äºææ§
- åçŽãªã¹ã¯ãªããããè€éãªã¢ããªã±ãŒã·ã§ã³ãã©ã€ãã©ãªãŸã§å¹ åºããããžã§ã¯ãã§äœ¿çšããããšãã§ããã
ã€ãã£ãŠã¿ã
ç°å¢
macOS
Python: 3.11.0
pytest: 8.3.3
ãã£ã¬ã¯ããªæ§æ
ä»åã¯ä»¥äžã®ãããªæ§æã§æ€èšŒããŠããŸãã
. âââ src â  âââ code.py âââ tests âââ __init__.py âââ test.py
åºæ¬ç³»
code.py
def sum_numbers(a, b):
return a + b
äžèšã®ãããªãè¶³ãç®ã®çµæãè¿ã颿°ã«å¯ŸããŠä»¥äžã®ãããªãã¹ãã³ãŒããäœæããããšã§ããã¹ãããããšãã§ããŸãã
äžèšã®ãã¹ãã³ãŒãã§ã¯ããã¹ã颿°å
ã§äžèšã®é¢æ°ãåŒã³åºãã颿°ã®æ»ãå€ãšæåŸ
å€ãäžèŽããŠãããassertããŠããŸãã
test.py
from src import code
def test_sum_numbers():
result = code.sum_numbers(3, 4)
assert result == 7
ã¿ãŒããã«ã§ãã¹ããå®è¡ããŸãã
$ pytest tests/test.py
以äžã®ããã«äžã€ã®é¢æ°ã«å¯ŸããŠãã¹ããéã£ãŠããããšãããããŸãã
pytest tests/test.py ================================================================= test session starts ================================================================= platform darwin -- Python 3.11.0, pytest-8.3.3, pluggy-1.5.0 rootdir: /Users/pytest collected 1 item tests/test.py . [100%] ================================================================== 1 passed in 0.01s ==================================================================
fixture
fixtureã䜿çšããããšã§ããã¹ãã®ããã®åæºåïŒã»ããã¢ããïŒãåŸåŠçïŒã¯ãªãŒã³ã¢ããïŒããã¹ã颿°ããåé¢ããŠè¡ãããšãã§ããŸãã
ãŸãããããã®åŠçãè€æ°ã®ãã¹ãéã§å
±æããããèªåçã«äœ¿çšããããã«èšå®ããããšãã§ããŸãã
ç¹ã«ãDBæ¥ç¶ããã¡ã€ã«ã·ã¹ãã ã®äœ¿çšãªã©äºåäœæ¥ãåŸåŠçãå¿
èŠãªå Žåã«åœ¹ç«ã¡ãŸãã
ãã¡ã€ã«æäœãè¡ãã³ãŒããäŸã«æ€èšŒããŸãã
code.py
# äžæãã¡ã€ã«ã«ããŒã¿ãæžã蟌ã颿°
def write_file(path):
with open(path, 'w') as f:
f.write('test data')
fixtureã䜿çšãããã¹ã颿°ãå®è¡ãããååŠçãšããŠãäžæãã¡ã€ã«ãäœæããåŸåŠçãšããŠãã¹ã颿°å®è¡åŸã«ãã¡ã€ã«ãåé€ããŸãã
ãã¹ã颿°ã®åŒæ°ã«fixtureã®é¢æ°åãæå®ããŸãã
颿°ã«å¯ŸããŠã以äžã®ããã«ãã³ã¬ãŒã¿ãšããŠfixtureãã»ããããããšã§ããã¹ãã³ãŒãã®ååŠçãå®çŸ©ã§ããŸãã
yieldã®äžã®è¡ã«åŸåŠçãèšè¿°ããããšã§ããã¹ã颿°å®è¡åŸã«å®çŸ©ããåŸåŠçãå®è¡ããããšãã§ããŸãã
test.py
import tempfile
import pytest
from src import code
@pytest.fixture
def temp_file():
fd, path = tempfile.mkstemp()
os.close(fd) # ãã¡ã€ã«ãã£ã¹ã¯ãªãã¿ãéãã
yield path # ãã¹ãã«ãã¡ã€ã«ãã¹ãæäŸ
os.remove(path) # ãã¹ãåŸã«ãã¡ã€ã«ãåé€
# ãã£ã¯ã¹ãã£ã䜿çšãããã¹ã颿°
def test_write_file(temp_file):
code.write_file(temp_file) # ãã¡ã€ã«ã«ããŒã¿ãæžã蟌ã
# ãã¡ã€ã«ã®å
容ã確èª
with open(temp_file, 'r') as f:
assert f.read() == "test data"
pytest tests/test.py ================================================================= test session starts ================================================================= platform darwin -- Python 3.11.0, pytest-8.3.3, pluggy-1.5.0 rootdir: /Users/pytest collected 1 item tests/test.py . [100%] ================================================================== 1 passed in 0.01s ==================================================================
mark.paramiterize
pytest ã® @pytest.mark.parametrize ãã³ã¬ãŒã¿ã䜿çšããããšã§ãåããã¹ã颿°ãç°ãªãåŒæ°ã»ããã§è€æ°åå®è¡ããããšãã§ããŸãã颿°ãç°ãªãå ¥åã§ã©ã®ããã«åäœãããããã¹ãããå Žåã«äŸ¿å©ãªæ©èœã§ãã
以äžã®ïŒã€ã®æ°å€ã®ç©ãè¿ã颿°ã«å¯ŸããŠãã¹ããè¡ããŸãã
code.py
# ãã¹ããã颿°ïŒäŸïŒäºã€ã®æ°å€ã®ç©ãè¿ãïŒ
def multiply(a, b):
return a * b
ããã§ã¯ãå®çŸ©ããã5ã€ã®ã¿ãã«ãé çªã«äœ¿çšãã颿°ãïŒåå®è¡ãããŸãã
test.py
import pytest
from src import code
data = [
(2, 3, 6), # 2 * 3 = 6
(5, 5, 25), # 5 * 5 = 25
(0, 10, 0), # 0 * 10 = 0
(-1, -1, 1), # -1 * -1 = 1
(-1, 2, -2) # -1 * 2 = -2
]
# ãã¹ãã±ãŒã¹ããã©ã¡ãŒã¿å
@pytest.mark.parametrize("a, b, expected", data)
def test_multiply(a, b, expected):
assert code.multiply(a, b) == expected
pytest tests/test.py ================================================================= test session starts ================================================================= platform darwin -- Python 3.11.0, pytest-8.3.3, pluggy-1.5.0 rootdir: /Users/pytest collected 5 items tests/test.py ..... [100%] ================================================================== 5 passed in 0.01s ==================================================================
monkeypatch
pytestã§monkeypatchãã£ã¯ã¹ãã£ã䜿çšãããšããã¹ãäžã«é¢æ°ãç°å¢å€æ°ã屿§ãªã©ãåçã«å€æŽããããšãã§ããŸããããã¯ããã¹ãã®ããã«ã³ãŒãã®æåãäžæçã«å€æŽãããå Žåã«äœ¿ããŸããäŸãã°ãå€éšã®APIãåŒã³åºã颿°ãã¢ãã¯ããããšãªã©ãå¯èœã§ãã
å€éšãµãŒãã¹ããããŒã¿ãååŸãã颿°get_external_dataãäœæãããã®é¢æ°ã®æ»ãå€ããã¹ãäžã«ã¢ãã¯ããŸãã
code.py
# ãã¹ã察象ã®é¢æ°ïŒäŸïŒå€éšããŒã¿ãœãŒã¹ããããŒã¿ãååŸïŒ
def get_external_data():
# å®éã«ã¯å€éšAPIãåŒã³åºããªã©ã®åŠç
return "å€éšAPIãªã©ã®ããååŸããããŒã¿"
ãã¹ãçšã®ã¢ãã¯é¢æ°mock_get_external_dataãå®çŸ©ãããã¹ãäžã«å®éã®é¢æ°ã®ä»£ããã«äœ¿çšããŸãã
monkeypatch.setattrã䜿ã£ãŠãget_external_data颿°ãmock_get_external_dataã«çœ®ãæããŸãã
ã¢ãã¯ã䜿çšããŠé¢æ°ãåŒã³åºããæåŸ
ãããçµæïŒã¢ãã¯ããã®æ»ãå€ïŒãåŸãããããæ€èšŒããŸãã
test.py
import pytest
from src import code
# ãã¹ãã§äœ¿çšããã¢ãã¯é¢æ°
def mock_get_external_data():
return "mocked_data"
# ãã¹ã颿°
def test_external_data(monkeypatch):
# get_external_data颿°ãã¢ãã¯é¢æ°ã§çœ®ãæã
monkeypatch.setattr("src.code.get_external_data", mock_get_external_data)
# ã¢ãã¯ã䜿çšããŠãã¹ããå®è¡
result = code.get_external_data()
assert result == "mocked_data"```
pytest tests/test.py ================================================================= test session starts ================================================================= platform darwin -- Python 3.11.0, pytest-8.3.3, pluggy-1.5.0 rootdir: /Users/pytest collected 1 item tests/test.py . [100%] ================================================================== 1 passed in 0.01s ==================================================================
moto
motoã¯AWSãµãŒãã¹ã䜿çšããã³ãŒãã®åäœãã¹ããã¢ãã¯ããŠãããã©ã€ãã©ãªã§ãã
ä»åã¯S3ãäŸãšããŠè¡ããŸãããsqs, sesãªã©å¹
åºããµãŒãã¹ã«å¯Ÿå¿ããŠããããã§ãã
S3ã«ãã¡ã€ã«ãã¢ããããŒãããã³ãŒããäœæããã¢ãã¯ããS3ç°å¢ã§åäœãã¹ããè¡ãããšãã§ãããæ€èšŒããŸãã
code.py
import boto3
def upload_to_s3(local_file_path, bucket_name, s3_file_name):
s3 = boto3.client('s3')
s3.upload_file(local_file_path, bucket_name, s3_file_name)
S3ã®ã¢ãã¯ç°å¢ãäœæããããã«ãã¡ã€ã«ãã¢ããããŒãããæ£ãããã¡ã€ã«ãã¢ããããŒããããŠããã確èªããŸãã
äžèšã§èšè¿°ããfixtureã®æ©èœã䜿çšãããã¹ãã³ãŒããåŒã³åºãååŸã«ãã¡ã€ã«äœæåŠçãšåé€åŠçãè¡ãããã«ããŸãã
test.py
import boto3
import pytest
from moto import mock_aws
from src.code import upload_to_s3
import os
@pytest.fixture
def s3_setup():
# `moto` ã䜿ã£ã S3 ã¢ãã¯ã®äœæ
with mock_aws():
# S3ã®ã¢ãã¯ç°å¢ãã»ããã¢ãã
s3 = boto3.client('s3', region_name='us-east-1')
bucket_name = "test-bucket"
s3.create_bucket(Bucket=bucket_name)
# äžæãã¡ã€ã«ãäœæ
test_file_path = "test_file.txt"
with open(test_file_path, "w") as f:
f.write("This is a test file.")
yield {
'bucket_name': bucket_name,
'test_file_path': test_file_path
}
# ãã¹ãçµäºåŸã«ãã¡ã€ã«ãåé€
if os.path.exists(test_file_path):
os.remove(test_file_path)
def test_upload_to_s3(s3_setup):
# ã¢ãã¯ãããS3ãã±ããã«ãã¡ã€ã«ãã¢ããããŒã
upload_to_s3(s3_setup['test_file_path'], s3_setup['bucket_name'], 'uploaded_test_file.txt')
# S3ã¯ã©ã€ã¢ã³ããäœæããŠã¢ããããŒããæåããã確èª
s3 = boto3.client('s3')
# head_objectã§ã¢ããããŒãããããã¡ã€ã«ã®ååšã確èª
response = s3.head_object(Bucket=s3_setup['bucket_name'], Key='uploaded_test_file.txt')
# head_objectãäŸå€ãæããªããã°ãã¡ã€ã«ãååšããããšã«ãªã
assert response is not None
pytest tests/test.py ================================================================= test session starts ================================================================= platform darwin -- Python 3.11.0, pytest-8.3.3, pluggy-1.5.0 rootdir: /Users/pytest collected 1 item tests/test.py . [100%] ================================================================== 1 passed in 0.39s ==================================================================
æåŸã«
ä»åã¯èªåãæ¥åã®äžã§ç¹ã«äœ¿çšããæ©èœã«ã€ããŠãŸãšããŸããã
衚é¢çã§ã¯ãããŸãããpytestã®åºæ¬çãªæ©èœãåŠã³çŽãããšãã§ããŸããã
ãããã®ä»ã«ãæ§ã
ãªãµãŒãããŒãã£ãã©ã°ã€ã³ããã¹ãã®å®è¡æ¹æ³ãå¶åŸ¡ããã³ãã³ãã©ã€ã³ãªã©ãããããã§ãã
èªãã§ããã ãããããšãããããŸãïŒ
åèæç®
- https://docs.pytest.org/en/latest/how-to/assert.html
- https://docs.pytest.org/en/stable/explanation/fixtures.html
- https://docs.pytest.org/en/stable/how-to/parametrize.html
- https://zenn.dev/onigiri_w2/articles/5e6cf4d3ba9ed5
- https://zenn.dev/tk_resilie/articles/python_test_template
- https://qiita.com/_YukiOgawa/items/a436877f6b80dff1b1a3
- https://qiita.com/abemaru/items/7a521a8cf6d755c0f34c