๐ [Python] ํ์ด์ฌ์ ํ ์คํธ ์ฝ๋๋ฅผ ์ด๋ป๊ฒ ์ธ๊น?
TDD(Test Driven Development)?
์ผ๋ฐ์ ์ผ๋ก๋ ํ๋ก๊ทธ๋จ ๊ฐ๋ฐ์ด ๋๋ ํ ํ ์คํธ๋ฅผ ์งํํ๋ค.
ํ์ง๋ง TDD
๋ ํ
์คํธ ์ฝ๋๋ถํฐ ๋จผ์ ์์ฑํ๊ณ , ๊ทธ ํ
์คํธ ์ฝ๋๋ฅผ ํต๊ณผํ๋ ์ค์ ์ฝ๋๋ฅผ ๋์ค์ ๋ง๋ ๋ค.
TDD์ ์์
- ๊ตฌํํด์ผ ํ ๋ด์ฉ์ ์ ์ํ๋ค. (Need)
- ์คํจํ๋ ํ ์คํธ๋ฅผ ์์ฑํ๋ค. (Test)
- ํ ์คํธ๋ฅผ ์ฑ๊ณตํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ค. (Code)
- ์ฝ๋๋ฅผ ๋ฆฌํฉํ ๋งํ๋ค. (Refactoring)
- ๊ตฌํํด์ผ ํ ๋ด์ฉ์ ์์ฑํ ๋๊น์ง ์์ 1 ~ 4๋ฒ์ ๊ณผ์ ์ ๋ฐ๋ณตํ๋ค.
Simple Code
๊ฐ TDD์ ๊ถ๊ทน์ ์ธ ๋ชฉํ์!
Simple Code๋?
์๋ํ๋ ๊นจ๋ํ ์ฝ๋ ๋ฅผ ๋งํ๋ค!
์ฝ๋๊ฐ ๋จ์ํ๋ค๋ ์๋ฏธ๋ณด๋ค๋ ์ค๋ณต์ด ์๊ณ ๋๊ฐ ๋ด๋ ๋ช
ํํ ์ฝ๋๋ฅผ ์๋ฏธํ๋ค
.
TDD๋ฅผ ์ ์ฉํด ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ Simple Code๋ฅผ ์์ฑํ๋ ์ง๋ฆ๊ธธ์ด๋ค!
Unit Test (๋จ์ ํ ์คํธ)
import unittest
sssss
# ์ค์ ์ฝ๋
def leap_year(year):
pass
# ํ
์คํธ์ฝ๋
class LeapYearTest(unittest.TestCase):
def test_leap_year(self):
pass
# ํ
์คํธ๋ฅผ ์งํ
if __name__ == '__main__':
unittest.main()
๋จ์ ํ ์คํธ ๋ชจ๋์ ์ฌ์ฉํ ๋์ ๊ท์น์ ์๋์ ๊ฐ๋ค.
- ํ
์คํธ ์ฝ๋๋ unittest์
TestCase
ํด๋์ค๋ฅผ ์์๋ฐ์ ์์ฑํ๋ค. test_leap_year()
์ฒ๋ผ test~ ๋ก ์์ํ๋ ๋ฉ์๋๋ ์๋์ผ๋ก ์คํ๋๋ค.
๋ค์์ผ๋ก ํ ์คํธ๋ฅผ ์งํํ ๋ ์์ฃผ ์ฌ์ฉํ๋ ๊ฒ์ฆ ๋ฉ์๋๋ ์๋์ ๊ฐ๋ค.
- assertTrue(a) : a๊ฐ ์ฐธ์ธ์ง ์กฐ์ฌํ๋ค.
- assertFalse(a) : a๊ฐ ๊ฑฐ์ง์ธ์ง ์กฐ์ฌํ๋ค.
- assertEqual(a, b) : a์ b๊ฐ ๊ฐ์์ง ์กฐ์ฌํ๋ค.
Pytest?
pytest
๋ ๋ง ๊ทธ๋๋ก python์ test ํ๋ Framework ์!
๐ย pytest ๊ณต์ ๋ฌธ์
ํนํ ๋ฐ์ดํฐ ๋ถ์ ๋ถ์ผ์์ ๋ง์ด ์ฌ์ฉ๋๊ณ ์๋ค๊ณ ํ๋ค!
pandas์์๋ pytest๋ฅผ ํตํด ์ฝ๋ ํ ์คํ ์ ์งํํ๊ณ ์๊ณ , ์ฌ์ง์ด pandas ๊ณต์ ๋ฌธ์์์๋ test์ ์ค์์ฑ์ ์๋ฆฌ๋ฉฐ pytest์ ์ฌ์ฉ๋ฒ์ ํจ๊ป ์ค๋ช ํ๊ณ ์๋ค!
๋ค๋ฅธ ์์๋ก๋ SQLAlchemy
๊ฐ ์๋ค!
python์ ORM ๊ธฐ์ ์ค ํ๋์ธ SQLAlchemy
๋ DB ๊ด๋ จ ๊ฐ๋ฐ์์ ๋ง์ด ํ์ฉ๋๋ ์คํ์์ค์!
์ฌ๊ธฐ์๋ ํ ์คํธ์ ์ค์์ฑ๊ณผ pytest์ ์ฌ์ฉ๋ฒ์ ํจ๊ป ์๋ ค์ฃผ๊ณ ์๋ค.
๐ย SQLAlchemy ๊ณต์ ๋ฌธ์ ์ค test ํํธ
์ด์ฒ๋ผ pytest๋ ์ฌ๋ฌ ๋ถ์ผ, ๊ธฐ์ ์์ ์ฌ์ฉ๋๊ณ ์๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค! โ ๊ทธ๋งํผ ์ ์ฉํ ํ ์คํธ ๋๊ตฌ๋ผ๋ ๋ป์ด๊ฒ ์ง..!
pytest ์์ํ๊ธฐ
Install
$ pip install -U pytest
์ ๋ช ๋ น์ด๋ก ์ค์นํ๊ณ ,
$ pytest --version
์ด๊ฑธ๋ก ๋ฒ์ ํ์ธ์ ํด๋ณด์!
Test code ์์ฑํ๊ธฐ ์์
# test.py
# ํ
์คํธ๋ฅผ ํด๋ณด๊ณ ์ถ์ ํจ์
def func(x):
return x + 1
# ํ
์คํธ ํจ์ -> ์์์ test~๋ก ์์ํ๋ ํจ์๊ฐ ํ
์คํ
ํจ์๋ผ๊ณ ์ธ๊ธํ์๋ค!
def test_answer():
assert func(3) == 5
$ python test.py
- ์ ์ฝ๋๋ ๋น์ฅ ์ง์ ๊ณ์ฐํด๋ด๋ False๊ฐ ๋์ค๋ ๊ฒ์ ์ ์ ์๋ค!
- ์ด๋ฅผ ์คํํ๋ฉด False๊ฐ ๊ฒฐ๊ณผ๋ก ๋์ค๋ ๋์์, ์ด๋ ๋ถ๋ถ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋์ง ์๋ ค์ฃผ๊ณ , ์ด๋ค ํ๋ซํผ์์ ์๋ํ๊ณ ์์ผ๋ฉฐ, ์ด๋ค ์๋ฌ๊ฐ ๋ฐ์ํ๊ฑด์ง ๋ง์ง๋ง์ผ๋ก ์์ฝ์ ํตํด ์ด ๋ช ๊ฐ์ ํ ์คํธ๊ฐ pass & fail ์ธ์ง๋ฅผ ๋ํ๋ด์ค๋ค. โ ์ด์ ๊ฑธ๋ฆฐ ์ด ์๊ฐ๋ ๊ฒฐ๊ณผ๋ก ํจ๊ป ์ค ใทใทใท
Structure
์์ ์์ ๋ [test.py](http://test.py)
๋ฅผ ์์ฑํ๊ณ ํฐ๋ฏธ๋์ python test.py
๋ช
๋ น์ด๋ฅผ ํตํด ํ
์คํธ๋ฅผ ์คํํ๋ค.
๋ํ ํ ํ์ผ์ ์ผ๋ฐ ํจ์์ ํ ์คํธ ์ฝ๋๊ฐ ๊ณต์กดํ๋ค!
ํ์ง๋ง, ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ ๋๋ ํ ์คํธ ์ฝ๋๋ฅผ ๋ฐ๋ก ๊ด๋ฆฌํ๊ณ , ์ด์ ์ ํฉํ๊ฒ ๊ตฌ์กฐ๋ฅผ ๊ตฌ์ฑํด๋๋ ๊ฒ์ด ํจ์จ์ ์ด๋ค.
๊ทธ๋์ ํ
์คํธ ์ฝ๋๋ ํ๋ก์ ํธ ์ฝ๋๋ค๊ณผ ๋ฌ๋ฆฌ test
๋ผ๋ ๋๋ ํ ๋ฆฌ์ ํจ๊ป ๊ด๋ฆฌํ๋ค!
project/
core_code/
__init__.py
sample_code1.py
sample_code2.py
sample_code3.py
tests/
test_sample1.py
test_sample2.py
test_sample3.py
$ python -m pytest tests
์ ๊ตฌ์กฐ๋ก ๊ตฌ์ฑํ๊ณ , ์ด ๋ช ๋ น์ด๋ฅผ ํตํด tests ํ์ผ๋ค์ ์คํํ ์ ์๋ค!
pytest fixtures??
fixtures
๋ผ๋๊ฒ ์๋ค๋๋ฐ..?
์ด๊ฑด pytest์ ํน์ง ์ค ํ๋๋ก, ์ํ๋ ํ ์คํ ์ ์์ด ํ์ํ ๋ถ๋ถ๋ค์ ๊ฐ๊ณ ์๋ ์ฝ๋ ๋๋ ๋ฆฌ์์ค ๋ฅผ ์๋ฏธํ๋ค!
๋ฐ์ฝ๋ ์ดํฐ
์ ํจ๊ป ์ฌ์ฉํ๋ค!
fixtures ์์
# calculator.py
class Calculator(object):
"""Calculator class"""
def __init__(self):
pass
@staticmethod
def add(a, b):
return a + b
@staticmethod
def subtract(a, b):
return a - b
@staticmethod
def multiply(a, b):
return a * b
@staticmethod
def divide(a, b):
return a / b
test - 1
# test_calculator.py
from src.calculator import Calculator
def test_add():
calculator = Calculator()
assert calculator.add(1, 2) == 3
assert calculator.add(2, 2) == 4
def test_subtract():
calculator = Calculator()
assert calculator.subtract(5, 1) == 4
assert calculator.subtract(3, 2) == 1
def test_multiply():
calculator = Calculator()
assert calculator.multiply(2, 2) == 4
assert calculator.multiply(5, 6) == 30
def test_divide():
calculator = Calculator()
assert calculator.divide(8, 2) == 4
assert calculator.divide(9, 3) == 3
calculator = Calculator()
๊ฐ ์ค๋ณต๋๋๊ฑธ ๋ณผ ์ ์๋ค!- ์ด๊ฑธ ๋ ๊น๋ํ๊ฒ ๊ณ ์ณ๋ณด์!
test - 2
import pytest
from src.calculator import Calculator
@pytest.fixture
def calculator():
calculator = Calculator()
return calculator
def test_add(calculator):
assert calculator.add(1, 2) == 3
assert calculator.add(2, 2) == 4
def test_subtract(calculator):
assert calculator.subtract(5, 1) == 4
assert calculator.subtract(3, 2) == 1
def test_multiply(calculator):
assert calculator.multiply(2, 2) == 4
assert calculator.multiply(5, 6) == 30
calculator = Calculator()
๋ฅผ calculator()์ ์ ์ํด ํ ๋ฒ๋ง ์์ฑ๋ ๊ฑธ ๋ณผ ์ ์๋ค!- ์ด์ฒ๋ผ fixture๋ฅผ ์ฌ์ฉํด ์ ์ํ ํจ์๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ฌ์ฉํด ํ ์คํธ๋ฅผ ์ํ ํด๋์ค๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์.
conftest.py ???
๋ง์ฝ ํ ์คํธ๋ฅผ ์ํ ํด๋์ค๋ฅผ ๋ถ๋ฌ์ค๋ ๋ก์ง์ด ์ฌ๋ฌ ํ ์คํธ ํ์ผ์์ ์ฌ์ฉ๋๋ค๊ณ ์๊ฐํด๋ณด์!
๊ทธ๋ผ @pytest.fixture
๊ฐ ๋ถ์ ๋ฉ์๋๋ฅผ ๋ฐ๋ก ํ์ผ๋ก ๋นผ์ ๊ด๋ฆฌํ๊ณ , ํ
์คํธ ํ์ผ์์ import ํด์ ์ฌ์ฉํ๋ฉด ์ข ๋ ํธํ๊ฒ ์ง?
๊ทธ๋์ [conftest.py](http://conftest.py)
๋ผ๋ ํ์ผ์ ๋ง๋ค๊ณ , ์ฌ๊ธฐ์ fixture ๋ฉ์๋๋ฅผ ๊ด๋ฆฌํ๋ฉด ์กฐ๊ธ ๋ ์ฝ๋๊ฐ Simple Code์ ๊ฐ๊น์์ง๋ค!
# conftest.py
@pytest.fixture
def calculator():
calculator = Calculator()
return calculator
์ฐธ๊ณ ์๋ฃ
unittest โ Unit testing framework
107 ์์ฑํ ์ฝ๋๋ฅผ ํ ์คํธํ๋ ค๋ฉด? โ unittest
pytest: helps you write better programs โ pytest documentation
๋๊ธ๋จ๊ธฐ๊ธฐ