ํ”ผ๋“œ๋กœ ๋Œ์•„๊ฐ€๊ธฐ
๐Ÿ›‘ Stop Testing Your Code and Ignoring Your Database (Catching N+1 in Pytest)
Dev.toDev.to
Database

pytest-capquery๋กœ N+1 ์ฟผ๋ฆฌ ํšŒ๊ท€๋ฅผ CI ๋‹จ๊ณ„์—์„œ ์ฆ‰์‹œ ๊ฐ์ง€ํ•จ

๐Ÿ›‘ Stop Testing Your Code and Ignoring Your Database (Catching N+1 in Pytest)

Felipe Cardoso Martins2026๋…„ 4์›” 1์ผ2๋ถ„intermediate

Context

Python ๊ฐœ๋ฐœ์ž๋“ค ๋Œ€๋ถ€๋ถ„ CI ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ตœ์ข… ์ƒํƒœ๋งŒ ๊ฒ€์ฆํ•จ. SQLAlchemy ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ ˆ์ด์–ด๋ฅผ ๋ธ”๋ž™๋ฐ•์Šค๋กœ ์ทจ๊ธ‰ํ•˜์—ฌ N+1 ์ฟผ๋ฆฌ ๋ฌธ์ œ๊ฐ€ ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ์ง์ „๊นŒ์ง€ ๋ฐœ๊ฒฌ๋˜์ง€ ์•Š์Œ. lazy-load์œผ๋กœ ์ธํ•œ ๋น„ํšจ์œจ์  ์ฟผ๋ฆฌ๊ฐ€ ๋ฉ”์ธ ๋ธŒ๋žœ์น˜์— ์นจํˆฌํ•˜๋ฉด ํด๋ผ์šฐ๋“œ ๋น„์šฉ์ด ์ฆ๊ฐ€ํ•˜๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ์ €ํ•˜๋จ.

Technical Solution

  • pytest-capquery: Pytest suite์—์„œ SQL ์ฟผ๋ฆฌ๋ฅผ 1๋“ฑ ์‹œ๋ฏผ์œผ๋กœ ์ทจ๊ธ‰ํ•˜๋Š” ์˜คํ”ˆ์†Œ์Šค ๋„๊ตฌ์ž„
  • SQLAlchemy engine ๊ฐ€๋กœ์ฑ„๊ธฐ: ๋“œ๋ผ์ด๋ฒ„ ๋ ˆ๋ฒจ์—์„œ SQL์„ ์ธํ„ฐ์…‰ํŠธํ•จ
  • Chronological timeline enforcement: ์‹คํ–‰ ํ”์ ์˜ ์—„๊ฒฉํ•œ ์‹œ๊ฐ„์ˆœ ๊ฐ์‹œ๋ฅผ ๋ณด์žฅํ•จ
  • assert_executed_queries(): ๊ฒฐ์ •๋ก ์  I/O๋ฅผ ์—„๊ฒฉํ•˜๊ฒŒ ๊ฒ€์ฆํ•จ
  • N+1 regression detection: ๋น„ํšจ์œจ ์ฟผ๋ฆฌ ๊ฐ์ง€ ์‹œ ๋นŒ๋“œ๋ฅผ ์ฆ‰์‹œ ์‹คํŒจ์‹œํ‚ด

Impact

ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ N+1 ํŒจํ„ด์ด ๋ฐœ๊ฒฌ๋˜๋ฉด ๋นŒ๋“œ๊ฐ€ ์‹คํŒจํ•˜๋ฏ€๋กœ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ์‚ฌ์ „ ์ฐจ๋‹จํ•จ.

Key Takeaway

ORM ์„ฑ๋Šฅ ๋ณ‘๋ชฉ์˜ ์›์ธ์„ ORM์ด ์•„๋‹ˆ๋ผ ํ…Œ์ŠคํŠธ ์ „๋žต์—์„œ ์ฐพ์•„์•ผ ํ•จ. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ๋ฅผ ํ…Œ์ŠคํŠธ ์‹œ ํฌํ•จํ•˜๋ฉด ์†Œํ”„ํŠธ์›จ์–ด ํšŒ๋ณต ํƒ„๋ ฅ์„ฑ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋จ.


SQLAlchemy ๊ธฐ๋ฐ˜ Python ํ”„๋กœ์ ํŠธ์—์„œ Pytest ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์‹œ pytest-capquery๋ฅผ ํ™œ์šฉํ•  ๊ฒƒ. db_session๊ณผ capquery fixture๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ์‹คํ–‰ ํ›„ assert_executed_queries()๋กœ ์ •ํ™•ํ•œ SQL ํŒจํ„ด์„ ๊ฒ€์ฆํ•˜๋ฉด N+1 ํšŒ๊ท€๊ฐ€ ๋นŒ๋“œ ๋‹จ๊ณ„์—์„œ ์ž๋™์œผ๋กœ ํƒ์ง€๋จ.

์›๋ฌธ ์ฝ๊ธฐ