๊ฐœ์š”

parametrize๋Š” pytest์—์„œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์˜ ์ค‘๋ณต์„ ์ค„์ด๊ณ  ๋‹ค์–‘ํ•œ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ์œ ์šฉํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋กœ ์—ฌ๋Ÿฌ ์ž…๋ ฅ ์กฐํ•ฉ์— ๋Œ€ํ•ด ๋…๋ฆฝ์ ์ธ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด, ๋น„์Šทํ•œ ํŒจํ„ด์˜ ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐ˜๋ณต ์ž‘์„ฑํ•˜๋Š” ๋Œ€์‹  ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•œ ์ฝ”๋“œ๋กœ ๋‹ค์–‘ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ปค๋ฒ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฃผ์š” ์žฅ์ 

  • ์ฝ”๋“œ ์ค‘๋ณต ๊ฐ์†Œ: ์—ฌ๋Ÿฌ ์ž…๋ ฅ๊ฐ’์„ ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋กœ ์ฒ˜๋ฆฌ
  • ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ: ๋‹ค์–‘ํ•œ ์ž…๋ ฅ๊ณผ ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„
  • ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด์„ฑ: ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์ถ”๊ฐ€ ๋ฐ ์ˆ˜์ •์ด ๊ฐ„ํŽธ
  • ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ํ™•๋Œ€: ๋‹ค์–‘ํ•œ ์ž…๋ ฅ ์กฐํ•ฉ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

@pytest.mark.parametrize("์ธ์ž๋ช…", [๊ฐ’1, ๊ฐ’2, ๊ฐ’3])
def test_ํ•จ์ˆ˜๋ช…(์ธ์ž๋ช…):
    # ํ…Œ์ŠคํŠธ ๋กœ์ง
    assert ...

์ฃผ์š” ๊ธฐ๋Šฅ

1. ์—ฌ๋Ÿฌ ์ธ์ž parametrize

@pytest.mark.parametrize("a, b, expected", [
    (1, 2, 3),
    (5, 5, 10),
    (-1, 1, 0),
    (0, 0, 0),
    (100, 200, 300)
])
def test_add(a, b, expected):
    assert add(a, b) == expected

์‹คํ–‰ ๊ฒฐ๊ณผ:

test_math.py::test_add[1-2-3] PASSED
test_math.py::test_add[5-5-10] PASSED
test_math.py::test_add[-1-1-0] PASSED
test_math.py::test_add[0-0-0] PASSED
test_math.py::test_add[100-200-300] PASSED

๋ณด์ถฉ ์„ค๋ช…: ์—ฌ๋Ÿฌ ์ธ์ž๋ฅผ ํ•œ ๋ฒˆ์— parametrize ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ฐ ํŠœํ”Œ์€ ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ์œ„ ์˜ˆ์‹œ์—์„œ๋Š” 5๊ฐœ์˜ ์ธ์ž ์กฐํ•ฉ์— ๋Œ€ํ•ด test_add ํ•จ์ˆ˜๊ฐ€ 5๋ฒˆ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. pytest๋Š” ์ž๋™์œผ๋กœ ๊ฐ ์ผ€์ด์Šค์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ID๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค(์˜ˆ: [1-2-3]).

2. ํ…Œ์ŠคํŠธ ์‹๋ณ„์„ฑ ๋†’์ด๊ธฐ (ids ์‚ฌ์šฉ)

@pytest.mark.parametrize("a, b, expected", [
    (1, 2, 3),
    (5, 5, 10),
    (-1, 1, 0),
    (0, 0, 0)
], ids=["simple_add", "double_five", "add_neg_pos", "add_zeros"])
def test_add_with_custom_ids(a, b, expected):
    assert add(a, b) == expected

์‹คํ–‰ ๊ฒฐ๊ณผ:

test_math_with_ids.py::test_add_with_custom_ids[simple_add] PASSED
test_math_with_ids.py::test_add_with_custom_ids[double_five] PASSED
test_math_with_ids.py::test_add_with_custom_ids[add_neg_pos] PASSED
test_math_with_ids.py::test_add_with_custom_ids[add_zeros] PASSED

๋ณด์ถฉ ์„ค๋ช…: ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ์ž๋™ ์ƒ์„ฑ๋œ ID([1-2-3])๋งŒ์œผ๋กœ๋Š” ์–ด๋–ค ํ…Œ์ŠคํŠธ์ธ์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ids ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์— ์˜๋ฏธ ์žˆ๋Š” ์ด๋ฆ„์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ์–ด ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ํ•ด์„ํ•˜๊ณ  ๋””๋ฒ„๊น…ํ•˜๊ธฐ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค. ํŠนํžˆ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ–ˆ์„ ๋•Œ ์–ด๋–ค ์ผ€์ด์Šค์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ๋น ๋ฅด๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. pytest.param ํ™œ์šฉ

