3 ๋ถ„ ์†Œ์š”

TDD(Test Driven Development)?

์ผ๋ฐ˜์ ์œผ๋กœ๋Š” ํ”„๋กœ๊ทธ๋žจ ๊ฐœ๋ฐœ์ด ๋๋‚œ ํ›„ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค.

ํ•˜์ง€๋งŒ TDD ๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ถ€ํ„ฐ ๋จผ์ € ์ž‘์„ฑํ•˜๊ณ , ๊ทธ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ํ†ต๊ณผํ•˜๋Š” ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ๋‚˜์ค‘์— ๋งŒ๋“ ๋‹ค.

TDD์˜ ์ˆœ์„œ

  1. ๊ตฌํ˜„ํ•ด์•ผ ํ•  ๋‚ด์šฉ์„ ์ •์˜ํ•œ๋‹ค. (Need)
  2. ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค. (Test)
  3. ํ…Œ์ŠคํŠธ๋ฅผ ์„ฑ๊ณตํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค. (Code)
  4. ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•œ๋‹ค. (Refactoring)
  5. ๊ตฌํ˜„ํ•ด์•ผ ํ•  ๋‚ด์šฉ์„ ์™„์„ฑํ•  ๋•Œ๊นŒ์ง€ ์œ„์˜ 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

[pytest] python ์ฝ”๋“œ๋ฅผ ํ…Œ์ŠคํŠธ ํ•ด๋ด…์‹œ๋‹ค.

ํƒœ๊ทธ: ,

์นดํ…Œ๊ณ ๋ฆฌ:

์—…๋ฐ์ดํŠธ:

๋Œ“๊ธ€๋‚จ๊ธฐ๊ธฐ