ã¯ããã«ïŒPytestãšã¯ïŒ ð€
Pytestã¯ãPythonã§åºã䜿ãããŠãããªãŒãã³ãœãŒã¹ã®ãã¹ããã¬ãŒã ã¯ãŒã¯ã§ããPythonæšæºã®unittest
ãšæ¯èŒããŠãããã·ã³ãã«ã§çŽæçãªãã¹ãã³ãŒãã®èšè¿°ãå¯èœã«ããè±å¯ãªæ©èœãšé«ãæ¡åŒµæ§ãæäŸããŸããå°èŠæš¡ãªãŠããããã¹ãããè€éãªæ©èœãã¹ããAPIãã¹ããŸã§ãå¹
åºããã¹ãããŒãºã«å¯Ÿå¿ã§ãããããå€ãã®Pythonéçºè
ã«ãšã£ãŠããã¡ã¯ãã¹ã¿ã³ããŒããšãªãã€ã€ãããŸãã
Pytestãéžã¶çç±ã¯ããã€ããããŸãããäž»ãªç¹åŸŽãšããŠã¯ä»¥äžã®ç¹ãæããããŸãã
- ç°¡æœãªãã¹ãèšè¿°: Pythonæšæºã®
assert
æã䜿çšããã ãã§ãã¹ãã±ãŒã¹ãäœæã§ããunittest
ã®ãããªç¹å®ã®ã¯ã©ã¹ç¶æ¿ãã¡ãœããåŒã³åºãïŒself.assertEqual
ãªã©ïŒã¯äžèŠã§ããããã«ããããã€ã©ãŒãã¬ãŒãã³ãŒãïŒå®åçãªã³ãŒãïŒãå€§å¹ ã«åæžãããèªã¿ãããä¿å®ãããããã¹ãã³ãŒãã«ãªããŸãã - 匷åãªãã£ã¯ã¹ãã£æ©èœ: ãã¹ãã®å®è¡ååŸã®æºåïŒã»ããã¢ããïŒãåŸçä»ãïŒãã£ã¢ããŠã³ïŒãç°¡åãã€æè»ã«ç®¡çã§ããŸããããŒã¿ããŒã¹æ¥ç¶ãäžæãã¡ã€ã«ã®äœæã»åé€ãªã©ãå ±éã®åŠçããã£ã¯ã¹ãã£ãšããŠå®çŸ©ãããã¹ãé¢æ°ã«æ³šå ¥ïŒDependency InjectionïŒããããšã§ãã³ãŒãã®åå©çšæ§ãé«ããŸãã
- è±å¯ãªã¢ãµãŒã·ã§ã³æ
å ±: ãã¹ãã倱æããéã
assert
æã®æ¯èŒå¯Ÿè±¡ãšãªã£ãå€ã®è©³çŽ°ãªæ å ±ïŒå·®åãªã©ïŒã衚瀺ãããããã倱æåå ã®ç¹å®ã容æã«ãªããŸãã - ãã©ã¡ãŒã¿åãã¹ã: åããã¹ãããžãã¯ãç°ãªãå ¥åããŒã¿ã§ç¹°ãè¿ãå®è¡ããããã©ã¡ãŒã¿åããç°¡åã«å®çŸã§ããŸããããã«ãããå°ãªãã³ãŒãã§ç¶²çŸ çãªãã¹ããå¯èœã«ãªããŸãã
- ããŒã«ãŒæ©èœ: ãã¹ãé¢æ°ã«ãããŒã«ãŒããšåŒã°ããã¡ã¿ããŒã¿ãä»äžããç¹å®ã®ããŒã«ãŒãä»ãããã¹ãã®ã¿ãå®è¡ããããã¹ããããããããããšãã§ããŸããäŸãã°ããslowãïŒæéã®ããããã¹ãïŒããdatabaseãïŒããŒã¿ããŒã¹ã¢ã¯ã»ã¹ãå¿ èŠãªãã¹ãïŒãšãã£ãããŒã«ãŒã§ãã¹ããåé¡ã»ç®¡çã§ããŸãã
- èªåãã¹ãæ€åº: `test_`ã§å§ãŸããã¡ã€ã«åãé¢æ°åã`_test`ã§çµãããã¡ã€ã«åãªã©ãç¹å®ã®åœåèŠåã«åŸããã¹ããèªåçã«æ€åºããŠå®è¡ããŸããç¹å¥ãªèšå®ãªãã«ãã¹ããèªèããŠããããããæ軜ã«å§ããããšãã§ããŸãã
- è±å¯ãªãã©ã°ã€ã³ãšã³ã·ã¹ãã : Pytestã¯éåžžã«æ¡åŒµæ§ãé«ããå€ãã®ãµãŒãããŒãã£è£œãã©ã°ã€ã³ãååšããŸããã³ãŒãã«ãã¬ããžæž¬å®ïŒ
pytest-cov
ïŒããã¹ãã®äžŠåå®è¡ïŒpytest-xdist
ïŒãã¢ãã¯ã®å®¹æãªå©çšïŒpytest-mock
ïŒãã©ã³ãã ãªå®è¡é åºïŒpytest-randomly
ïŒãªã©ãæ§ã ãªæ©èœãè¿œå ã§ããŸããå ¬åŒãªã¹ãã ãã§ã1000以äžã®ãã©ã°ã€ã³ãååšããŸãïŒ2023幎12ææç¹ïŒã
ãããã®ç¹åŸŽã«ãããPytestã¯éçºè ã®ãã¹ãäœæã»å®è¡ã®å¹çãå€§å¹ ã«åäžããããœãããŠã§ã¢ã®å質ç¶æã«è²¢ç®ããŸãã
ð Pytestã䜿ã£ãŠã¿ããïŒã€ã³ã¹ããŒã«ãšåºæ¬çãªãã¹ã
ã€ã³ã¹ããŒã«
Pytestã®ã€ã³ã¹ããŒã«ã¯pipã³ãã³ãã§ç°¡åã«è¡ããŸããéçºæã®ã¿å¿
èŠãšãªãå Žåãå€ãããã-d
(ãŸã㯠--dev
) ãªãã·ã§ã³ãä»ããŠéçºäŸåé¢ä¿ãšããŠã€ã³ã¹ããŒã«ããããšãæšå¥šãããŸãã
pip install pytest
# ãŸã㯠pipenv ã䜿çšããå Žå
pipenv install --dev pytest
åºæ¬çãªãã¹ãã®æžãæ¹
Pytestã§ã¯ããã¹ã察象ã®ã³ãŒããšã¯å¥ã«ãã¹ãçšã®ãã¡ã€ã«ãäœæããŸãããã¹ããã¡ã€ã«åã¯test_*.py
ãŸãã¯*_test.py
ãšãã圢åŒã«ããå¿
èŠããããŸãããã¹ãé¢æ°åã¯test_
ã§å§ããå¿
èŠããããŸãã
äŸãšããŠãäžããããæ°å€ãå¶æ°ãã©ãããå€å®ããé¢æ°is_even
ããã¹ãããŠã¿ãŸãããã
ãŸãããã¹ã察象ã®é¢æ°ãèšè¿°ããŸãïŒäŸ: `my_math.py`ïŒã
# my_math.py
def is_even(number):
"""äžããããæ°å€ãå¶æ°ãã©ãããå€å®ãã"""
if not isinstance(number, int):
raise TypeError("Input must be an integer.")
return number % 2 == 0
次ã«ããã®é¢æ°ããã¹ãããã³ãŒãããã¹ããã¡ã€ã«ã«èšè¿°ããŸãïŒäŸ: `test_my_math.py`ïŒã
# test_my_math.py
import pytest
from my_math import is_even # ãã¹ã察象ã®é¢æ°ãã€ã³ããŒã
def test_is_even_positive_even():
"""æ£ã®å¶æ°ããã¹ã"""
assert is_even(4) == True
def test_is_even_positive_odd():
"""æ£ã®å¥æ°ããã¹ã"""
assert is_even(5) == False
def test_is_even_zero():
"""ãŒãããã¹ã"""
assert is_even(0) == True
def test_is_even_negative_even():
"""è² ã®å¶æ°ããã¹ã"""
assert is_even(-2) == True
def test_is_even_negative_odd():
"""è² ã®å¥æ°ããã¹ã"""
assert is_even(-3) == False
def test_is_even_non_integer():
"""æŽæ°ä»¥å€ã®å Žåã«TypeErrorãçºçããããšããã¹ã"""
with pytest.raises(TypeError):
is_even(3.14)
ãã®äŸã§ã¯ã
- ãã¹ãé¢æ°ã¯
test_
ã§å§ãŸã£ãŠããŸãã - ãã¹ã察象ã®é¢æ°
is_even
ãfrom my_math import is_even
ã§ã€ã³ããŒãããŠããŸãã - æåŸ
ããçµæã
assert
æã§æ€èšŒããŠããŸããassert is_even(4) == True
ã¯ããis_even(4)
ã®å®è¡çµæãTrue
ã§ããããšããæ€èšŒããŸãã - ç¹å®ã®äŸå€ãçºçããããšãæåŸ
ããå Žåã¯ã
with pytest.raises(äŸå€ã¯ã©ã¹):
ã䜿ããŸãããã®ãããã¯å ã§æå®ããäŸå€ãçºçããã°ãã¹ãã¯æåãçºçããªããã°å€±æãšãªããŸãã
ãã¹ãã®å®è¡
ãã¹ããå®è¡ããã«ã¯ãã¿ãŒããã«ã§ãããžã§ã¯ãã®ã«ãŒããã£ã¬ã¯ããªãªã©ã«ç§»åããpytest
ã³ãã³ããå®è¡ããŸãã
pytest
ç¹å®ã®ãã¡ã€ã«ããã£ã¬ã¯ããªãæå®ããŠå®è¡ããããšãå¯èœã§ãã
pytest test_my_math.py # ç¹å®ã®ãã¡ã€ã«ãå®è¡
pytest tests/ # testsãã£ã¬ã¯ããªä»¥äžã®ãã¹ããå®è¡
å®è¡çµæã«ã¯ãåãã¹ãã®æåïŒ.
ïŒã倱æïŒF
ïŒããšã©ãŒïŒE
ïŒãã¹ãããïŒs
ïŒãäºæããã倱æïŒx
ïŒãäºæãã¬æåïŒX
ïŒãªã©ã衚瀺ãããæåŸã«ãµããªãŒãåºåãããŸãã倱æãããã¹ãã«ã€ããŠã¯ãã©ã®assert
æã§å€±æãããããã®éã®å€æ°ã®å€ãªã©ã詳现ã«è¡šç€ºãããŸãã
äŸãã°ãtest_is_even_positive_even
ã§assert is_even(4) == False
ãšèª€ã£ãã¢ãµãŒã·ã§ã³ãæžããå Žåã以äžã®ãããªå€±æã¬ããŒãã衚瀺ãããããšããããŸãïŒè¡šç€ºå
容ã¯Pytestã®ããŒãžã§ã³ãèšå®ã«ããç°ãªããŸãïŒã
___________________________ test_is_even_positive_even ___________________________
def test_is_even_positive_even():
"""æ£ã®å¶æ°ããã¹ã"""
> assert is_even(4) == False
E assert True == False
E + where True = is_even(4)
test_my_math.py:8: AssertionError
=========================== short test summary info ============================
FAILED test_my_math.py::test_is_even_positive_even - assert True == False
ãã®ããã«ãPytestã¯éåžžã«ã·ã³ãã«ã«ãã¹ããéå§ã§ãããã€å€±ææã®ãããã°æ å ±ãè±å¯ã§ããð
ð§ Pytestã®åŒ·åãªæ©èœïŒãã£ã¯ã¹ã㣠(Fixtures)
ãã£ã¯ã¹ãã£ã¯ãPytestã®æã匷åã§ç¹åŸŽçãªæ©èœã®äžã€ã§ãããã¹ãã®å®è¡ã«å¿ èŠãªãæºåãïŒã»ããã¢ããïŒãšãåŸçä»ããïŒãã£ã¢ããŠã³ïŒãè¡ãããã®ä»çµã¿ãæäŸããŸããããã«ããããã¹ãé¢æ°èªäœã¯æ€èšŒããžãã¯ã«éäžã§ãããã¹ãã³ãŒãã®å¯èªæ§ãåå©çšæ§ãä¿å®æ§ãå€§å¹ ã«åäžããŸãã
ãã£ã¯ã¹ãã£ã¯ã以äžã®ãããªç®çã§å©çšãããŸãã
- ãã¹ãããŒã¿ã®æºåïŒäŸ: ç¹å®ã®æ§é ãæã€ãªããžã§ã¯ãããªã¹ããèŸæžãªã©ïŒ
- ãã¹ãç°å¢ã®ã»ããã¢ããïŒäŸ: ããŒã¿ããŒã¹æ¥ç¶ãäžæãã¡ã€ã«ã®äœæãèšå®ã®èªã¿èŸŒã¿ïŒ
- å€éšãµãŒãã¹ã®ã¢ãã¯å
- ãã¹ãåŸã®ã¯ãªãŒã³ã¢ããïŒäŸ: ããŒã¿ããŒã¹æ¥ç¶ã®åæãäžæãã¡ã€ã«ã®åé€ïŒ
ãã£ã¯ã¹ãã£ã®å®çŸ©ãšäœ¿çš
ãã£ã¯ã¹ãã£ã¯ã@pytest.fixture
ãã³ã¬ãŒã¿ãä»ããé¢æ°ãšããŠå®çŸ©ããŸãããã¹ãé¢æ°ããã®ãã£ã¯ã¹ãã£é¢æ°åãåŒæ°ãšããŠåãåãããšã§ããã£ã¯ã¹ãã£ã®è¿ãå€ïŒæºåãããããŒã¿ããªããžã§ã¯ãïŒãå©çšã§ããŸãã
import pytest
# ãã£ã¯ã¹ãã£ã®å®çŸ©
@pytest.fixture
def sample_list():
"""ãã¹ãçšã®ã·ã³ãã«ãªãªã¹ããæäŸãããã£ã¯ã¹ãã£"""
print("\n--- sample_list ãã£ã¯ã¹ã㣠ã»ããã¢ãã ---")
data = [1, 2, 3, 4, 5]
yield data # yieldã§å€ãè¿ãããã以éããã£ã¢ããŠã³åŠç
print("\n--- sample_list ãã£ã¯ã¹ã㣠ãã£ã¢ããŠã³ ---")
# data.clear() # äŸãã°åŸçä»ãåŠç
# ãã¹ãé¢æ°ã§ãã£ã¯ã¹ãã£ã䜿çš
def test_list_length(sample_list): # åŒæ°ã«ãã£ã¯ã¹ãã£åãæå®
"""ãªã¹ãã®é·ãããã¹ã"""
assert len(sample_list) == 5
def test_list_content(sample_list):
"""ãªã¹ãã®å
容ããã¹ã"""
assert sample_list[0] == 1
assert 3 in sample_list
ãã®äŸã§ã¯ãsample_list
ãšãããã£ã¯ã¹ãã£ãå®çŸ©ããŠããŸãããã®ãã£ã¯ã¹ãã£ã¯ãªã¹ã[1, 2, 3, 4, 5]
ãæºåããŸããyield
ããŒã¯ãŒãã䜿ãããšã§ããã¹ãé¢æ°ãå®è¡ãããåã«yield
ãŸã§ã®åŠçïŒã»ããã¢ããïŒãè¡ããããã¹ãé¢æ°ã«yield
ã®å³åŽã®å€ãæž¡ãããŸãããã¹ãé¢æ°ã®å®è¡åŸã«ã¯ãyield
以éã®åŠçïŒãã£ã¢ããŠã³ïŒãå®è¡ãããŸããreturn
ã䜿ããšãã£ã¢ããŠã³åŠçã¯å®çŸ©ã§ããŸããã
test_list_length
ãštest_list_content
ã¯ãåŒæ°ãšããŠsample_list
ãåãåã£ãŠããŸããPytestã¯ãããèªèãããã¹ãå®è¡åã«sample_list
ãã£ã¯ã¹ãã£ãå®è¡ãããã®çµæïŒããã§ã¯ãªã¹ãïŒãåŒæ°sample_list
ã«æž¡ããŸãã
ãã£ã¯ã¹ãã£ã®ã¹ã³ãŒã (Scope)
ãã£ã¯ã¹ãã£ã¯ããã®ã»ããã¢ããã»ãã£ã¢ããŠã³åŠçãã©ã®ç¯å²ã§å®è¡ãããããå¶åŸ¡ãããã¹ã³ãŒãããæå®ã§ããŸããã¹ã³ãŒããæå®ããããšã§ãã»ããã¢ããåŠçã®ã³ã¹ããé«ãå Žåã«å®è¡åæ°ãæãããã¹ãå šäœã®å®è¡æéãççž®ã§ããŸãã
ã¹ã³ãŒãã¯@pytest.fixture
ãã³ã¬ãŒã¿ã®scope
åŒæ°ã§æå®ããŸããäž»ãªã¹ã³ãŒãã¯ä»¥äžã®éãã§ãïŒå®è¡é »åºŠãäœãé ïŒã
ã¹ã³ãŒã | 説æ |
---|---|
function (ããã©ã«ã) | ãã£ã¯ã¹ãã£ã䜿çšããåãã¹ãé¢æ°ã®å®è¡ããšã«ã»ããã¢ããã»ãã£ã¢ããŠã³ãå®è¡ãããŸãã |
class | ãã£ã¯ã¹ãã£ã䜿çšãããã¹ãã¯ã©ã¹ããšã«1åã ãã»ããã¢ããã»ãã£ã¢ããŠã³ãå®è¡ãããŸãã |
module | ãã£ã¯ã¹ãã£ã䜿çšãããã¹ãã¢ãžã¥ãŒã«ïŒ.pyãã¡ã€ã«ïŒããšã«1åã ãã»ããã¢ããã»ãã£ã¢ããŠã³ãå®è¡ãããŸãã |
package | ãã£ã¯ã¹ãã£ã䜿çšãããã¹ãããã±ãŒãžïŒãã£ã¬ã¯ããªïŒããšã«1åã ãã»ããã¢ããã»ãã£ã¢ããŠã³ãå®è¡ãããŸãã(æ¯èŒçæ°ããããŒãžã§ã³ã§å®éšçã«å°å ¥) |
session | pytestã®ãã¹ãã»ãã·ã§ã³å
šäœïŒpytest ã³ãã³ãå®è¡ïŒã§1åã ãã»ããã¢ããã»ãã£ã¢ããŠã³ãå®è¡ãããŸãã |
import pytest
import time
@pytest.fixture(scope="session")
def expensive_resource():
"""ã»ããã¢ããã«æéãããããªãœãŒã¹ïŒã»ãã·ã§ã³ã¹ã³ãŒãïŒ"""
print(f"\n--- expensive_resource ã»ããã¢ãã ({time.time()}) ---")
time.sleep(1) # æéããããåŠçãã·ãã¥ã¬ãŒã
resource = {"id": 1, "data": "heavy data"}
yield resource
print(f"\n--- expensive_resource ãã£ã¢ããŠã³ ({time.time()}) ---")
# ãªãœãŒã¹è§£æŸåŠç
def test_resource_id(expensive_resource):
assert expensive_resource["id"] == 1
def test_resource_data(expensive_resource):
assert "heavy" in expensive_resource["data"]
ãã®äŸã§ã¯ãexpensive_resource
ãã£ã¯ã¹ãã£ã¯scope="session"
ã§å®çŸ©ãããŠãããããpytest
ã³ãã³ããå®è¡ããéã«äžåºŠã ãã»ããã¢ãããããå
šãŠã®ãã¹ããçµäºããåŸã«äžåºŠã ããã£ã¢ããŠã³ãããŸããtest_resource_id
ãštest_resource_data
ã®äž¡æ¹ãåããªãœãŒã¹ã€ã³ã¹ã¿ã³ã¹ã䜿çšããŸãã
ãã£ã¯ã¹ãã£ã®èªåé©çš (Autouse)
éåžžããã£ã¯ã¹ãã£ã¯ãã¹ãé¢æ°ãåŒæ°ãšããŠèŠæ±ããå Žåã«ã®ã¿å®è¡ãããŸããããããautouse=True
ãæå®ãããšããã®ã¹ã³ãŒãå
ã§å®çŸ©ããããã¹ãŠã®ãã¹ãã«å¯ŸããŠèªåçã«ãã£ã¯ã¹ãã£ãé©çšãããŸããããã¯ããã°èšå®ãç°å¢å€æ°ã®èšå®ãªã©ãæ瀺çã«èŠæ±ããå¿
èŠã¯ãªããåžžã«å®è¡ãããŠã»ããåŠçã«äŸ¿å©ã§ãã
import pytest
import os
@pytest.fixture(autouse=True, scope="module")
def set_test_environment():
"""ãã¹ãçšã®ç°å¢å€æ°ãèšå®ãã (ã¢ãžã¥ãŒã«ã¹ã³ãŒãã§èªåé©çš)"""
print("\n--- ç°å¢å€æ°èšå® ---")
original_value = os.environ.get("MY_TEST_VAR")
os.environ["MY_TEST_VAR"] = "test_value"
yield
print("\n--- ç°å¢å€æ°åŸ©å
---")
if original_value is None:
del os.environ["MY_TEST_VAR"]
else:
os.environ["MY_TEST_VAR"] = original_value
def test_env_var_exists():
assert "MY_TEST_VAR" in os.environ
def test_env_var_value():
assert os.environ["MY_TEST_VAR"] == "test_value"
ãã®äŸã§ã¯ãset_test_environment
ãã£ã¯ã¹ãã£ãautouse=True
ã§å®çŸ©ãããŠããããããã®ã¢ãžã¥ãŒã«å
ã®test_env_var_exists
ãštest_env_var_value
ã®äž¡æ¹ã®ãã¹ãå®è¡åã«èªåçã«å®è¡ãããç°å¢å€æ°ãèšå®ãããŸãã
conftest.pyã«ãããã£ã¯ã¹ãã£ã®å ±æ
è€æ°ã®ãã¹ããã¡ã€ã«ïŒã¢ãžã¥ãŒã«ïŒã§å
±éããŠäœ¿çšããããã£ã¯ã¹ãã£ã¯ãconftest.py
ãšããååã®ãã¡ã€ã«ã«å®çŸ©ããŸããconftest.py
ãã¡ã€ã«ã¯ç¹å¥ãªãã¡ã€ã«ã§ãPytestã¯ãã®ãã¡ã€ã«ãååšãããã£ã¬ã¯ããªããã³ãã®ãµããã£ã¬ã¯ããªå
ã®ãã¹ããããããã«å®çŸ©ããããã£ã¯ã¹ãã£ãèªåçã«èªèããŠå©çšå¯èœã«ããŸããconftest.py
ã«å®çŸ©ããããã£ã¯ã¹ãã£ã¯ããã¹ããã¡ã€ã«åŽã§ã€ã³ããŒãããå¿
èŠã¯ãããŸããã
ãããžã§ã¯ãæ§æäŸïŒ
my_project/
âââ src/
â âââ my_app/
â âââ __init__.py
â âââ core.py
âââ tests/
âââ conftest.py # testsãã£ã¬ã¯ããªå
±éã®ãã£ã¯ã¹ãã£
âââ test_module_a.py
âââ sub_dir/
âââ conftest.py # sub_dirãã£ã¬ã¯ããªåºæã®ãã£ã¯ã¹ã㣠(ä»»æ)
âââ test_module_b.py
tests/conftest.py
ã«ãã£ã¯ã¹ãã£ãå®çŸ©ãããšãtest_module_a.py
ãš test_module_b.py
ã®äž¡æ¹ããå©çšã§ããŸãããã tests/sub_dir/conftest.py
ã«ããã£ã¯ã¹ãã£ãå®çŸ©ãããŠããã°ãtest_module_b.py
ã¯äž¡æ¹ã®conftest.py
ã®ãã£ã¯ã¹ãã£ãå©çšã§ããŸããåãååã®ãã£ã¯ã¹ãã£ãååšããå Žåããããã¹ããã¡ã€ã«ã«è¿ãconftest.py
ã®å®çŸ©ãåªå
ãããŸãã
ãã£ã¯ã¹ãã£ã¯Pytestã䜿ãããªãäžã§éåžžã«éèŠãªæŠå¿µã§ããé©åã«å©çšããããšã§ããã¹ãã³ãŒããåçã«æŽçããå¹çåããããšãã§ããŸããâš
ð·ïž ããŒã«ãŒ (Markers)ïŒãã¹ãã®åé¡ãšå¶åŸ¡
ããŒã«ãŒã¯ããã¹ãé¢æ°ããã¹ãã¯ã©ã¹ã«ã¡ã¿ããŒã¿ïŒç®å°ïŒãä»äžããããã®æ©èœã§ãã@pytest.mark.<markername>
ãšãããã³ã¬ãŒã¿åœ¢åŒã§äœ¿çšããŸããããŒã«ãŒã䜿ãããšã§ããã¹ããã°ã«ãŒãåããããç¹å®ã®æ¡ä»¶äžã§ãã¹ãã®å®è¡ãå¶åŸ¡ãããã§ããŸãã
çµã¿èŸŒã¿ããŒã«ãŒ
Pytestã«ã¯ããã䜿ãããæ©èœã®ããã®çµã¿èŸŒã¿ããŒã«ãŒãããã€ãçšæãããŠããŸãã
skip
: ãã¹ããç¡æ¡ä»¶ã«ã¹ãããããŸããreason
åŒæ°ã§ã¹ãããããçç±ãæèšã§ããŸããimport pytest @pytest.mark.skip(reason="ãŸã å®è£ ãããŠããªãæ©èœã®ãã¹ã") def test_new_feature(): # ... ãã¹ãã³ãŒã ... pass
skipif
: æå®ããæ¡ä»¶ãTrue
ã®å Žåã«ãã¹ããã¹ãããããŸããOSãPythonã®ããŒãžã§ã³ãç¹å®ã®ã©ã€ãã©ãªã®æç¡ãªã©ã«åºã¥ããŠã¹ããããå¶åŸ¡ããã®ã«äŸ¿å©ã§ããimport sys import pytest @pytest.mark.skipif(sys.platform == "win32", reason="Windowsã§ã¯åäœããªããã¹ã") def test_linux_specific_function(): # ... Linuxåºæã®æ©èœã䜿ããã¹ã ... pass NEEDS_PANDAS = pytest.mark.skipif(pytest.importorskip("pandas") is None, reason="pandasãå¿ èŠã§ã") @NEEDS_PANDAS def test_with_pandas(): import pandas as pd # ... pandas ã䜿ããã¹ã ... pass
pytest.importorskip("module_name")
ã¯ãæå®ããã¢ãžã¥ãŒã«ãã€ã³ããŒãã§ããã°ã¢ãžã¥ãŒã«ãªããžã§ã¯ããè¿ããã§ããªããã°ãã¹ããã¹ããããã䟿å©ãªæ©èœã§ããxfail
: ãã¹ãã倱æããããšãæ³å®ããŠããå Žåã«ããŒã¯ããŸãããExpected FailureãïŒäºæããã倱æïŒãæå³ããŸãããã¹ããå®éã«å€±æããã°xfailed (XF)
ãäºæããæåããå Žåã¯xpassed (XP)
ãšããŠã¬ããŒããããŸãããã°ãä¿®æ£ããããŸã§ã®éãªã©ã«äœ¿ãããŸããimport pytest @pytest.mark.xfail(reason="æ¢ç¥ã®ãã° #123 ãä¿®æ£ããããŸã§å€±æãã") def test_known_bug(): # ... ãã°ã®åœ±é¿ãåããã³ãŒã ... assert complex_calculation() == expected_but_wrong_value
parametrize
: ãã¹ãé¢æ°ã«è€æ°ã®ãã©ã¡ãŒã¿ã»ãããæž¡ããŠç¹°ãè¿ãå®è¡ããŸããããã¯éåžžã«åŒ·åãªæ©èœã§ã次ã®ã»ã¯ã·ã§ã³ã§è©³ãã説æããŸããusefixtures
: åŒæ°ã§çŽæ¥èŠæ±ããªããã£ã¯ã¹ãã£ïŒéåžžã¯autouse=True
ã§ã¯ãªããã®ïŒããã¹ãé¢æ°ãã¯ã©ã¹ã«é©çšããŸããäž»ã«ãå¯äœçšïŒç¶æ ã®å€æŽãªã©ïŒãæã€ãå€ãè¿ããªããã£ã¯ã¹ãã£ã䜿ãå Žåã«å©çšãããŸããimport pytest @pytest.fixture def setup_database(): print("\n--- DBã»ããã¢ãã ---") # DBæ¥ç¶ãããŒãã«äœæãªã© yield print("\n--- DBã¯ãªãŒã³ã¢ãã ---") # ããŒãã«åé€ãªã© @pytest.mark.usefixtures("setup_database") def test_database_operation_1(): # setup_databaseãã£ã¯ã¹ãã£ãé©çšããã # ... DBæäœã®ãã¹ã ... pass @pytest.mark.usefixtures("setup_database") class TestDBSuite: def test_database_operation_2(self): # ã¯ã©ã¹å ã®å šãã¹ãã«ãã£ã¯ã¹ãã£ãé©çšããã pass def test_database_operation_3(self): pass
ã«ã¹ã¿ã ããŒã«ãŒ
èªåã§ä»»æã®ååã®ããŒã«ãŒãå®çŸ©ããŠããã¹ããèªç±ã«åé¡ã§ããŸããäŸãã°ãç¹å®ã®æ©èœïŒ@pytest.mark.login
ïŒããã¹ãã®çš®é¡ïŒ@pytest.mark.integration
ïŒãå®è¡é床ïŒ@pytest.mark.slow
ïŒãªã©ã§ããŒã¯ã§ããŸãã
import pytest
import time
@pytest.mark.slow
def test_long_running():
time.sleep(2)
assert True
@pytest.mark.api
@pytest.mark.user_management
def test_create_user():
# ... ãŠãŒã¶ãŒäœæAPIã®ãã¹ã ...
pass
@pytest.mark.database
class TestDatabaseRelated:
def test_db_read(self):
pass
def test_db_write(self):
pass
ã«ã¹ã¿ã ããŒã«ãŒã䜿çšããå ŽåãPytestãèŠåãåºããªãããã«ããããžã§ã¯ãã®èšå®ãã¡ã€ã«ïŒpytest.ini
, pyproject.toml
, tox.ini
ãªã©ïŒã«ããŒã«ãŒãç»é²ããããšãæšå¥šãããŸãã
pytest.ini
ã®äŸ:
[pytest]
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
api: marks tests related to API calls
user_management: marks tests for user management features
database: marks tests requiring database access
pyproject.toml
ã®äŸ:
[tool.pytest.ini_options]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"api: marks tests related to API calls",
"user_management: marks tests for user management features",
"database: marks tests requiring database access",
]
ç»é²ããŠããããšã§ãpytest --markers
ã³ãã³ãã§å©çšå¯èœãªããŒã«ãŒãšãã®èª¬æã®äžèŠ§ã衚瀺ã§ããŸãã
ããŒã«ãŒã䜿ã£ããã¹ãã®éžæå®è¡
pytest
ã³ãã³ãã®-m
ãªãã·ã§ã³ã䜿ã£ãŠãç¹å®ã®ããŒã«ãŒãä»ãããã¹ãããŸãã¯ä»ããŠããªããã¹ããéžæããŠå®è¡ã§ããŸãã
pytest -m slow
:@pytest.mark.slow
ãä»ãããã¹ãã®ã¿å®è¡ãpytest -m "not slow"
:@pytest.mark.slow
ãä»ããŠããªããã¹ãã®ã¿å®è¡ãpytest -m "api and user_management"
:@pytest.mark.api
ãš@pytest.mark.user_management
ã®äž¡æ¹ãä»ãããã¹ãã®ã¿å®è¡ãpytest -m "database or api"
:@pytest.mark.database
ãŸãã¯@pytest.mark.api
ãä»ãããã¹ããå®è¡ãpytest -m "not (slow or database)"
:@pytest.mark.slow
ã@pytest.mark.database
ãä»ããŠããªããã¹ããå®è¡ã
ããŒã«ãŒã¯ããã¹ãã¹ã€ãŒãã倧ãããªã£ãéã«ãç¹å®ã®ãã¹ã矀ã ããå¹ççã«å®è¡ããããCI/CDãã€ãã©ã€ã³ã§å®è¡ãããã¹ãã段éçã«å¶åŸ¡ãããããã®ã«éåžžã«åœ¹ç«ã¡ãŸããðââïžðš
ð ãã©ã¡ãŒã¿å (Parametrization)ïŒå¹ççãªãã¹ãã±ãŒã¹çæ
ãã©ã¡ãŒã¿åã¯ãåããã¹ãããžãã¯ãç°ãªãå
¥åå€ãæåŸ
å€ã®çµã¿åããã§ç¹°ãè¿ãå®è¡ããããã®åŒ·åãªæ©èœã§ããããã«ãããã³ãŒãã®éè€ãé¿ãã€ã€ãæ§ã
ãªã±ãŒã¹ã網çŸ
ãããã¹ããå¹ççã«èšè¿°ã§ããŸããPytestã§ã¯äž»ã«@pytest.mark.parametrize
ãã³ã¬ãŒã¿ã䜿ã£ãŠå®çŸããŸãã
@pytest.mark.parametrize ã®åºæ¬
@pytest.mark.parametrize(argnames, argvalues)
ã®åœ¢ã§äœ¿çšããŸãã
argnames
: ãã©ã¡ãŒã¿åãæå®ããæååïŒã«ã³ãåºåãïŒããŸãã¯æååã®ãªã¹ã/ã¿ãã«ããã¹ãé¢æ°ã®åŒæ°åã«å¯Ÿå¿ããŸããargvalues
: ãã©ã¡ãŒã¿å€ã®ãªã¹ããåèŠçŽ ã¯ãargnames
ã§æå®ããããã©ã¡ãŒã¿ã«å¯Ÿå¿ããå€ã®ã¿ãã«ããŸãã¯åäžãã©ã¡ãŒã¿ã®å Žåã¯å€ãã®ãã®ã§ãããªã¹ãã®åèŠçŽ ãäžåã®ãã¹ãå®è¡ã«çžåœããŸãã
äŸïŒç°¡åãªè¶³ãç®é¢æ°ã®ãã¹ã
import pytest
def add(a, b):
return a + b
# ãã©ã¡ãŒã¿åããããã¹ãé¢æ°
@pytest.mark.parametrize("input_a, input_b, expected", [
(1, 2, 3), # test_add[1-2-3]
(5, 5, 10), # test_add[5-5-10]
(-1, 1, 0), # test_add[-1-1-0]
(0, 0, 0), # test_add[0-0-0]
(100, -50, 50), # test_add[100--50-50]
])
def test_add(input_a, input_b, expected):
assert add(input_a, input_b) == expected
ãã®äŸã§ã¯ãtest_add
é¢æ°ãargvalues
ãªã¹ãã®åã¿ãã«ããã©ã¡ãŒã¿ãšããŠ5åå®è¡ãããŸãã
- 1åç®:
input_a=1
,input_b=2
,expected=3
- 2åç®:
input_a=5
,input_b=5
,expected=10
- … 以äžåæ§
Pytestã¯å®è¡æã«åãã©ã¡ãŒã¿ã»ããã«å¯ŸããŠåãããããIDïŒäŸ: test_add[1-2-3]
ïŒãèªåçæããã¬ããŒãã«è¡šç€ºããŸãã
ãã¹ãIDã®ã«ã¹ã¿ãã€ãº
èªåçæããããã¹ãIDã®ä»£ããã«ããã説æçãªIDãä»ãããå Žåã¯ãids
åŒæ°ã䜿çšããŸããids
ã«ã¯ãargvalues
ã®åèŠçŽ ã«å¯Ÿå¿ããæååã®ãªã¹ããæå®ããŸãã
import pytest
@pytest.mark.parametrize("test_input, expected", [
("hello", 5),
("", 0),
(" pytest ", 8), # ã¹ããŒã¹ãå«ã
], ids=["normal string", "empty string", "string with spaces"])
def test_string_length(test_input, expected):
assert len(test_input) == expected
å®è¡çµæã«ã¯ãtest_string_length[normal string]
, test_string_length[empty string]
, test_string_length[string with spaces]
ã®ããã«è¡šç€ºãããŸãã
ãŸããpytest.param
ã䜿ã£ãŠå€ãšIDãããŒã«ãŒãäžç·ã«æå®ããããšãå¯èœã§ãã
import pytest
import sys
def my_complex_logic(data):
if isinstance(data, str) and sys.platform != "win32":
return "processed:" + data
elif isinstance(data, int):
return data * 2
else:
raise ValueError("Unsupported data type or platform")
@pytest.mark.parametrize("data, expected", [
pytest.param("abc", "processed:abc", id="string_on_non_windows"),
pytest.param(10, 20, id="integer_input"),
pytest.param([1, 2], None, marks=pytest.mark.xfail(raises=ValueError), id="list_raises_valueerror"),
pytest.param("win_str", None, marks=pytest.mark.skipif(sys.platform != "win32", reason="Windows only test"), id="string_on_windows"),
])
def test_my_logic(data, expected):
if expected is None: # äŸå€ãã¹ããããæåŸ
ãããå Žå
with pytest.raises(ValueError): # ä»®ã«ValueErrorãæåŸ
my_complex_logic(data)
else:
assert my_complex_logic(data) == expected
ãã®äŸã§ã¯ãpytest.param
ã䜿çšããŠãåãã©ã¡ãŒã¿ã»ããã«IDãä»ããç¹å®ã®ã»ããã«ã¯xfail
ãskipif
ããŒã«ãŒãé©çšããŠããŸãã
è€æ°ã®parametrizeãã³ã¬ãŒã¿
äžã€ã®ãã¹ãé¢æ°ã«è€æ°ã®@pytest.mark.parametrize
ãã³ã¬ãŒã¿ãé©çšãããšããããã®ãã©ã¡ãŒã¿ã®çµã¿åããïŒçŽç©ïŒã§ãã¹ããå®è¡ãããŸãã
import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_combinations(x, y):
print(f"Testing with x={x}, y={y}")
assert isinstance(x, int)
assert isinstance(y, int)
ãã®ãã¹ãã¯ä»¥äžã®çµã¿åããã§4åå®è¡ãããŸãã
- x=0, y=2
- x=0, y=3
- x=1, y=2
- x=1, y=3
ãã£ã¯ã¹ãã£ã®ãã©ã¡ãŒã¿å
ãã£ã¯ã¹ãã£èªäœããã©ã¡ãŒã¿åããããšãå¯èœã§ãããã£ã¯ã¹ãã£ã®params
åŒæ°ã«å€ã®ãªã¹ããæž¡ãããã£ã¯ã¹ãã£é¢æ°å
ã§request.param
ã䜿ã£ãŠåãã©ã¡ãŒã¿å€ã«ã¢ã¯ã»ã¹ããŸãã
import pytest
@pytest.fixture(params=["user_a", "user_b", "admin"], ids=["regular_user_A", "regular_user_B", "admin_user"])
def user_role(request):
"""ç°ãªããŠãŒã¶ãŒããŒã«ãæäŸãããã£ã¯ã¹ãã£"""
role = request.param
print(f"\n--- Setup for role: {role} ---")
# ããã§ããŒã«ã«å¿ããã»ããã¢ãããè¡ã (äŸ: DBã«ãŠãŒã¶ãŒäœæ)
yield role
print(f"\n--- Teardown for role: {role} ---")
# ããŒã«ã«å¿ããã¯ãªãŒã³ã¢ãã
def test_access_level(user_role):
"""ãŠãŒã¶ãŒããŒã«ã«åºã¥ããŠã¢ã¯ã»ã¹æš©ããã¹ã"""
if user_role == "admin":
print(f"Testing admin access for {user_role}")
assert check_admin_access(user_role) == True
else:
print(f"Testing user access for {user_role}")
assert check_user_access(user_role) == True
# ãããŒã®ã¢ã¯ã»ã¹ãã§ãã¯é¢æ°
def check_admin_access(role): return role == "admin"
def check_user_access(role): return role in ["user_a", "user_b", "admin"]
ãã®test_access_level
é¢æ°ã¯ãuser_role
ãã£ã¯ã¹ãã£ã®åãã©ã¡ãŒã¿ïŒ”user_a”, “user_b”, “admin”ïŒã«å¯ŸããŠå®è¡ãããŸãããã£ã¯ã¹ãã£ããã©ã¡ãŒã¿åããããšãããã䜿çšãããã¹ãé¢æ°ãèªåçã«ãã©ã¡ãŒã¿åãããŸããããã¯ãç°ãªãèšå®ãç°å¢ïŒäŸ: ç°ãªãDBæ¥ç¶ãç°ãªãèšå®ãã¡ã€ã«ïŒã§åããã¹ããå®è¡ãããå Žåã«äŸ¿å©ã§ãã
ãã©ã¡ãŒã¿åã¯ããã¹ãã³ãŒããDRYïŒDon’t Repeat YourselfïŒã«ä¿ã¡ããã¹ãã®ã«ãã¬ããžãé«ããããã®éåžžã«å¹æçãªæ段ã§ããð
𧩠ãã©ã°ã€ã³ãšã³ã·ã¹ãã ïŒPytestã®æ¡åŒµ
Pytestã®å€§ããªé åã®äžã€ã¯ããã®è±å¯ãªãã©ã°ã€ã³ãšã³ã·ã¹ãã ã§ãããã©ã°ã€ã³ãã€ã³ã¹ããŒã«ããã ãã§ãPytestã®ã³ã¢æ©èœãæ¡åŒµãããã¹ãã¯ãŒã¯ãããŒãããã«å¹çåã»é«åºŠåã§ããŸããå€ãã®ãã©ã°ã€ã³ã¯pipã§ç°¡åã«ã€ã³ã¹ããŒã«ã§ããŸãã
ããã§ã¯ãç¹ã«äººæ°ããã䟿å©ãªãã©ã°ã€ã³ãããã€ã玹ä»ããŸãã
ãã©ã°ã€ã³å | äž»ãªæ©èœ | ã€ã³ã¹ããŒã« | ç°¡åãªèª¬æ |
---|---|---|---|
pytest-cov | ãã¹ãã«ãã¬ããžæž¬å® | pip install pytest-cov | ãã¹ããã³ãŒãã®ã©ã®éšåãå®è¡ãããã枬å®ããã¬ããŒãïŒã¿ãŒããã«ãHTMLãXMLãªã©ïŒãçæããŸãããã¹ãã®ç¶²çŸ
æ§ã確èªããã®ã«äžå¯æ¬ ã§ããéåžžãpytest --cov=my_project ã®ããã«äœ¿ããŸãã |
pytest-xdist | ãã¹ãã®äžŠåå®è¡ | pip install pytest-xdist | è€æ°ã®CPUã³ã¢ããªã¢ãŒããã·ã³ãå©çšããŠãã¹ãã䞊åå®è¡ãããã¹ãã¹ã€ãŒãå
šäœã®å®è¡æéã倧å¹
ã«ççž®ããŸããpytest -n auto ïŒCPUã³ã¢æ°ã«å¿ããŠèªåã§ã¯ãŒã«ãŒæ°ã決å®ïŒã®ããã«äœ¿ããŸãããã¹ãéã«äŸåé¢ä¿ããªãå Žåã«ç¹ã«æå¹ã§ãã |
pytest-mock | ã¢ãã¯åŠçã®ç°¡ç¥å | pip install pytest-mock | Pythonæšæºã®unittest.mock ã©ã€ãã©ãªãããç°¡åã«äœ¿ããããã«ããmocker ãã£ã¯ã¹ãã£ãæäŸããŸããäŸåãããªããžã§ã¯ããå€éšAPIãªã©ããã¹ãããã«ïŒã¢ãã¯ãã¹ã¿ãïŒã«çœ®ãæããéã«äŸ¿å©ã§ãã |
pytest-randomly | ãã¹ãå®è¡é åºã®ã©ã³ãã å | pip install pytest-randomly | ãã¹ãã®å®è¡é åºãæ¯åã©ã³ãã ã«å€æŽããŸãããã¹ãã¯æ¬æ¥äºãã«ç¬ç«ããŠããã¹ãã§ãããæå³ããªãé åºäŸåæ§ïŒåã®ãã¹ãã®çµæã«åŸã®ãã¹ãã圱é¿ããããªã©ïŒãçºèŠããã®ã«åœ¹ç«ã¡ãŸãã |
pytest-sugar | ãã¹ãçµæ衚瀺ã®æ¹å | pip install pytest-sugar | ãã¹ãã®å®è¡ç¶æ³ïŒããã°ã¬ã¹ããŒïŒãçµæ衚瀺ãã«ã©ãã«ã§èŠãããããŸãããã¹ãå®è¡äžã®ãã£ãŒãããã¯ãåäžããŸãã |
pytest-benchmark | ã³ãŒãã®ãã³ãããŒã¯æž¬å® | pip install pytest-benchmark | ç¹å®ã®ã³ãŒãçã®å®è¡æéã枬å®ããçµ±èšçã«æ¯èŒããããã®benchmark ãã£ã¯ã¹ãã£ãæäŸããŸããããã©ãŒãã³ã¹æ¹åã®å¹æ枬å®ãªã©ã«å©çšã§ããŸãã |
pytest-clarity | 倱ææã®diff衚瀺æ¹å | pip install pytest-clarity | ãã¹ã倱ææã®assert æ¯èŒã§ãæåŸ
å€ãšå®éã®å€ã®å·®åïŒdiffïŒãããã«ã©ãã«ã§åããããã衚瀺ããŸããç¹ã«å€§ããªããŒã¿æ§é ïŒãªã¹ããèŸæžïŒã®æ¯èŒæã«å·®åãææ¡ãããããªããŸãã |
pytest-freezegun | æéã«é¢é£ãããã¹ãã®å¶åŸ¡ | pip install pytest-freezegun | freezegun ã©ã€ãã©ãªãšé£æºãããã¹ãäžã®ãçŸåšæå»ããåºå®ããããç¹å®ã®æ¥æã«é²ãããããããšãã§ããŸããæ¥æã«ãã£ãŠæåãå€ããæ©èœã®ãã¹ãã«åœ¹ç«ã¡ãŸããfreezer ãã£ã¯ã¹ãã£ãæäŸããŸãã |
pytest-django / pytest-flask ãªã© | Webãã¬ãŒã ã¯ãŒã¯é£æº | (ãããã) | DjangoãFlaskãªã©ã®ç¹å®ã®ãã¬ãŒã ã¯ãŒã¯ã䜿ã£ãã¢ããªã±ãŒã·ã§ã³ã®ãã¹ãã容æã«ããããã®ãã£ã¯ã¹ãã£ããŠãŒãã£ãªãã£ãæäŸããŸããïŒäŸ: ãã¹ãçšããŒã¿ããŒã¹ã®ã»ããã¢ããããã¹ãã¯ã©ã€ã¢ã³ãã®æäŸãªã©ïŒ |
pytest-bdd | æ¯ãèãé§åéçº(BDD)ã®ãµããŒã | pip install pytest-bdd | GherkinèšèªïŒGiven/When/Then圢åŒïŒã§èšè¿°ããããã£ãŒãã£ãŒãã¡ã€ã«ã«åºã¥ããŠãã¹ããèšè¿°ã»å®è¡ã§ããããã«ããŸããããžãã¹èŠä»¶ãšãã¹ãã³ãŒããçµã³ã€ããããããŸãã |
ãããã®ãã©ã°ã€ã³ã¯ãPytestã®åºæ¬çãªæ©èœã ãã§ã¯ã«ããŒããããªããããé«åºŠãªãã¹ãèŠä»¶ãéçºã¯ãŒã¯ãããŒã®æ¹åã«è²¢ç®ããŸãããããžã§ã¯ãã®ããŒãºã«åãããŠé©åãªãã©ã°ã€ã³ãéžæã»å°å ¥ããããšã§ããã¹ãéçºã®çç£æ§ãããã«é«ããããšãã§ããŸãã
å©çšå¯èœãªãã©ã°ã€ã³ã®å®å šãªãªã¹ãã¯ãPytestã®å ¬åŒããã¥ã¡ã³ãã§ç¢ºèªã§ããŸããæ°ãããã©ã°ã€ã³ãéæéçºãããŠããã®ã§ãå®æçã«ãã§ãã¯ããã®ãè¯ãã§ããããð§âš
âïž èšå®ãã¡ã€ã«ãšã³ãã³ãã©ã€ã³ãªãã·ã§ã³
Pytestã¯èšå®ãã¡ã€ã«ãã³ãã³ãã©ã€ã³ãªãã·ã§ã³ãéããŠããã®æåã现ããã«ã¹ã¿ãã€ãºããããšãã§ããŸããããã«ããããããžã§ã¯ãåºæã®èŠä»¶ã«åããããããã¹ãå®è¡æã®äœéšãåäžããããããããšãå¯èœã§ãã
èšå®ãã¡ã€ã«
Pytestã¯ããããžã§ã¯ãã®ã«ãŒããã£ã¬ã¯ããªã«ãã以äžã®ããããã®ãã¡ã€ã«ãèªåçã«èªèããŠèšå®ãèªã¿èŸŒã¿ãŸãã
pytest.ini
pyproject.toml
([tool.pytest.ini_options]
ããŒãã«å )tox.ini
([pytest]
ã»ã¯ã·ã§ã³å )setup.cfg
([tool:pytest]
ã»ã¯ã·ã§ã³å )
ã©ã®ãã¡ã€ã«ã䜿çšãããã¯ãããžã§ã¯ãã®èŠçŽã奜ã¿ã«ãããŸãããè¿å¹Žã§ã¯pyproject.toml
ã«ä»ã®ããŒã«ïŒäŸ: Black, isort, Ruff ãªã©ïŒã®èšå®ãšå
±ã«ãŸãšããã®ãäžè¬çã«ãªãã€ã€ãããŸãã
èšå®ãã¡ã€ã«ã§ã¯ã以äžã®ãããªé ç®ãèšå®ã§ããŸãã
markers
: ã«ã¹ã¿ã ããŒã«ãŒã®ç»é²ãaddopts
:pytest
ã³ãã³ãå®è¡æã«åžžã«é©çšãããããã©ã«ãã®ã³ãã³ãã©ã€ã³ãªãã·ã§ã³ãtestpaths
: ãã¹ããã¡ã€ã«ãæ€çŽ¢ãããã£ã¬ã¯ããªã®æå®ãæå®ããªãå Žåãã«ã¬ã³ããã£ã¬ã¯ããªããæ€çŽ¢ãããŸããpython_files
: ãã¹ããã¡ã€ã«ãšããŠèªèãããã¡ã€ã«åã®ãã¿ãŒã³ãããã©ã«ãã¯test_*.py *_test.py
ãpython_classes
: ãã¹ãã¯ã©ã¹ãšããŠèªèããã¯ã©ã¹åã®ãã¿ãŒã³ãããã©ã«ãã¯Test*
ãpython_functions
: ãã¹ãé¢æ°ãšããŠèªèããé¢æ°åã®ãã¿ãŒã³ãããã©ã«ãã¯test_*
ãfilterwarnings
: ç¹å®ã®èŠåãç¡èŠãããããšã©ãŒãšããŠæ±ã£ããããèšå®ã- ãã®ä»ããã©ã°ã€ã³åºæã®èšå®ãªã©ã
pyproject.toml
ã§ã®èšå®äŸ:
[tool.pytest.ini_options]
# ãã䜿ããªãã·ã§ã³ãããã©ã«ãã«èšå®
addopts = "-ra -q --strict-markers --cov=my_project --cov-report=term-missing"
# ãã¹ããã¡ã€ã«ã®å Žæãæå®
testpaths = [
"tests",
"integration_tests",
]
# ãã¹ããã¡ã€ã«ã®åœåèŠåãå€æŽ (äŸ)
python_files = "test_*.py check_*.py"
# ã«ã¹ã¿ã ããŒã«ãŒã®ç»é²
markers = [
"slow: marks tests as slow",
"api: marks API tests",
]
# ç¹å®ã®DeprecationWarningãç¡èŠããäŸ
filterwarnings = [
"ignore::DeprecationWarning:some_module.*:",
]
èšå®ãã¡ã€ã«ã䜿çšããããšã§ãããŒã å ã§å ±éã®èšå®ãå ±æããæ¯åã³ãã³ãã©ã€ã³ãªãã·ã§ã³ãæå®ããæéãçãããšãã§ããŸãã
äž»ãªã³ãã³ãã©ã€ã³ãªãã·ã§ã³
Pytestã«ã¯å€æ°ã®ã³ãã³ãã©ã€ã³ãªãã·ã§ã³ãçšæãããŠããŸããããã§ã¯ãã䜿ããããã®ãããã€ã玹ä»ããŸãã
ãªãã·ã§ã³ | 説æ |
---|---|
-k EXPRESSION | ãã¹ãåïŒé¢æ°åãã¯ã©ã¹åããã¡ã€ã«ãã¹ã®äžéšãªã©ïŒã«åºã¥ããŠå®è¡ãããã¹ãããã£ã«ã¿ãªã³ã°ããŸããåŒã䜿ã£ãŠand , or , not ã§çµã¿åãããããšãå¯èœã§ããïŒäŸ: -k "TestClass and not test_method" ïŒ |
-m MARKEXPR | ããŒã«ãŒã«åºã¥ããŠå®è¡ãããã¹ãããã£ã«ã¿ãªã³ã°ããŸããïŒäŸ: -m "slow or api" ïŒ |
-v / --verbose | ãã詳现ãªåºåãè¡ããŸããåãã¹ããã¡ã€ã«åãšé¢æ°åãçµæã衚瀺ãããŸãã-vv ã®ããã«éãããšããã«è©³çŽ°ã«ãªããŸãã |
-q / --quiet | åºåãæå¶ãããã¹ãçµæã®ãµããªãŒã®ã¿ãç°¡æœã«è¡šç€ºããŸãã |
-x / --exitfirst | æåã®ãã¹ã倱æãŸãã¯ãšã©ãŒãçºçããæç¹ã§ããã¹ãã»ãã·ã§ã³å šäœãå³åº§ã«äžæ¢ããŸãã |
--maxfail=NUM | æå®ããæ°ã ããã¹ãã倱æãŸãã¯ãšã©ãŒã«ãªã£ãæç¹ã§ãã¹ãã»ãã·ã§ã³ãäžæ¢ããŸãã |
-s / --capture=no | ãã¹ãå®è¡äžã®æšæºåºåïŒprint æãªã©ïŒããã£ããã£ããããã®ãŸãŸè¡šç€ºããŸãããããã°æã«äŸ¿å©ã§ããããã©ã«ãã§ã¯Pytestã¯åºåããã£ããã£ãããã¹ã倱ææã®ã¿è¡šç€ºããŸãã |
-l / --showlocals | ãã¹ã倱ææã«ã倱æç®æã®ããŒã«ã«å€æ°ã®å€ããã¬ãŒã¹ããã¯æ å ±ãšå ±ã«è¡šç€ºããŸãããããã°ã«åœ¹ç«ã¡ãŸãã |
--lf / --last-failed | ååã®pytest å®è¡ã§å€±æãããã¹ãã®ã¿ãåå®è¡ããŸããä¿®æ£åŸã®ç¢ºèªãªã©ã«äŸ¿å©ã§ãã |
--ff / --failed-first | ååã®å®è¡ã§å€±æãããã¹ããæåã«å®è¡ãããã®åŸæ®ãã®ãã¹ããå®è¡ããŸãã |
--pdb | ãã¹ãã倱æãŸãã¯ãšã©ãŒã«ãªã£ãéã«ãPythonã®ãããã¬ïŒpdbïŒãèµ·åããŸãã |
--collect-only | ãã¹ããå®è¡ãããåéïŒãã£ã¹ã«ããªïŒããããã¹ãã±ãŒã¹ã®äžèŠ§ã®ã¿ã衚瀺ããŸãããã¹ããæ£ããèªèãããŠãããã確èªããã®ã«äœ¿ããŸãã |
--markers | å©çšå¯èœãªïŒç»é²ãããŠããïŒããŒã«ãŒã®äžèŠ§ã衚瀺ããŸãã |
--fixtures | å©çšå¯èœãªãã£ã¯ã¹ãã£ã®äžèŠ§ïŒå®çŸ©å Žæãå«ãïŒã衚瀺ããŸãã-v ä»ãã§è©³çŽ°è¡šç€ºã |
--setup-show | åãã¹ããã©ã®ãã£ã¯ã¹ãã£ãã©ã®ããã«ïŒã»ããã¢ããã»ãã£ã¢ããŠã³ïŒäœ¿çšãããã詳现ã«è¡šç€ºããŸãããã£ã¯ã¹ãã£ã®åäœç解ã«åœ¹ç«ã¡ãŸãã |
--color=yes|no|auto | åºåã®ã«ã©ãŒè¡šç€ºãå¶åŸ¡ããŸããããã©ã«ãã¯auto ïŒã¿ãŒããã«ã察å¿ããŠããã°ã«ã©ãŒè¡šç€ºïŒã |
--cov[=PATH] (pytest-cov) | ã«ãã¬ããžæž¬å®ãæå¹ã«ããŸãã察象ã®ãœãŒã¹ã³ãŒããã¹ãæå®ã§ããŸãã |
--cov-report[=TYPE] (pytest-cov) | ã«ãã¬ããžã¬ããŒãã®åœ¢åŒãæå®ããŸã (äŸ: term , html , xml )ã |
-n NUM_PROCESSES (pytest-xdist) | æå®ããæ°ã®ããã»ã¹ã§ãã¹ãã䞊åå®è¡ããŸãã-n auto ã§CPUã³ã¢æ°ãèªåæ€åºã |
ãããã®èšå®ãã¡ã€ã«ãšã³ãã³ãã©ã€ã³ãªãã·ã§ã³ãçµã¿åãããããšã§ãPytestãããæè»ãã€å¹ççã«æŽ»çšããããšãã§ããŸãããããžã§ã¯ããå人ã®å¥œã¿ã«åãããŠæé©ãªèšå®ãèŠã€ããŠã¿ãŠãã ãããð ïž
ð Pytest vs unittestïŒæ¯èŒãšéžæ
Pythonã«ã¯æšæºã©ã€ãã©ãªãšããŠunittest
ãšãããã¹ããã¬ãŒã ã¯ãŒã¯ãå«ãŸããŠããŸããPytestãç»å Žãã以åã¯ãunittest
ãPythonã®ãã¹ãã«ãããäž»èŠãªéžæè¢ã§ãããçŸåšã§ãunittest
ã¯åºã䜿ãããŠããŸãããå€ãã®éçºè
ãç¹å®ã®çç±ããPytestãéžæããããã«ãªã£ãŠããŸããããã§ã¯ãPytestãšunittest
ã®äž»ãªéããæ¯èŒããŠã¿ãŸãããã
ç¹åŸŽ | Pytest | unittest |
---|---|---|
ã€ã³ã¹ããŒã« | å¿
èŠ (pip install pytest ) | äžèŠ (Pythonæšæºã©ã€ãã©ãª) |
ãã¹ãèšè¿°åœ¢åŒ | éåžžã®Pythoné¢æ° (def test_...(): )ã¯ã©ã¹ãå©çšå¯èœ ( class Test...: ) | unittest.TestCase ãç¶æ¿ããã¯ã©ã¹å
ã®ã¡ãœãã (def test_...(self): ) |
ã¢ãµãŒã·ã§ã³ | æšæºã®assert æã䜿çš(äŸ: assert x == y , assert x in list )倱ææã«è©³çŽ°ãªæ¯èŒæ å ±ã衚瀺 | TestCase ã¯ã©ã¹ã®å°çšã¡ãœããã䜿çš(äŸ: self.assertEqual(x, y) , self.assertTrue(x) , self.assertIn(x, list) )å€ãã® assert* ã¡ãœãããèŠããå¿
èŠãã |
ã»ããã¢ãã/ãã£ã¢ããŠã³ | ãã£ã¯ã¹ã㣠(@pytest.fixture )ã¹ã³ãŒãæå® (function, class, module, session) äŸåæ§æ³šå ¥ã«ããæè»ãªçµã¿åãã yield ã«ãããã£ã¢ããŠã³ | ã¯ã©ã¹ã¡ãœãã (setUp , tearDown – åãã¹ãã¡ãœããæ¯)ã¯ã©ã¹ã¡ãœãã ( setUpClass , tearDownClass – ã¯ã©ã¹æ¯)ã¢ãžã¥ãŒã«ã¬ãã« ( setUpModule , tearDownModule ) |
ãã©ã¡ãŒã¿å | @pytest.mark.parametrize ãã³ã¬ãŒã¿ç°¡æœã§åŒ·å | æšæºã§ã¯çŽæ¥çãªæ©èœã¯éå®ç ( unittest.TestSuite ããµããã¹ããå€éšã©ã€ãã©ãªparameterized ãªã©ãå©çšããå¿
èŠãã) |
ãã¹ãæ€åº | ãã¡ã€ã«å(test_*.py , *_test.py )ãã¯ã©ã¹å(Test* )ãé¢æ°å(test_* )ã«ããèªåæ€åº | ãã¡ã€ã«å(test_*.py )ãã¯ã©ã¹å(Test* )ãã¡ãœããå(test_* )ã«ããèªåæ€åº (unittest discover ã³ãã³ããªã©) |
æ¡åŒµæ§ | éåžžã«é«ã è±å¯ãªãã©ã°ã€ã³ãšã³ã·ã¹ãã | éå®ç (ç¬èªã®æ¡åŒµã¯å¯èœã ãPytestã»ã©å®¹æã§ã¯ãªã) |
ç°¡æœã/å¯èªæ§ | ãã€ã©ãŒãã¬ãŒããå°ãªããã³ãŒããç°¡æœã§èªã¿ãããåŸå | ã¯ã©ã¹ããŒã¹ã®æ§é ãå°çšã¢ãµãŒãã¡ãœããã«ãããã³ãŒããåé·ã«ãªããã¡ |
æ¢åãã¹ããšã®äºææ§ | unittest ãnose ã§æžããããã¹ããå®è¡å¯èœ | unittest 圢åŒã®ã¿ |
ã©ã¡ããéžã¶ã¹ããïŒ
å€ãã®å Žåãç¹ã«æ°èŠãããžã§ã¯ãããããå¹ççã§è¡šçŸåè±ããªãã¹ããæžãããå Žåã«ã¯ãPytestãæšå¥šãããŸãããã®çç±ã¯ä»¥äžã®éãã§ãã
- æžãããããšèªã¿ããã:
assert
æã®ã·ã³ãã«ãããã£ã¯ã¹ãã£æ©èœã«ããããã¹ãã³ãŒããçŽæçã§ç解ãããããªããŸãã - 匷åãªæ©èœ: ãã£ã¯ã¹ãã£ããã©ã¡ãŒã¿åãããŒã«ãŒãªã©ã®é«åºŠãªæ©èœãçµã¿èŸŒã¿ã§ããŸãã¯å®¹æã«è¿œå ã§ããè€éãªãã¹ãã·ããªãªã«ã察å¿ããããã§ãã
- é«ãæ¡åŒµæ§: è±å¯ãªãã©ã°ã€ã³ã«ãããã«ãã¬ããžæž¬å®ã䞊åå®è¡ãWebãã¬ãŒã ã¯ãŒã¯é£æºãªã©ãç°¡åã«è¿œå ã§ããŸãã
- 掻çºãªã³ãã¥ããã£ãšéçº: Pytestã¯éåžžã«æŽ»çºã«éçºãç¶ããããŠãããã³ãã¥ããã£ã倧ãããæ å ±ããµããŒããåŸãããã§ãã
äžæ¹ã§ã以äžã®ãããªç¶æ³ã§ã¯unittest
ãéžæããçç±ããããããããŸããã
- å€éšã©ã€ãã©ãªã®è¿œå ãå¶éãããŠããç°å¢: Pytestã¯ãµãŒãããŒãã£ã©ã€ãã©ãªãªã®ã§ãã€ã³ã¹ããŒã«ãèš±å¯ãããªãå ŽåããããŸãã
unittest
ã¯æšæºã©ã€ãã©ãªãªã®ã§ãè¿œå ã€ã³ã¹ããŒã«ã¯äžèŠã§ãã - æ¢åã®ãã¹ãã¹ã€ãŒãã
unittest
ã§å€§èŠæš¡ã«æ§ç¯ãããŠããå Žå: Pytestã¯unittest
ã®ãã¹ããå®è¡ã§ããŸãããå®å šã«Pytestã®å©ç¹ã掻ããã«ã¯æžãæããå¿ èŠã«ãªãå ŽåããããŸãã移è¡ã³ã¹ããèæ ®ããå¿ èŠããããŸãã - Javaã®JUnitãªã©ãxUnitã¹ã¿ã€ã«ã®ãã¹ããã¬ãŒã ã¯ãŒã¯ã«æ
£ããŠããå Žå:
unittest
ã®æ§é ã®æ¹ã銎æã¿ããããããããŸããã
çµè«ãšããŠãç¹å¥ãªå¶çŽããªãéããPytestã¯ãã®çç£æ§ãšæ©èœæ§ã®é«ããããçŸä»£çãªPythonãããžã§ã¯ãã«ããããã¹ããã¬ãŒã ã¯ãŒã¯ã®ç¬¬äžåè£ãšèšããã§ããããè¿·ã£ããPytestããè©ŠããŠã¿ãããšããå§ãããŸããð
ãŸãšãïŒPytestã§ãã¹ãéçºãå éããããïŒ ð
ãããŸã§èŠãŠããããã«ãPytestã¯Pythonã«ããããã¹ãéçºãå€§å¹ ã«å¹çåããããå ç¢ã§ä¿å®æ§ã®é«ããã¹ãã³ãŒããäœæããããã®åŒ·åãªãã¬ãŒã ã¯ãŒã¯ã§ãã
Pytestã®äž»ãªå©ç¹ãå確èªããŸãããã
- â
ã·ã³ãã«ãªæ§æ:
assert
æã ãã§çŽæçã«ãã¹ããèšè¿°ã§ããŸãã - â 匷åãªãã£ã¯ã¹ãã£: ãã¹ãã®æºåãšåŸçä»ãããšã¬ã¬ã³ãã«ç®¡çããã³ãŒãã®åå©çšæ§ãé«ããŸãã
- â æè»ãªãã©ã¡ãŒã¿å: å°ãªãã³ãŒãã§å€ãã®ãã¹ãã±ãŒã¹ãç¶²çŸ ã§ããŸãã
- â 䟿å©ãªããŒã«ãŒ: ãã¹ãã®åé¡ãã¹ããããéžæå®è¡ã容æã«ããŸãã
- â 詳现ãªå€±æã¬ããŒã: ãããã°ãå©ããè±å¯ãªæ å ±ãæäŸããŸãã
- â èªåãã¹ãæ€åº: é¢åãªèšå®ãªãã«ãã¹ããèªèããŸãã
- â è±å¯ãªãã©ã°ã€ã³: ã«ãã¬ããžã䞊åå®è¡ãã¢ãã¯ãªã©ãå¿ èŠãªæ©èœãç°¡åã«è¿œå ã§ããŸãã
ãããã®æ©èœã«ãããPytestã¯Pythonæšæºã®unittest
ãšæ¯èŒããŠãå€ãã®å Žåã§ããçç£çã§å¿«é©ãªãã¹ãäœéšãæäŸããŸãããã¹ãã³ãŒãã®èšè¿°éãæžããå¯èªæ§ãåäžããããšã§ãéçºè
ã¯ããæ¬è³ªçãªããžãã¯ã®æ€èšŒã«éäžã§ããããã«ãªããŸãã
ããããªããPythonã§éçºãè¡ã£ãŠããŠããŸã Pytestãè©Šããããšããªãã®ã§ããã°ããã²å°å ¥ãæ€èšããŠã¿ãŠãã ãããå°èŠæš¡ãªã¹ã¯ãªãããã倧èŠæš¡ãªã¢ããªã±ãŒã·ã§ã³ãŸã§ããããããããžã§ã¯ãã§ãã®æ©æµãåããããšãã§ããã¯ãã§ãããã¹ããæžãããšã¯ããã°ãæ©æã«çºèŠãããªãã¡ã¯ã¿ãªã³ã°ã容æã«ããæçµçã«ã¯ãœãããŠã§ã¢å šäœã®å質ãé«ããããã®éèŠãªæè³ã§ããPytestã¯ãã®æè³å¹æãæ倧åããããã®åªããããŒã«ãšèšããã§ãããã
ãããPytestã䜿ã£ãŠãèªä¿¡ãæã£ãŠã³ãŒããæžããããè¯ããœãããŠã§ã¢éçºãå®çŸããŸãããïŒHappy testing! ðð
ã³ã¡ã³ã