@pytest.mark.parametrize("a, b, expected", [
    pytest.param(1, 2, 3, id="positive_numbers_check"),
    pytest.param(5, 5, 10, id="equal_numbers_check"),
    pytest.param(-1, 1, 0, id="positive_and_negative_check"),
    pytest.param(0, 0, 0, id="zeros_check"),
])
def test_add_with_pytest_param_ids(a, b, expected):
    assert add(a, b) == expected

์‹คํ–‰ ๊ฒฐ๊ณผ:

test_math_with_ids.py::test_add_with_pytest_param_ids[positive_numbers_check] PASSED
test_math_with_ids.py::test_add_with_pytest_param_ids[equal_numbers_check] PASSED
test_math_with_ids.py::test_add_with_pytest_param_ids[positive_and_negative_check] PASSED
test_math_with_ids.py::test_add_with_pytest_param_ids[zeros_check] PASSED

๋ณด์ถฉ ์„ค๋ช…: pytest.param์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋งˆ๋‹ค ID๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค๋ฅธ pytest ๋งˆํฌ(marks)๋„ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋‹จ์ˆœํžˆ ID๋ฅผ ๋ถ€์—ฌํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋” ๋งŽ์€ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŠน์ • ์ผ€์ด์Šค๋งŒ ๊ฑด๋„ˆ๋›ฐ๊ฑฐ๋‚˜(skip) ์‹คํŒจ๊ฐ€ ์˜ˆ์ƒ๋˜๋Š” ์ผ€์ด์Šค(xfail)๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4. ์—ฌ๋Ÿฌ parametrize ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์กฐํ•ฉ

@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    print(f"x: {x}, y: {y}")
    assert True

์‹คํ–‰ ๊ฒฐ๊ณผ:

test_multiple_parametrize.py::test_foo[2-0] PASSED
test_multiple_parametrize.py::test_foo[3-0] PASSED
test_multiple_parametrize.py::test_foo[2-1] PASSED
test_multiple_parametrize.py::test_foo[3-1] PASSED

๋ณด์ถฉ ์„ค๋ช…: ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜์— ์—ฌ๋Ÿฌ parametrize ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ ์šฉํ•˜๋ฉด, pytest๋Š” ๋ชจ๋“  ๊ฐ€๋Šฅํ•œ ์กฐํ•ฉ(๋ฐ์นด๋ฅดํŠธ ๊ณฑ)์„ ์ƒ์„ฑํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์œ„ ์˜ˆ์‹œ์—์„œ๋Š” x์˜ 2๊ฐ€์ง€ ๊ฐ’๊ณผ y์˜ 2๊ฐ€์ง€ ๊ฐ’์˜ ์กฐํ•ฉ์œผ๋กœ ์ด 4๋ฒˆ์˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ๋‘ ๊ฐœ ์ด์ƒ์˜ ๋…๋ฆฝ์ ์ธ ๋ณ€์ˆ˜๋“ค์˜ ๋ชจ๋“  ์กฐํ•ฉ์„ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์ˆœ์„œ์— ๋”ฐ๋ผ ํ…Œ์ŠคํŠธ ID์˜ ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆœ์„œ๊ฐ€ ๊ฒฐ์ •๋˜๋ฏ€๋กœ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

5. fixture parametrize (indirect=True)

@pytest.fixture
def my_fixture(request):
    # request.param์€ parametrize์—์„œ ์ „๋‹ฌ๋œ ๊ฐ’์ž…๋‹ˆ๋‹ค
    return request.param * 10
 
@pytest.mark.parametrize("my_fixture", [1, 2, 3], indirect=True)
def test_indirect_fixture(my_fixture):
    assert my_fixture % 10 == 0
    if my_fixture == 10:
        assert True
    elif my_fixture == 20:
        assert True
    elif my_fixture == 30:
        assert True

์‹คํ–‰ ๊ฒฐ๊ณผ:

test_indirect_parametrize.py::test_indirect_fixture[1] PASSED
test_indirect_parametrize.py::test_indirect_fixture[2] PASSED
test_indirect_parametrize.py::test_indirect_fixture[3] PASSED

๋ณด์ถฉ ์„ค๋ช…: indirect=True ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด parametrize๋œ ๊ฐ’์„ ์ง์ ‘ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜์˜ ์ธ์ž๋กœ ์ „๋‹ฌํ•˜๋Š” ๋Œ€์‹ , ๊ฐ™์€ ์ด๋ฆ„์˜ fixture์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ fixture ํ•จ์ˆ˜ ๋‚ด์—์„œ request.param์„ ํ†ตํ•ด parametrize๋œ ๊ฐ’์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค:

  • ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ „์— ๋ณต์žกํ•œ ์„ค์ •์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ
  • parametrize๋œ ๊ฐ’์„ ๊ฐ€๊ณตํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ
  • ๋™์ผํ•œ ํ…Œ์ŠคํŠธ์— ๋Œ€ํ•ด ์„œ๋กœ ๋‹ค๋ฅธ ํ™˜๊ฒฝ ์„ค์ •์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ

