Python์ Hypothesis ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์์ฑ ๊ธฐ๋ฐ ํ ์คํธ๋ฅผ ๊ฒฝํํด๋ณด์ธ์. ์์ ๊ธฐ๋ฐ ํ ์คํธ๋ฅผ ๋์ด ์ฃ์ง ์ผ์ด์ค๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋ ๊ฒฌ๊ณ ํ๊ณ ์ ๋ขฐํ ์ ์๋ ์ํํธ์จ์ด๋ฅผ ๊ตฌ์ถํ์ธ์.
๋จ์ ํ ์คํธ๋ฅผ ๋์ด์: Python์ Hypothesis๋ฅผ ์ด์ฉํ ์์ฑ ๊ธฐ๋ฐ ํ ์คํธ ์ฌ์ธต ๋ถ์
์ํํธ์จ์ด ๊ฐ๋ฐ์ ์ธ๊ณ์์ ํ
์คํ
์ ํ์ง์ ์ด์์
๋๋ค. ์์ญ ๋
๋์ ์ง๋ฐฐ์ ์ธ ํจ๋ฌ๋ค์์ ์์ ๊ธฐ๋ฐ ํ
์คํ
์ด์์ต๋๋ค. ์ฐ๋ฆฌ๋ ์ธ์ฌํ๊ฒ ์
๋ ฅ์ ๋ง๋ค๊ณ , ์์ ์ถ๋ ฅ์ ์ ์ํ๋ฉฐ, ์ฝ๋๊ฐ ๊ณํ๋๋ก ๋์ํ๋์ง ๊ฒ์ฆํ๊ธฐ ์ํด ๋จ์ธ๋ฌธ(assertion)์ ์์ฑํฉ๋๋ค. unittest
๋ pytest
๊ฐ์ ํ๋ ์์ํฌ์์ ๋ณผ ์ ์๋ ์ด ์ ๊ทผ ๋ฐฉ์์ ๊ฐ๋ ฅํ๊ณ ํ์์ ์
๋๋ค. ํ์ง๋ง ์ฌ๋ฌ๋ถ์ด ์๊ฐ์ง๋ ๋ชปํ๋ ๋ฒ๊ทธ๋ฅผ ์ฐพ์๋ผ ์ ์๋ ๋ณด์์ ์ธ ์ ๊ทผ ๋ฐฉ์์ด ์๋ค๊ณ ํ๋ค๋ฉด ์ด๋จ๊น์?
์์ฑ ๊ธฐ๋ฐ ํ ์คํ ์ ์ธ๊ณ์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค. ์ด๋ ํ ์คํธ์ ์ด์ ์ ํน์ ์์ ์์ ์ฝ๋์ ์ผ๋ฐ์ ์ธ ์์ฑ์ ๊ฒ์ฆํ๋ ๊ฒ์ผ๋ก ์ฎ๊ธฐ๋ ํจ๋ฌ๋ค์์ ๋๋ค. ๊ทธ๋ฆฌ๊ณ ํ์ด์ฌ ์ํ๊ณ์์ ์ด ์ ๊ทผ ๋ฐฉ์์ ๋ ๋ณด์ ์ธ ์ฑํผ์ธ์ Hypothesis๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
์ด ํฌ๊ด์ ์ธ ๊ฐ์ด๋๋ ์ฌ๋ฌ๋ถ์ ์์ ํ ์ด๋ณด์์์ Hypothesis๋ฅผ ์ฌ์ฉํ ์์ฑ ๊ธฐ๋ฐ ํ ์คํ ์ ์์ ๊ฐ ์๋ ์ค๋ฌด์๋ก ์ด๋์ด ์ค ๊ฒ์ ๋๋ค. ์ฐ๋ฆฌ๋ ํต์ฌ ๊ฐ๋ ์ ํ์ํ๊ณ , ์ค์ ์์ ๋ฅผ ๊น์ด ํ๊ณ ๋ค๋ฉฐ, ์ด ๊ฐ๋ ฅํ ๋๊ตฌ๋ฅผ ์ผ์์ ์ธ ๊ฐ๋ฐ ์ํฌํ๋ก์ฐ์ ํตํฉํ์ฌ ๋ ๊ฒฌ๊ณ ํ๊ณ , ์ ๋ขฐํ ์ ์์ผ๋ฉฐ, ๋ฒ๊ทธ์ ๊ฐํ ์ํํธ์จ์ด๋ฅผ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ธ ๊ฒ์ ๋๋ค.
์์ฑ ๊ธฐ๋ฐ ํ ์คํธ๋ ๋ฌด์์ธ๊ฐ? ์ฌ๊ณ ์ ์ ํ
Hypothesis๋ฅผ ์ดํดํ๊ธฐ ์ํด์๋ ๋จผ์ ์์ฑ ๊ธฐ๋ฐ ํ ์คํ ์ ๊ทผ๋ณธ์ ์ธ ์์ด๋์ด๋ฅผ ํ์ ํด์ผ ํฉ๋๋ค. ์ฐ๋ฆฌ ๋ชจ๋๊ฐ ์๊ณ ์๋ ์ ํต์ ์ธ ์์ ๊ธฐ๋ฐ ํ ์คํ ๊ณผ ๋น๊ตํด ๋ณด๊ฒ ์ต๋๋ค.
์์ ๊ธฐ๋ฐ ํ ์คํธ: ์ต์ํ ๋ฐฉ์
์ฌ๋ฌ๋ถ์ด ์ง์ ๋ง๋ ์ ๋ ฌ ํจ์ my_sort()
๋ฅผ ์์ฑํ๋ค๊ณ ์์ํด ๋ด
์๋ค. ์์ ๊ธฐ๋ฐ ํ
์คํ
์์๋ ์ฌ๋ฌ๋ถ์ ์ฌ๊ณ ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ ๊ฒ์
๋๋ค:
- "๊ฐ๋จํ ์ ๋ ฌ๋ ๋ฆฌ์คํธ๋ก ํ
์คํธํด ๋ณด์." ->
assert my_sort([1, 2, 3]) == [1, 2, 3]
- "์ญ์์ผ๋ก ์ ๋ ฌ๋ ๋ฆฌ์คํธ๋ ์ด๋จ๊น?" ->
assert my_sort([3, 2, 1]) == [1, 2, 3]
- "๋น ๋ฆฌ์คํธ๋?" ->
assert my_sort([]) == []
- "์ค๋ณต๋ ๊ฐ์ด ์๋ ๋ฆฌ์คํธ๋?" ->
assert my_sort([5, 1, 5, 2]) == [1, 2, 5, 5]
- "๊ทธ๋ฆฌ๊ณ ์์๊ฐ ์๋ ๋ฆฌ์คํธ๋?" ->
assert my_sort([-1, -5, 0]) == [-5, -1, 0]
์ด ๋ฐฉ๋ฒ์ ํจ๊ณผ์ ์ด์ง๋ง, ๊ทผ๋ณธ์ ์ธ ํ๊ณ๊ฐ ์์ต๋๋ค: ์ฌ๋ฌ๋ถ์ด ์๊ฐํ ์ ์๋ ์ผ์ด์ค๋ง ํ ์คํธํ๋ค๋ ๊ฒ์ ๋๋ค. ํ ์คํธ๋ ์ฌ๋ฌ๋ถ์ ์์๋ ฅ๋งํผ๋ง ํจ๊ณผ์ ์ ๋๋ค. ๋งค์ฐ ํฐ ์ซ์, ๋ถ๋ ์์์ ๋ถ์ ํ์ฑ, ํน์ ์ ๋์ฝ๋ ๋ฌธ์ ๋๋ ์์์น ๋ชปํ ๋์์ ์ ๋ฐํ๋ ๋ณต์กํ ๋ฐ์ดํฐ ์กฐํฉ๊ณผ ๊ด๋ จ๋ ์ฃ์ง ์ผ์ด์ค๋ฅผ ๋์น ์ ์์ต๋๋ค.
์์ฑ ๊ธฐ๋ฐ ํ ์คํธ: ๋ถ๋ณ ์์ฑ์ผ๋ก ์๊ฐํ๊ธฐ
์์ฑ ๊ธฐ๋ฐ ํ
์คํ
์ ํ๋๋ฅผ ๋ฐ๊ฟ๋๋ค. ํน์ ์์ ๋ฅผ ์ ๊ณตํ๋ ๋์ , ์ฌ๋ฌ๋ถ์ ํจ์์ ์์ฑ(properties) ๋๋ ๋ถ๋ณ ์์ฑ(invariants)์ ์ ์ํฉ๋๋ค. ์ฆ, ๋ชจ๋ ์ ํจํ ์
๋ ฅ์ ๋ํด ์ฐธ์ด์ด์ผ ํ๋ ๊ท์น์
๋๋ค. ์ฐ๋ฆฌ์ my_sort()
ํจ์์ ๋ํ ์์ฑ์ ๋ค์๊ณผ ๊ฐ์ ์ ์์ต๋๋ค:
- ์ถ๋ ฅ์ ์ ๋ ฌ๋์ด ์๋ค: ์์์ ์ซ์ ๋ฆฌ์คํธ์ ๋ํด, ์ถ๋ ฅ ๋ฆฌ์คํธ์ ๋ชจ๋ ์์๋ ๊ทธ ๋ค์ ์ค๋ ์์๋ณด๋ค ์๊ฑฐ๋ ๊ฐ์ต๋๋ค.
- ์ถ๋ ฅ์ ์ ๋ ฅ๊ณผ ๋์ผํ ์์๋ฅผ ํฌํจํ๋ค: ์ ๋ ฌ๋ ๋ฆฌ์คํธ๋ ์๋ ๋ฆฌ์คํธ์ ์์ด์ผ ๋ฟ์ด๋ฉฐ, ์์๊ฐ ์ถ๊ฐ๋๊ฑฐ๋ ์์ค๋์ง ์์ต๋๋ค.
- ํจ์๋ ๋ฉฑ๋ฑ์ฑ(idempotent)์ ๊ฐ์ง๋ค: ์ด๋ฏธ ์ ๋ ฌ๋ ๋ฆฌ์คํธ๋ฅผ ์ ๋ ฌํด๋ ๋ณํ์ง ์์์ผ ํฉ๋๋ค. ์ฆ,
my_sort(my_sort(some_list)) == my_sort(some_list)
์ ๋๋ค.
์ด ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด, ์ฌ๋ฌ๋ถ์ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๋ ๊ฒ์ด ์๋๋๋ค. ๊ท์น์ ์์ฑํ๋ ๊ฒ์ ๋๋ค. ๊ทธ๋ฐ ๋ค์ Hypothesis์ ๊ฐ์ ํ๋ ์์ํฌ๊ฐ ์ฌ๋ฌ๋ถ์ ์์ฑ์ด ํ๋ ธ์์ ์ฆ๋ช ํ๊ธฐ ์ํด ์๋ฐฑ ๋๋ ์์ฒ ๊ฐ์ ๋ฌด์์์ ์ด๊ณ ๋ค์ํ๋ฉฐ ์ข ์ข ๊ต๋ฌํ ์ ๋ ฅ๋ค์ ์์ฑํ๋๋ก ํฉ๋๋ค. ๋ง์ฝ ์์ฑ์ ๊นจ๋จ๋ฆฌ๋ ์ ๋ ฅ์ ๋ฐ๊ฒฌํ๋ฉด, ๊ทธ๊ฒ์ ๋ฒ๊ทธ๋ฅผ ์ฐพ์ ๊ฒ์ ๋๋ค.
Hypothesis ์๊ฐ: ์๋ํ๋ ํ ์คํธ ๋ฐ์ดํฐ ์์ฑ๊ธฐ
Hypothesis๋ ํ์ด์ฌ์ ์ํ ์ต๊ณ ์ ์์ฑ ๊ธฐ๋ฐ ํ ์คํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. ์ฌ๋ฌ๋ถ์ด ์ ์ํ ์์ฑ์ ๊ฐ์ ธ์ ๊ทธ๊ฒ์ ๋์ ํ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๋ ํ๋ ์์ ์ ์ํํฉ๋๋ค. ์ด๊ฒ์ ๋จ์ํ ๋ฌด์์ ๋ฐ์ดํฐ ์์ฑ๊ธฐ๊ฐ ์๋๋ผ, ํจ์จ์ ์ผ๋ก ๋ฒ๊ทธ๋ฅผ ์ฐพ๋๋ก ์ค๊ณ๋ ์ง๋ฅ์ ์ด๊ณ ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค.
Hypothesis์ ์ฃผ์ ํน์ง
- ์๋ ํ ์คํธ ์ผ์ด์ค ์์ฑ: ํ์ํ ๋ฐ์ดํฐ์ *ํํ*๋ฅผ ์ ์ํ๋ฉด (์: "์ ์ ๋ฆฌ์คํธ", "๋ฌธ์๋ง ํฌํจ๋ ๋ฌธ์์ด", "๋ฏธ๋์ datetime ๊ฐ์ฒด"), Hypothesis๋ ๊ทธ ํํ์ ๋ง๋ ๋งค์ฐ ๋ค์ํ ์์ ๋ฅผ ์์ฑํฉ๋๋ค.
- ์ง๋ฅ์ ์ถ์(Intelligent Shrinking): ์ด๊ฒ์ด ๋ฐ๋ก ๋ง๋ฒ ๊ฐ์ ๊ธฐ๋ฅ์
๋๋ค. Hypothesis๊ฐ ์คํจํ๋ ํ
์คํธ ์ผ์ด์ค๋ฅผ ์ฐพ์์ ๋(์: ์ ๋ ฌ ํจ์๋ฅผ ๋ค์ด์ํค๋ 50๊ฐ์ ๋ณต์์๋ก ์ด๋ฃจ์ด์ง ๋ฆฌ์คํธ), ๋จ์ํ ๊ทธ ๊ฑฐ๋ํ ๋ฆฌ์คํธ๋ฅผ ๋ณด๊ณ ํ์ง ์์ต๋๋ค. ์ง๋ฅ์ ์ด๊ณ ์๋์ผ๋ก ์
๋ ฅ์ ๋จ์ํํ์ฌ ์คํจ๋ฅผ ์ ๋ฐํ๋ ๊ฐ๋ฅํ ๊ฐ์ฅ ์์ ์์ ๋ฅผ ์ฐพ์๋
๋๋ค. 50๊ฐ ์์์ ๋ฆฌ์คํธ ๋์ , ๋จ์ง
[inf, nan]
์ผ๋ก ์ธํด ์คํจ๊ฐ ๋ฐ์ํ๋ค๊ณ ๋ณด๊ณ ํ ์ ์์ต๋๋ค. ์ด๋ ๋๋ฒ๊น ์ ๋ฏฟ์ ์ ์์ ์ ๋๋ก ๋น ๋ฅด๊ณ ํจ์จ์ ์ผ๋ก ๋ง๋ญ๋๋ค. - ์ํํ ํตํฉ: Hypothesis๋
pytest
๋unittest
์ ๊ฐ์ ์ธ๊ธฐ ์๋ ํ ์คํ ํ๋ ์์ํฌ์ ์๋ฒฝํ๊ฒ ํตํฉ๋ฉ๋๋ค. ๊ธฐ์กด์ ์์ ๊ธฐ๋ฐ ํ ์คํธ์ ํจ๊ป ์์ฑ ๊ธฐ๋ฐ ํ ์คํธ๋ฅผ ์ถ๊ฐํ์ฌ ์ํฌํ๋ก์ฐ๋ฅผ ๋ณ๊ฒฝํ ํ์๊ฐ ์์ต๋๋ค. - ํ๋ถํ ์ ๋ต(Strategy) ๋ผ์ด๋ธ๋ฌ๋ฆฌ: ๋จ์ํ ์ ์์ ๋ฌธ์์ด๋ถํฐ ๋ณต์กํ๊ณ ์ค์ฒฉ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ, ์๊ฐ๋๋ฅผ ์ธ์ํ๋ datetime, ์ฌ์ง์ด NumPy ๋ฐฐ์ด์ ์ด๋ฅด๊ธฐ๊น์ง ๋ชจ๋ ๊ฒ์ ์์ฑํ๊ธฐ ์ํ ๋ฐฉ๋ํ ๋ด์ฅ "์ ๋ต" ๋ชจ์์ด ํจ๊ป ์ ๊ณต๋ฉ๋๋ค.
- ์ํ ๊ธฐ๋ฐ ํ ์คํ (Stateful Testing): ๋ ๋ณต์กํ ์์คํ ์ ๊ฒฝ์ฐ, Hypothesis๋ ์ผ๋ จ์ ๋์์ ํ ์คํธํ์ฌ ์ํ ์ ์ด์์ ๋ฒ๊ทธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. ์ด๋ ์์ ๊ธฐ๋ฐ ํ ์คํ ์ผ๋ก๋ ์ ๋ช ๋๊ฒ ์ด๋ ค์ด ์์ ์ ๋๋ค.
์์ํ๊ธฐ: ์ฒซ ๋ฒ์งธ Hypothesis ํ ์คํธ
์ง์ ํ๋ฒ ํด๋ด ์๋ค. Hypothesis๋ฅผ ์ดํดํ๋ ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ ์ค์ ๋ก ๋์ํ๋ ๊ฒ์ ๋ณด๋ ๊ฒ์ ๋๋ค.
์ค์น
๋จผ์ Hypothesis์ ์ ํธํ๋ ํ
์คํธ ๋ฌ๋(์ฌ๊ธฐ์๋ pytest
๋ฅผ ์ฌ์ฉ)๋ฅผ ์ค์นํด์ผ ํฉ๋๋ค. ๊ฐ๋จํฉ๋๋ค:
pip install pytest hypothesis
๊ฐ๋จํ ์์ : ์ ๋๊ฐ ํจ์
์ซ์์ ์ ๋๊ฐ์ ๊ณ์ฐํ๋ ๊ฐ๋จํ ํจ์๋ฅผ ์๊ฐํด ๋ด ์๋ค. ์ฝ๊ฐ์ ๋ฒ๊ทธ๊ฐ ์๋ ๊ตฌํ์ ๋ค์๊ณผ ๊ฐ์ ์ ์์ต๋๋ค:
# `my_math.py` ํ์ผ def custom_abs(x): """์ ๋๊ฐ ํจ์์ ์ปค์คํ ๊ตฌํ.""" if x < 0: return -x return x
์ด์ ํ
์คํธ ํ์ผ test_my_math.py
๋ฅผ ์์ฑํด ๋ด
์๋ค. ๋จผ์ , ์ ํต์ ์ธ pytest
์ ๊ทผ ๋ฐฉ์์
๋๋ค:
# test_my_math.py (์์ ๊ธฐ๋ฐ) def test_abs_positive(): assert custom_abs(5) == 5 def test_abs_negative(): assert custom_abs(-5) == 5 def test_abs_zero(): assert custom_abs(0) == 0
์ด ํ ์คํธ๋ค์ ํต๊ณผํฉ๋๋ค. ์ด ์์ ๋ค์ ๊ธฐ๋ฐ์ผ๋ก ๋ณด๋ฉด ์ฐ๋ฆฌ ํจ์๋ ์ฌ๋ฐ๋ฅด๊ฒ ๋ณด์ ๋๋ค. ํ์ง๋ง ์ด์ Hypothesis๋ฅผ ์ฌ์ฉํ์ฌ ์์ฑ ๊ธฐ๋ฐ ํ ์คํธ๋ฅผ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค. ์ ๋๊ฐ ํจ์์ ํต์ฌ ์์ฑ์ ๋ฌด์์ผ๊น์? ๊ฒฐ๊ณผ๋ ์ ๋ ์์์ฌ์๋ ์ ๋๋ค๋ ๊ฒ์ ๋๋ค.
# test_my_math.py (Hypothesis๋ฅผ ์ด์ฉํ ์์ฑ ๊ธฐ๋ฐ) from hypothesis import given from hypothesis import strategies as st from my_math import custom_abs @given(st.integers()) def test_abs_property_is_non_negative(x): """์์ฑ: ๋ชจ๋ ์ ์์ ์ ๋๊ฐ์ ํญ์ >= 0์ด๋ค.""" assert custom_abs(x) >= 0
ํ๋์ฉ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
from hypothesis import given, strategies as st
: ํ์ํ ๊ตฌ์ฑ ์์๋ฅผ ๊ฐ์ ธ์ต๋๋ค.given
์ ์ผ๋ฐ ํ ์คํธ ํจ์๋ฅผ ์์ฑ ๊ธฐ๋ฐ ํ ์คํธ๋ก ๋ฐ๊พธ๋ ๋ฐ์ฝ๋ ์ดํฐ์ ๋๋ค.strategies
๋ ๋ฐ์ดํฐ ์์ฑ๊ธฐ๋ฅผ ์ฐพ์ ์ ์๋ ๋ชจ๋์ ๋๋ค.@given(st.integers())
: ์ด๊ฒ์ด ํ ์คํธ์ ํต์ฌ์ ๋๋ค.@given
๋ฐ์ฝ๋ ์ดํฐ๋ Hypothesis์๊ฒ ์ด ํ ์คํธ ํจ์๋ฅผ ์ฌ๋ฌ ๋ฒ ์คํํ๋๋ก ์ง์ํฉ๋๋ค. ๊ฐ ์คํ๋ง๋ค ์ ๊ณต๋ ์ ๋ต์ธst.integers()
๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ์์ฑํ๊ณ , ์ด๋ฅผ ์ธ์x
๋ก ํ ์คํธ ํจ์์ ์ ๋ฌํฉ๋๋ค.assert custom_abs(x) >= 0
: ์ด๊ฒ์ด ์ฐ๋ฆฌ์ ์์ฑ์ ๋๋ค. Hypothesis๊ฐ ์ด๋ค ์ ์x
๋ฅผ ๋ง๋ค์ด๋ด๋ , ์ฐ๋ฆฌ ํจ์์ ๊ฒฐ๊ณผ๋ 0๋ณด๋ค ํฌ๊ฑฐ๋ ๊ฐ์์ผ ํ๋ค๊ณ ๋จ์ธํฉ๋๋ค.
pytest
๋ก ์ด๊ฒ์ ์คํํ๋ฉด, ๋ง์ ๊ฐ์ ๋ํด ํต๊ณผํ ๊ฒ์
๋๋ค. Hypothesis๋ 0, -1, 1, ํฐ ์์, ํฐ ์์ ๋ฑ์ ์๋ํ ๊ฒ์
๋๋ค. ์ฐ๋ฆฌ์ ๊ฐ๋จํ ํจ์๋ ์ด ๋ชจ๋ ๊ฒ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌํฉ๋๋ค. ์ด์ , ์ฝ์ ์ ์ฐพ์ ์ ์๋์ง ๋ค๋ฅธ ์ ๋ต์ ์๋ํด ๋ณด๊ฒ ์ต๋๋ค.
# ๋ถ๋ ์์์ ์ซ์๋ก ํ ์คํธํด ๋ณด์ @given(st.floats()) def test_abs_floats_property(x): assert custom_abs(x) >= 0
์ด๊ฒ์ ์คํํ๋ฉด, Hypothesis๋ ๋น ๋ฅด๊ฒ ์คํจํ๋ ์ผ์ด์ค๋ฅผ ์ฐพ์ ๊ฒ์ ๋๋ค!
Falsifying example: test_abs_floats_property(x=nan) ... assert custom_abs(nan) >= 0 AssertionError: assert nan >= 0
Hypothesis๋ ์ฐ๋ฆฌ ํจ์๊ฐ float('nan')
(์ซ์๊ฐ ์๋)์ ์
๋ ฅ๋ฐ์์ ๋ nan
์ ๋ฐํํ๋ค๋ ๊ฒ์ ๋ฐ๊ฒฌํ์ต๋๋ค. nan >= 0
์ด๋ผ๋ ๋จ์ธ๋ฌธ์ ๊ฑฐ์ง์
๋๋ค. ์ฐ๋ฆฌ๋ ์๋์ผ๋ก ํ
์คํธํ ์๊ฐ์ ๊ฑฐ์ ํ์ง ์์์ ๋ฏธ๋ฌํ ๋ฒ๊ทธ๋ฅผ ๋ฐฉ๊ธ ์ฐพ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ๋ฅผ ์ฒ๋ฆฌํ๋๋ก ํจ์๋ฅผ ์์ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ValueError
๋ฅผ ๋ฐ์์ํค๊ฑฐ๋ ํน์ ๊ฐ์ ๋ฐํํ๋๋ก ๋ง์ด์ฃ .
๋ ์ข์ ์ ์, ๋ง์ฝ ๋ฒ๊ทธ๊ฐ ๋งค์ฐ ํน์ ํ ๋ถ๋ ์์์ ์์ ๋ฐ์ํ๋ค๋ฉด ์ด๋ ์๊น์? Hypothesis์ ์ถ์(shrinker) ๊ธฐ๋ฅ์ ํฌ๊ณ ๋ณต์กํ ์คํจ ์ซ์๋ฅผ ๊ฐ์ ธ์์ ์ฌ์ ํ ๋ฒ๊ทธ๋ฅผ ์ ๋ฐํ๋ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฒ์ ์ผ๋ก ์ค์ฌ์ฃผ์์ ๊ฒ์ ๋๋ค.
์ ๋ต์ ํ: ํ ์คํธ ๋ฐ์ดํฐ ๋ง๋ค๊ธฐ
์ ๋ต(Strategies)์ Hypothesis์ ํต์ฌ์ ๋๋ค. ๊ทธ๊ฒ๋ค์ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๊ธฐ ์ํ ๋ ์ํผ์ ๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ์๋ ๋ฐฉ๋ํ ์ข ๋ฅ์ ๋ด์ฅ ์ ๋ต์ด ํฌํจ๋์ด ์์ผ๋ฉฐ, ์ฌ๋ฌ๋ถ์ ๊ทธ๊ฒ๋ค์ ๊ฒฐํฉํ๊ณ ์ฌ์ฉ์ ์ ์ํ์ฌ ์์ํ ์ ์๋ ๊ฑฐ์ ๋ชจ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
์ผ๋ฐ์ ์ธ ๋ด์ฅ ์ ๋ต
- ์ซ์ํ:
st.integers(min_value=0, max_value=1000)
: ์ ์๋ฅผ ์์ฑํ๋ฉฐ, ์ ํ์ ์ผ๋ก ํน์ ๋ฒ์ ๋ด์์ ์์ฑํฉ๋๋ค.st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False)
: ๋ถ๋ ์์์ ์ ์์ฑํ๋ฉฐ, ํน์ ๊ฐ์ ๋ํ ์ธ๋ฐํ ์ ์ด๊ฐ ๊ฐ๋ฅํฉ๋๋ค.st.fractions()
,st.decimals()
- ํ
์คํธ:
st.text(min_size=1, max_size=50)
: ํน์ ๊ธธ์ด์ ์ ๋์ฝ๋ ๋ฌธ์์ด์ ์์ฑํฉ๋๋ค.st.text(alphabet='abcdef0123456789')
: ํน์ ๋ฌธ์ ์งํฉ์ผ๋ก ๋ฌธ์์ด์ ์์ฑํฉ๋๋ค(์: 16์ง์ ์ฝ๋).st.characters()
: ๊ฐ๋ณ ๋ฌธ์๋ฅผ ์์ฑํฉ๋๋ค.
- ์ปฌ๋ ์
:
st.lists(st.integers(), min_size=1)
: ๊ฐ ์์๊ฐ ์ ์์ธ ๋ฆฌ์คํธ๋ฅผ ์์ฑํฉ๋๋ค. ์ธ์๋ก ๋ค๋ฅธ ์ ๋ต์ ์ ๋ฌํ๋ ๊ฒ์ ์ฃผ๋ชฉํ์ธ์! ์ด๊ฒ์ ์กฐํฉ(composition)์ด๋ผ๊ณ ํฉ๋๋ค.st.tuples(st.text(), st.booleans())
: ๊ณ ์ ๋ ๊ตฌ์กฐ์ ํํ์ ์์ฑํฉ๋๋ค.st.sets(st.integers())
st.dictionaries(keys=st.text(), values=st.integers())
: ์ง์ ๋ ํค์ ๊ฐ ์ ํ์ ๊ฐ์ง ๋์ ๋๋ฆฌ๋ฅผ ์์ฑํฉ๋๋ค.
- ์๊ฐ ๊ด๋ จ:
st.dates()
,st.times()
,st.datetimes()
,st.timedeltas()
. ์ด๋ค์ ์๊ฐ๋๋ฅผ ์ธ์ํ๋๋ก ๋ง๋ค ์ ์์ต๋๋ค.
- ๊ธฐํ:
st.booleans()
:True
๋๋False
๋ฅผ ์์ฑํฉ๋๋ค.st.just('constant_value')
: ํญ์ ๋์ผํ ๋จ์ผ ๊ฐ์ ์์ฑํฉ๋๋ค. ๋ณต์กํ ์ ๋ต์ ์กฐํฉํ ๋ ์ ์ฉํฉ๋๋ค.st.one_of(st.integers(), st.text())
: ์ ๊ณต๋ ์ ๋ต ์ค ํ๋์์ ๊ฐ์ ์์ฑํฉ๋๋ค.st.none()
: ์ค์งNone
๋ง ์์ฑํฉ๋๋ค.
์ ๋ต์ ๊ฒฐํฉ๊ณผ ๋ณํ
Hypothesis์ ์ง์ ํ ํ์ ๋ ๊ฐ๋จํ ์ ๋ต๋ค๋ก๋ถํฐ ๋ณต์กํ ์ ๋ต์ ๊ตฌ์ถํ๋ ๋ฅ๋ ฅ์์ ๋์ต๋๋ค.
.map()
์ฌ์ฉํ๊ธฐ
.map()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ํ ์ ๋ต์ ๊ฐ์ ๊ฐ์ ธ์ ๋ค๋ฅธ ๊ฒ์ผ๋ก ๋ณํํ ์ ์์ต๋๋ค. ์ด๊ฒ์ ์ฌ์ฉ์ ์ ์ ํด๋์ค์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ฐ ์๋ฒฝํฉ๋๋ค.
# ๊ฐ๋จํ ๋ฐ์ดํฐ ํด๋์ค from dataclasses import dataclass @dataclass class User: user_id: int username: str # User ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ์ ๋ต user_strategy = st.builds( User, user_id=st.integers(min_value=1), username=st.text(min_size=3, alphabet='abcdefghijklmnopqrstuvwxyz') ) @given(user=user_strategy) def test_user_creation(user): assert isinstance(user, User) assert user.user_id > 0 assert user.username.isalpha()
.filter()
์ assume()
์ฌ์ฉํ๊ธฐ
๋๋ก๋ ์์ฑ๋ ํน์ ๊ฐ์ ๊ฑฐ๋ถํด์ผ ํ ํ์๊ฐ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ํฉ์ด 0์ด ์๋ ์ ์ ๋ฆฌ์คํธ๊ฐ ํ์ํ ์ ์์ต๋๋ค. .filter()
๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค:
st.lists(st.integers()).filter(lambda x: sum(x) != 0)
ํ์ง๋ง .filter()
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋นํจ์จ์ ์ผ ์ ์์ต๋๋ค. ๋ง์ฝ ์กฐ๊ฑด์ด ์์ฃผ ๊ฑฐ์ง์ด๋ผ๋ฉด, Hypothesis๋ ์ ํจํ ์์ ๋ฅผ ์์ฑํ๋ ๋ฐ ์ค๋ ์๊ฐ์ ์๋นํ ์ ์์ต๋๋ค. ๋ ๋์ ์ ๊ทผ ๋ฐฉ์์ ์ข
์ข
ํ
์คํธ ํจ์ ๋ด์์ assume()
์ ์ฌ์ฉํ๋ ๊ฒ์
๋๋ค:
from hypothesis import assume @given(st.lists(st.integers())) def test_something_with_non_zero_sum_list(numbers): assume(sum(numbers) != 0) # ... ์ฌ๊ธฐ์ ํ ์คํธ ๋ก์ง์ ์์ฑํฉ๋๋ค ...
assume()
์ Hypothesis์๊ฒ "์ด ์กฐ๊ฑด์ด ์ถฉ์กฑ๋์ง ์์ผ๋ฉด, ์ด ์์ ๋ ๊ทธ๋ฅ ๋ฒ๋ฆฌ๊ณ ์๋ก์ด ๊ฒ์ ์๋ํด."๋ผ๊ณ ๋งํฉ๋๋ค. ์ด๊ฒ์ ํ
์คํธ ๋ฐ์ดํฐ๋ฅผ ์ ์ฝํ๋ ๋ ์ง์ ์ ์ด๊ณ ์ข
์ข
๋ ์ฑ๋ฅ์ด ์ข์ ๋ฐฉ๋ฒ์
๋๋ค.
st.composite()
์ฌ์ฉํ๊ธฐ
ํ๋์ ์์ฑ๋ ๊ฐ์ด ๋ค๋ฅธ ๊ฐ์ ์์กดํ๋ ์ ๋ง ๋ณต์กํ ๋ฐ์ดํฐ ์์ฑ์ ์ํด์๋ st.composite()
์ด ํ์ํ ๋๊ตฌ์
๋๋ค. ์ด๊ฒ์ ํน๋ณํ draw
ํจ์๋ฅผ ์ธ์๋ก ๋ฐ๋ ํจ์๋ฅผ ์์ฑํ ์ ์๊ฒ ํด์ฃผ๋ฉฐ, ์ด ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๋ค๋ฅธ ์ ๋ต๋ค๋ก๋ถํฐ ๋จ๊ณ๋ณ๋ก ๊ฐ์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
๊ณ ์ ์ ์ธ ์๋ ๋ฆฌ์คํธ์ ๊ทธ ๋ฆฌ์คํธ์ ๋ํ ์ ํจํ ์ธ๋ฑ์ค๋ฅผ ์์ฑํ๋ ๊ฒ์ ๋๋ค.
@st.composite def list_and_index(draw): # ๋จผ์ , ๋น์ด์์ง ์์ ๋ฆฌ์คํธ๋ฅผ ๋ฝ์ต๋๋ค my_list = draw(st.lists(st.integers(), min_size=1)) # ๊ทธ๋ฐ ๋ค์, ๊ทธ ๋ฆฌ์คํธ์ ๋ํด ์ ํจ์ฑ์ด ๋ณด์ฅ๋๋ ์ธ๋ฑ์ค๋ฅผ ๋ฝ์ต๋๋ค index = draw(st.integers(min_value=0, max_value=len(my_list) - 1)) return (my_list, index) @given(data=list_and_index()) def test_list_access(data): my_list, index = data # ์ด ์ ๊ทผ์ ์ ๋ต์ ๊ตฌ์ถํ ๋ฐฉ์ ๋๋ถ์ ์์ ํจ์ด ๋ณด์ฅ๋ฉ๋๋ค element = my_list[index] assert element is not None # ๊ฐ๋จํ ๋จ์ธ๋ฌธ
์ค์ ์์์ Hypothesis: ์ค์ ์๋๋ฆฌ์ค
์ด๋ฌํ ๊ฐ๋ ๋ค์ ์ํํธ์จ์ด ๊ฐ๋ฐ์๋ค์ด ๋งค์ผ ๋ง์ฃผํ๋ ๋ ํ์ค์ ์ธ ๋ฌธ์ ์ ์ ์ฉํด ๋ด ์๋ค.
์๋๋ฆฌ์ค 1: ๋ฐ์ดํฐ ์ง๋ ฌํ ํจ์ ํ ์คํธ
์ฌ์ฉ์ ํ๋กํ(๋์ ๋๋ฆฌ)์ URL์ ์์ ํ ๋ฌธ์์ด๋ก ์ง๋ ฌํํ๊ณ , ๊ทธ๊ฒ์ ๋ค์ ์ญ์ง๋ ฌํํ๋ ํจ์๊ฐ ์๋ค๊ณ ์์ํด ๋ด ์๋ค. ํต์ฌ ์์ฑ์ ๊ทธ ๊ณผ์ ์ด ์๋ฒฝํ๊ฒ ๊ฐ์ญ์ ์ด์ด์ผ ํ๋ค๋ ๊ฒ์ ๋๋ค.
import json import base64 def serialize_profile(data: dict) -> str: """๋์ ๋๋ฆฌ๋ฅผ URL-safe base64 ๋ฌธ์์ด๋ก ์ง๋ ฌํํฉ๋๋ค.""" json_string = json.dumps(data) return base64.urlsafe_b64encode(json_string.encode('utf-8')).decode('utf-8') def deserialize_profile(encoded_str: str) -> dict: """๋ฌธ์์ด์ ๋ค์ ๋์ ๋๋ฆฌ๋ก ์ญ์ง๋ ฌํํฉ๋๋ค.""" json_string = base64.urlsafe_b64decode(encoded_str.encode('utf-8')).decode('utf-8') return json.loads(json_string) # ์ด์ ํ ์คํธ ์ฐจ๋ก์ ๋๋ค # JSON ํธํ ๋์ ๋๋ฆฌ๋ฅผ ์์ฑํ๋ ์ ๋ต์ด ํ์ํฉ๋๋ค json_dictionaries = st.dictionaries( keys=st.text(), values=st.recursive(st.none() | st.booleans() | st.floats(allow_nan=False) | st.text(), lambda children: st.lists(children) | st.dictionaries(st.text(), children), max_leaves=10) ) @given(profile=json_dictionaries) def test_serialization_roundtrip(profile): """์์ฑ: ์ธ์ฝ๋ฉ๋ ํ๋กํ์ ์ญ์ง๋ ฌํํ๋ฉด ์๋ ํ๋กํ์ด ๋ฐํ๋์ด์ผ ํฉ๋๋ค.""" encoded = serialize_profile(profile) decoded = deserialize_profile(encoded) assert profile == decoded
์ด ๋จ ํ๋์ ํ ์คํธ๋ ์ฐ๋ฆฌ ํจ์๋ค์ ๋น ๋์ ๋๋ฆฌ, ์ค์ฒฉ๋ ๋ฆฌ์คํธ๊ฐ ์๋ ๋์ ๋๋ฆฌ, ์ ๋์ฝ๋ ๋ฌธ์๊ฐ ์๋ ๋์ ๋๋ฆฌ, ์ด์ํ ํค๋ฅผ ๊ฐ์ง ๋์ ๋๋ฆฌ ๋ฑ ์์ฒญ๋๊ฒ ๋ค์ํ ๋ฐ์ดํฐ๋ก ์ํํ ๊ฒ์ ๋๋ค. ์ด๊ฒ์ ๋ช ๊ฐ์ ์๋ ์์ ๋ฅผ ์์ฑํ๋ ๊ฒ๋ณด๋ค ํจ์ฌ ๋ ์ฒ ์ ํฉ๋๋ค.
์๋๋ฆฌ์ค 2: ์ ๋ ฌ ์๊ณ ๋ฆฌ์ฆ ํ ์คํธ
์ ๋ ฌ ์์ ๋ก ๋ค์ ๋์๊ฐ ๋ด ์๋ค. ์ด์ ์ ์ ์ํ๋ ์์ฑ๋ค์ ํ ์คํธํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
from collections import Counter def my_buggy_sort(numbers): # ๋ฏธ๋ฌํ ๋ฒ๊ทธ๋ฅผ ๋ฃ์ด๋ด ์๋ค: ์ค๋ณต๋ ๊ฐ์ ์ ๊ฑฐํฉ๋๋ค return sorted(list(set(numbers))) @given(st.lists(st.integers())) def test_sorting_properties(numbers): sorted_list = my_buggy_sort(numbers) # ์์ฑ 1: ์ถ๋ ฅ์ ์ ๋ ฌ๋์ด ์๋ค for i in range(len(sorted_list) - 1): assert sorted_list[i] <= sorted_list[i+1] # ์์ฑ 2: ์์๋ค์ด ๋์ผํ๋ค (์ด๊ฒ์ด ๋ฒ๊ทธ๋ฅผ ์ฐพ์ ๊ฒ์ ๋๋ค) assert Counter(numbers) == Counter(sorted_list) # ์์ฑ 3: ํจ์๋ ๋ฉฑ๋ฑ์ฑ์ ๊ฐ์ง๋ค assert my_buggy_sort(sorted_list) == sorted_list
์ด ํ
์คํธ๋ฅผ ์คํํ๋ฉด, Hypothesis๋ ์์ฑ 2์ ๋ํด numbers=[0, 0]
๊ณผ ๊ฐ์ ์คํจ ์์ ๋ฅผ ๋น ๋ฅด๊ฒ ์ฐพ์ ๊ฒ์
๋๋ค. ์ฐ๋ฆฌ ํจ์๋ [0]
์ ๋ฐํํ๊ณ , Counter([0, 0])
์ Counter([0])
๊ณผ ๊ฐ์ง ์์ต๋๋ค. ์ถ์ ๊ธฐ๋ฅ์ ์คํจ ์์ ๋ฅผ ๊ฐ๋ฅํ ํ ๊ฐ๋จํ๊ฒ ๋ง๋ค์ด ๋ฒ๊ทธ์ ์์ธ์ ์ฆ์ ๋ช
ํํ๊ฒ ๋ณด์ฌ์ค ๊ฒ์
๋๋ค.
์๋๋ฆฌ์ค 3: ์ํ ๊ธฐ๋ฐ ํ ์คํ
์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ ๋ด๋ถ ์ํ๊ฐ ๋ณํ๋ ๊ฐ์ฒด(๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ, ์ผํ ์นดํธ, ์บ์ ๋ฑ)์ ๊ฒฝ์ฐ ๋ฒ๊ทธ๋ฅผ ์ฐพ๋ ๊ฒ์ด ๋งค์ฐ ์ด๋ ค์ธ ์ ์์ต๋๋ค. ์ค๋ฅ๋ฅผ ์ ๋ฐํ๊ธฐ ์ํด ํน์ ์์์ ์์ ์ด ํ์ํ ์ ์์ต๋๋ค. Hypothesis๋ ๋ฐ๋ก ์ด ๋ชฉ์ ์ ์ํด `RuleBasedStateMachine`์ ์ ๊ณตํฉ๋๋ค.
์ธ๋ฉ๋ชจ๋ฆฌ ํค-๊ฐ ์ ์ฅ์์ ๋ํ ๊ฐ๋จํ API๋ฅผ ์์ํด ๋ณด์ธ์:
class SimpleKeyValueStore: def __init__(self): self._data = {} def set(self, key, value): self._data[key] = value def get(self, key): return self._data.get(key) def delete(self, key): if key in self._data: del self._data[key] def size(self): return len(self._data)
์ฐ๋ฆฌ๋ ๊ทธ๊ฒ์ ๋์์ ๋ชจ๋ธ๋งํ๊ณ ์ํ ๋จธ์ ์ผ๋ก ํ ์คํธํ ์ ์์ต๋๋ค:
from hypothesis.stateful import RuleBasedStateMachine, rule, Bundle class KeyValueStoreMachine(RuleBasedStateMachine): def __init__(self): super().__init__() self.model = {} self.sut = SimpleKeyValueStore() # Bundle()์ ๊ท์น ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค keys = Bundle('keys') @rule(target=keys, key=st.text(), value=st.integers()) def set_key(self, key, value): self.model[key] = value self.sut.set(key, value) return key @rule(key=keys) def delete_key(self, key): del self.model[key] self.sut.delete(key) @rule(key=st.text()) def get_key(self, key): model_val = self.model.get(key) sut_val = self.sut.get(key) assert model_val == sut_val @rule() def check_size(self): assert len(self.model) == self.sut.size() # ํ ์คํธ๋ฅผ ์คํํ๋ ค๋ฉด, ๋จธ์ ๊ณผ unittest.TestCase๋ฅผ ์์๋ฐ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค # pytest์์๋, ํ ์คํธ๋ฅผ ๋จธ์ ํด๋์ค์ ํ ๋นํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค TestKeyValueStore = KeyValueStoreMachine.TestCase
์ด์ Hypothesis๋ `set_key`, `delete_key`, `get_key`, `check_size` ์์ ์ ๋ฌด์์ ์ํ์ค๋ฅผ ์คํํ๋ฉฐ, ๋จ์ธ๋ฌธ ์ค ํ๋๊ฐ ์คํจํ๊ฒ ๋ง๋๋ ์ํ์ค๋ฅผ ๋์์์ด ์ฐพ์ผ๋ ค๊ณ ์๋ํ ๊ฒ์ ๋๋ค. ์ญ์ ๋ ํค๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๋์ํ๋์ง, ์ฌ๋ฌ ๋ฒ์ ์ค์ ๊ณผ ์ญ์ ํ์๋ ํฌ๊ธฐ๊ฐ ์ผ๊ด๋๋์ง, ๊ทธ๋ฆฌ๊ณ ์๋์ผ๋ก ํ ์คํธํ ์๊ฐ์ ํ์ง ๋ชปํ ์ ์๋ ๋ง์ ๋ค๋ฅธ ์๋๋ฆฌ์ค๋ค์ ํ์ธํ ๊ฒ์ ๋๋ค.
๋ชจ๋ฒ ์ฌ๋ก ๋ฐ ๊ณ ๊ธ ํ
- ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค: Hypothesis๋ ๋๋ํฉ๋๋ค. ๋ฒ๊ทธ๋ฅผ ์ฐพ์ผ๋ฉด ์คํจํ ์์ ๋ฅผ ๋ก์ปฌ ๋๋ ํ ๋ฆฌ(
.hypothesis/
)์ ์ ์ฅํฉ๋๋ค. ๋ค์์ ํ ์คํธ๋ฅผ ์คํํ ๋, ๊ทธ ์คํจํ ์์ ๋ฅผ ๋จผ์ ์ฌ์ํ์ฌ ๋ฒ๊ทธ๊ฐ ์ฌ์ ํ ์กด์ฌํ๋ค๋ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ค๋๋ค. ๋ฒ๊ทธ๋ฅผ ์์ ํ๋ฉด, ๊ทธ ์์ ๋ ๋ ์ด์ ์ฌ์๋์ง ์์ต๋๋ค. @settings
๋ก ํ ์คํธ ์คํ ์ ์ดํ๊ธฐ:@settings
๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ ์คํ์ ๋ง์ ์ธก๋ฉด์ ์ ์ดํ ์ ์์ต๋๋ค. ์์ ์ ์๋ฅผ ๋๋ฆฌ๊ฑฐ๋, ๋จ์ผ ์์ ๊ฐ ์คํ๋ ์ ์๋ ์๊ฐ ์ ํ(๋ฌดํ ๋ฃจํ๋ฅผ ์ก๊ธฐ ์ํด)์ ์ค์ ํ๊ณ , ํน์ ์ํ ๊ฒ์ฌ๋ฅผ ๋ ์ ์์ต๋๋ค.@settings(max_examples=500, deadline=1000) # 500๊ฐ์ ์์ ์คํ, 1์ด ์๊ฐ ์ ํ @given(...) ...
- ์คํจ ์ฌํํ๊ธฐ: ๋ชจ๋ Hypothesis ์คํ์ ์๋ ๊ฐ(์:
@reproduce_failure('version', 'seed')
)์ ์ถ๋ ฅํฉ๋๋ค. ๋ง์ฝ CI ์๋ฒ๊ฐ ๋ก์ปฌ์์ ์ฌํํ ์ ์๋ ๋ฒ๊ทธ๋ฅผ ๋ฐ๊ฒฌํ๋ฉด, ์ ๊ณต๋ ์๋์ ํจ๊ป ์ด ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ Hypothesis๊ฐ ์ ํํ ๋์ผํ ์์ ์ํ์ค๋ฅผ ์คํํ๋๋ก ๊ฐ์ ํ ์ ์์ต๋๋ค. - CI/CD์ ํตํฉํ๊ธฐ: Hypothesis๋ ๋ชจ๋ ์ง์์ ์ธ ํตํฉ ํ์ดํ๋ผ์ธ์ ์๋ฒฝํ๊ฒ ์ ํฉํฉ๋๋ค. ํ๋ก๋์ ์ ๋๋ฌํ๊ธฐ ์ ์ ๋ชจํธํ ๋ฒ๊ทธ๋ฅผ ์ฐพ๋ ๋ฅ๋ ฅ์ ๋งค์ฐ ๊ท์คํ ์์ ๋ง์ด ๋ฉ๋๋ค.
์ฌ๊ณ ์ ์ ํ: ์์ฑ์ผ๋ก ์๊ฐํ๊ธฐ
Hypothesis๋ฅผ ์ฑํํ๋ ๊ฒ์ ์๋ก์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ฐฐ์ฐ๋ ๊ฒ ์ด์์ ๋๋ค. ๊ทธ๊ฒ์ ์ฝ๋์ ์ ํ์ฑ์ ๋ํด ์๋ก์ด ๋ฐฉ์์ผ๋ก ์๊ฐํ๋ ๊ฒ์ ๋ฐ์๋ค์ด๋ ๊ฒ์ ๋๋ค. "์ด๋ค ์ ๋ ฅ์ ํ ์คํธํด์ผ ํ ๊น?"๋ผ๊ณ ๋ฌป๋ ๋์ , "์ด ์ฝ๋์ ๋ํ ๋ณดํธ์ ์ธ ์ง๋ฆฌ๋ ๋ฌด์์ธ๊ฐ?"๋ผ๊ณ ๋ฌป๊ธฐ ์์ํฉ๋๋ค.
์์ฑ์ ์๋ณํ๋ ค๊ณ ํ ๋ ๋์์ด ๋ ๋ช ๊ฐ์ง ์ง๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์ญ์ฐ์ฐ์ด ์๋๊ฐ? (์: ์ง๋ ฌํ/์ญ์ง๋ ฌํ, ์ํธํ/๋ณตํธํ, ์์ถ/์์ถ ํด์ ). ์์ฑ์ ์ฐ์ฐ๊ณผ ๊ทธ ์ญ์ฐ์ฐ์ ์ํํ๋ฉด ์๋ ์ ๋ ฅ์ ์ป์ด์ผ ํ๋ค๋ ๊ฒ์ ๋๋ค.
- ์ฐ์ฐ์ด ๋ฉฑ๋ฑ์ฑ์ ๊ฐ์ง๋๊ฐ? (์:
abs(abs(x)) == abs(x)
). ํจ์๋ฅผ ํ ๋ฒ ์ด์ ์ ์ฉํด๋ ํ ๋ฒ ์ ์ฉํ ๊ฒ๊ณผ ๋์ผํ ๊ฒฐ๊ณผ๊ฐ ๋์์ผ ํฉ๋๋ค. - ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ๊ณ์ฐํ๋ ๋ค๋ฅธ, ๋ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ด ์๋๊ฐ? ๋ณต์กํ๊ณ ์ต์ ํ๋ ํจ์๊ฐ ๊ฐ๋จํ๊ณ ๋ช
๋ฐฑํ๊ฒ ์ฌ๋ฐ๋ฅธ ๋ฒ์ ๊ณผ ๋์ผํ ์ถ๋ ฅ์ ์์ฑํ๋์ง ํ
์คํธํ ์ ์์ต๋๋ค(์: ์ฌ๋ฌ๋ถ์ ๋ฉ์ง ์ ๋ ฌ์ ํ์ด์ฌ์ ๋ด์ฅ
sorted()
์ ๋น๊ต ํ ์คํธ). - ์ถ๋ ฅ์ ๋ํด ํญ์ ์ฐธ์ด์ด์ผ ํ๋ ๊ฒ์ ๋ฌด์์ธ๊ฐ? (์: `find_prime_factors` ํจ์์ ์ถ๋ ฅ์ ์์๋ง ํฌํจํด์ผ ํ๋ฉฐ, ๊ทธ๋ค์ ๊ณฑ์ ์ ๋ ฅ๊ณผ ๊ฐ์์ผ ํฉ๋๋ค).
- ์ํ๋ ์ด๋ป๊ฒ ๋ณํ๋๊ฐ? (์ํ ๊ธฐ๋ฐ ํ ์คํ ์ ๊ฒฝ์ฐ) ์ ํจํ ์์ ํ์ ์ด๋ค ๋ถ๋ณ ์์ฑ์ด ์ ์ง๋์ด์ผ ํ๋๊ฐ? (์: ์ผํ ์นดํธ์ ํญ๋ชฉ ์๋ ์ ๋ ์์๊ฐ ๋ ์ ์๋ค).
๊ฒฐ๋ก : ์๋ก์ด ์ฐจ์์ ์์ ๊ฐ
Hypothesis๋ฅผ ์ฌ์ฉํ ์์ฑ ๊ธฐ๋ฐ ํ ์คํ ์ ์์ ๊ธฐ๋ฐ ํ ์คํ ์ ๋์ฒดํ์ง ์์ต๋๋ค. ์ค์ํ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ์ ์ดํด๋ ์๊ตฌ์ฌํญ(์: "X ๊ตญ๊ฐ์ ์ฌ์ฉ์๋ Y ๊ฐ๊ฒฉ์ ๋ณด์์ผ ํ๋ค")์ ๋ํด์๋ ์ฌ์ ํ ํน์ ํ๊ณ ์๋์ผ๋ก ์์ฑ๋ ํ ์คํธ๊ฐ ํ์ํฉ๋๋ค.
Hypothesis๊ฐ ์ ๊ณตํ๋ ๊ฒ์ ์ฝ๋์ ๋์์ ํ์ํ๊ณ ์์ธกํ์ง ๋ชปํ ์ฃ์ง ์ผ์ด์ค๋ก๋ถํฐ ๋ณดํธํ๋ ๊ฐ๋ ฅํ๊ณ ์๋ํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ๊ทธ๊ฒ์ ์ง์น์ง ์๋ ํํธ๋ ์ญํ ์ ํ๋ฉฐ, ์ธ๊ฐ์ด ํ์ค์ ์ผ๋ก ์์ฑํ ์ ์๋ ๊ฒ๋ณด๋ค ๋ ๋ค์ํ๊ณ ๊ต๋ฌํ ์์ฒ ๊ฐ์ ํ ์คํธ๋ฅผ ์์ฑํฉ๋๋ค. ์ฝ๋์ ๊ทผ๋ณธ์ ์ธ ์์ฑ์ ์ ์ํจ์ผ๋ก์จ, ์ฌ๋ฌ๋ถ์ Hypothesis๊ฐ ํ ์คํธํ ์ ์๋ ๊ฒฌ๊ณ ํ ๋ช ์ธ๋ฅผ ๋ง๋ค๊ณ , ์ํํธ์จ์ด์ ๋ํ ์๋ก์ด ์ฐจ์์ ์์ ๊ฐ์ ์ป๊ฒ ๋ฉ๋๋ค.
๋ค์์ ํจ์๋ฅผ ์์ฑํ ๋, ์ ์ ์๊ฐ์ ๋ด์ด ์์ ๋ฅผ ๋์ด์ ์๊ฐํด ๋ณด์ธ์. ์ค์ค๋ก์๊ฒ ๋ฌผ์ด๋ณด์ธ์, "๊ท์น์ ๋ฌด์์ธ๊ฐ? ํญ์ ์ฐธ์ด์ด์ผ ํ๋ ๊ฒ์ ๋ฌด์์ธ๊ฐ?" ๊ทธ๋ฐ ๋ค์, Hypothesis๊ฐ ๊ทธ๊ฒ๋ค์ ๊นจ๋จ๋ฆฌ๋ ค๋ ํ๋ ์์ ์ ํ๋๋ก ํ์ธ์. ๊ทธ๊ฒ์ด ์ฐพ์๋ด๋ ๊ฒ์ ๋๋ผ๊ฒ ๋ ๊ฒ์ด๊ณ , ์ฌ๋ฌ๋ถ์ ์ฝ๋๋ ๊ทธ๊ฒ์ผ๋ก ์ธํด ๋ ์ข์์ง ๊ฒ์ ๋๋ค.