๋˜ํ•œ indirect=['param1', 'param2'] ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์ค‘ ์ผ๋ถ€๋งŒ ๊ฐ„์ ‘ ์ฒ˜๋ฆฌํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

6. ๋‹ค๋ฅธ ๋งˆํฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ

@pytest.mark.parametrize("value, expected_type", [
    (10, int),
    ("hello", str),
    pytest.param(5.5, float, marks=pytest.mark.skip(reason="๋ถ€๋™ ์†Œ์ˆ˜์  ํ…Œ์ŠคํŠธ๋Š” ํ˜„์žฌ ๊ฑด๋„ˆ๋œ€")),
    pytest.param([1, 2], list, marks=pytest.mark.xfail(reason="๋ฆฌ์ŠคํŠธ ํƒ€์ž…์€ ์‹คํŒจ ์˜ˆ์ƒ"))
])
def test_types(value, expected_type):
    assert isinstance(value, expected_type)

์‹คํ–‰ ๊ฒฐ๊ณผ:

test_parametrize_with_marks.py::test_types[10-<class 'int'>] PASSED
test_parametrize_with_marks.py::test_types[hello-<class 'str'>] PASSED
test_parametrize_with_marks.py::test_types[5.5-<class 'float'>] SKIPPED (๋ถ€๋™ ์†Œ์ˆ˜์  ํ…Œ์ŠคํŠธ๋Š” ํ˜„์žฌ ๊ฑด๋„ˆ๋œ€)
test_parametrize_with_marks.py::test_types[[1, 2]-<class 'list'>] xfail (๋ฆฌ์ŠคํŠธ ํƒ€์ž…์€ ์‹คํŒจ ์˜ˆ์ƒ)

๋ณด์ถฉ ์„ค๋ช…: pytest.param๊ณผ ํ•จ๊ป˜ ๋‹ค์–‘ํ•œ pytest ๋งˆํฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์— ๋Œ€ํ•ด ์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค:

  • pytest.mark.skip: ํŠน์ • ์ผ€์ด์Šค๋ฅผ ๊ฑด๋„ˆ๋›ฐ๋„๋ก ์ง€์ •
  • pytest.mark.xfail: ์‹คํŒจ๊ฐ€ ์˜ˆ์ƒ๋˜๋Š” ์ผ€์ด์Šค๋ฅผ ํ‘œ์‹œ (ํ…Œ์ŠคํŠธ๋Š” ์‹คํ–‰๋˜์ง€๋งŒ ์‹คํŒจํ•ด๋„ ์ „์ฒด ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Œ)
  • pytest.mark.parametrize: ์ด ์˜ˆ์ œ์—์„œ์ฒ˜๋Ÿผ ๋‹ค๋ฅธ ๋งˆํฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

์ด๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ ์ค‘์ธ ๊ธฐ๋Šฅ์ด๋‚˜ ์•„์ง ๊ตฌํ˜„๋˜์ง€ ์•Š์€ ๋ถ€๋ถ„์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ๋ฏธ๋ฆฌ ์ž‘์„ฑํ•˜๊ณ , ์ ์ ˆํžˆ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ํŠน์ • ํ™˜๊ฒฝ์ด๋‚˜ ์กฐ๊ฑด์—์„œ๋งŒ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ์—๋„ ์ข‹์Šต๋‹ˆ๋‹ค.

์ถ”๊ฐ€ ํŒ

ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’ ์ƒ์„ฑ์„ ์œ„ํ•œ ํ•จ์ˆ˜ ์‚ฌ์šฉ

def generate_test_data():
    return [(i, i*2) for i in range(5)]
 
@pytest.mark.parametrize("input, expected", generate_test_data())
def test_double(input, expected):
    assert input * 2 == expected

๋งค๊ฐœ๋ณ€์ˆ˜ํ™”๋œ ํด๋ž˜์Šค

@pytest.mark.parametrize("value", [1, 2, 3])
class TestClass:
    def test_one(self, value):
        assert value >= 1
        
    def test_two(self, value):
        assert value <= 3

๊ฒฐ๋ก 

pytest.mark.parametrize๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์˜ ํšจ์œจ์„ฑ๊ณผ ๊ฐ€๋…์„ฑ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ค๋ฉฐ, ๋‹ค์–‘ํ•œ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ , ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ๊ด€๋ฆฌ๊ฐ€ ์šฉ์ดํ•˜๋ฉฐ, ๋‹ค์–‘ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ํ™•๋Œ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์—ฌ๋Ÿฌ ์ž…๋ ฅ ์กฐํ•ฉ์— ๋Œ€ํ•ด ๋™์ผํ•œ ๋กœ์ง์„ ๋ฐ˜๋ณต์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉํ•˜๋ฉด ๋งค์šฐ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.