๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
pnpm fixture ๋ช…๋ น์–ด๋Š” ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•  ์ผ๊ด€๋œ ๋ฐ์ดํ„ฐ ์„ธํŠธ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์˜ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์œผ๋กœ ๋ณต์‚ฌํ•˜์—ฌ, ์•ˆ์ •์ ์ด๊ณ  ๋ฐ˜๋ณต ๊ฐ€๋Šฅํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ๊ฐœ๋…

Fixture๋Š” ํ…Œ์ŠคํŠธ์šฉ ๊ณ ์ •๋œ ๋ฐ์ดํ„ฐ ์„ธํŠธ์ž…๋‹ˆ๋‹ค:
  • ์ผ๊ด€์„ฑ: ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ™์€ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋กœ ์‹œ์ž‘
  • ๊ฒฉ๋ฆฌ: ํ…Œ์ŠคํŠธ DB์™€ ๊ฐœ๋ฐœ DB ๋ถ„๋ฆฌ
  • ์žฌํ˜„์„ฑ: ๊ฐ™์€ ์กฐ๊ฑด์—์„œ ๋ฐ˜๋ณต ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
  • ๊ด€๊ณ„ ๋ณด์กด: ์™ธ๋ž˜ํ‚ค ๊ด€๊ณ„๊ฐ€ ์œ ์ง€๋จ

๋ช…๋ น์–ด

init - ํ…Œ์ŠคํŠธ DB ์ดˆ๊ธฐํ™”

๊ฐœ๋ฐœ DB์˜ ์Šคํ‚ค๋งˆ๋ฅผ ํ…Œ์ŠคํŠธ DB๋กœ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค.
pnpm fixture init
์‹คํ–‰ ๊ณผ์ •:
DUMP...
  โœ“ Schema dumped from development_master

SYNC to (REMOTE) Fixture DB...
  โœ“ Database myapp_fixture created
  โœ“ Schema applied

SYNC to (LOCAL) Testing DB...
  โœ“ Database myapp_test created
  โœ“ Schema applied

Fixture initialization completed!
์ƒ์„ฑ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค:
  • Fixture DB (์›๊ฒฉ): ๊ณต์œ  fixture ์ €์žฅ์†Œ
  • Test DB (๋กœ์ปฌ): ๋กœ์ปฌ ํ…Œ์ŠคํŠธ์šฉ DB
init์€ ์Šคํ‚ค๋งˆ๋งŒ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋Š” ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

import - ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ

๊ฐœ๋ฐœ DB์—์„œ ํŠน์ • ๋ ˆ์ฝ”๋“œ๋ฅผ fixture๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
pnpm fixture import
๋Œ€ํ™”ํ˜• ํ”„๋กฌํ”„ํŠธ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค:
? Please select entity: (Use arrow keys)
โฏ User
  Post
  Comment

? Enter record IDs (comma-separated): 1,2,3

Importing fixtures...
  โœ“ Imported User #1
  โœ“ Imported User #2
  โœ“ Imported User #3
  โœ“ Imported related records (5 dependencies)

Fixture import completed!
์ž๋™์œผ๋กœ ํฌํ•จ๋˜๋Š” ๊ด€๋ จ ๋ฐ์ดํ„ฐ:
  • ์™ธ๋ž˜ํ‚ค๋กœ ์ฐธ์กฐ๋˜๋Š” ๋ ˆ์ฝ”๋“œ
  • ๊ด€๊ณ„ ํ…Œ์ด๋ธ”์˜ ๋ ˆ์ฝ”๋“œ
  • ์—ญ์ฐธ์กฐ ๋ ˆ์ฝ”๋“œ (์„ ํƒ ๊ฐ€๋Šฅ)

sync - ๋™๊ธฐํ™”

์ €์žฅ๋œ fixture๋ฅผ ํ…Œ์ŠคํŠธ DB์— ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
pnpm fixture sync
์‹คํ–‰ ๊ณผ์ •:
Syncing fixtures...

Clearing test database...
  โœ“ Cleared all tables

Applying fixtures...
  โœ“ Applied 3 users
  โœ“ Applied 7 posts
  โœ“ Applied 12 comments
  โœ“ Applied 5 categories

Fixture sync completed!
ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ „์— sync๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ํ•ญ์ƒ ๊นจ๋—ํ•œ ์ƒํƒœ์—์„œ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

gen - ์ž๋™ Fixture ์ƒ์„ฑ

Entity์˜ cone ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ Fixture๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๊ฑฐ๋‚˜ ๊ฐ€์ƒ์˜ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
pnpm fixture gen
๋Œ€ํ™”ํ˜• ๋ชจ๋“œ:
? Fixture๋ฅผ ์ƒ์„ฑํ•  Entity๋ฅผ ์„ ํƒํ•˜์„ธ์š”: (Use arrow keys)
โฏ โ—ฏ User
  โ—ฏ Post
  โ—ฏ Comment

? ๊ฐ Entity๋ณ„ ์ƒ์„ฑ ๊ฐœ์ˆ˜: 5

? ์ €์žฅ ๋ฐฉ์‹: (Use arrow keys)
โฏ Fixture DB์— ์ €์žฅ
  ํŒŒ์ผ๋กœ ์ €์žฅ (์ž๋™ ํŒŒ์ผ๋ช…)
  ํŒŒ์ผ๋กœ ์ €์žฅ (ํŒŒ์ผ๋ช… ์ง€์ •)
  ์ €์žฅ ์•ˆ ํ•จ (์ถœ๋ ฅ๋งŒ)

? LLM์œผ๋กœ ๋” ํ˜„์‹ค์ ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ• ๊นŒ์š”? (fixtureHint ๊ธฐ๋ฐ˜, ANTHROPIC_API_KEY ํ•„์š”) No
CLI ์˜ต์…˜:
์˜ต์…˜์„ค๋ช…์˜ˆ์‹œ
--all๋ชจ๋“  Entity์— ๋Œ€ํ•ด ์ƒ์„ฑpnpm fixture gen --all
--includeํŠน์ • Entity๋งŒ ํฌํ•จpnpm fixture gen --include User,Post
--excludeํŠน์ • Entity ์ œ์™ธ (โ€”all๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ)pnpm fixture gen --all --exclude Comment
--count์ƒ์„ฑํ•  ๋ ˆ์ฝ”๋“œ ์ˆ˜pnpm fixture gen --include User --count 10
--save-to์ €์žฅ ๋ฐฉ์‹ ์ง€์ •pnpm fixture gen --save-to db
--use-llmLLM์œผ๋กœ ํ˜„์‹ค์ ์ธ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ (fixtureHint ๊ธฐ๋ฐ˜, ANTHROPIC_API_KEY ํ•„์š”)pnpm fixture gen --use-llm
--no-cacheLLM ์บ์‹œ ๋น„ํ™œ์„ฑํ™” (--use-llm๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ)pnpm fixture gen --use-llm --no-cache
์ €์žฅ ๋ฐฉ์‹:
  • db: Fixture DB์— ์ง์ ‘ ์ €์žฅ (๊ธฐ๋ณธ๊ฐ’)
  • file: test/fixtures/{entity_table}.json ํŒŒ์ผ๋กœ ์ €์žฅ
  • file:{filename}: ์ง€์ •๋œ ํŒŒ์ผ๋ช…์œผ๋กœ ์ €์žฅ
  • none: ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ์ฝ˜์†”์— ์ถœ๋ ฅ๋งŒ
# ์˜ˆ์‹œ: User, Post ๊ฐ 10๊ฐœ์”ฉ ์ƒ์„ฑํ•˜์—ฌ DB์— ์ €์žฅ
pnpm fixture gen --include User,Post --count 10 --save-to db
๊ฐ’ ์ƒ์„ฑ ์šฐ์„ ์ˆœ์œ„: ๊ฐ ํ•„๋“œ์˜ ๊ฐ’์„ ์ƒ์„ฑํ•  ๋•Œ ์•„๋ž˜ ์ˆœ์„œ๋Œ€๋กœ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋จผ์ € ๋งค์นญ๋˜๋Š” ์ „๋žต์ด ์‚ฌ์šฉ๋˜๊ณ , ์ดํ›„ ๋‹จ๊ณ„๋Š” ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.
  1. Relation โ€” ์™ธ๋ž˜ํ‚ค ๊ด€๊ณ„๊ฐ€ ์žˆ๋Š” ํ•„๋“œ๋Š” ์ž๋™์œผ๋กœ ๊ด€๋ จ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑ/์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.
  2. cone.note + LLM โ€” --use-llm ์˜ต์…˜์ด ์ผœ์ ธ ์žˆ๊ณ , ํ•ด๋‹น ํ•„๋“œ์˜ cone์— note๊ฐ€ ์ง€์ •๋œ ๊ฒฝ์šฐ LLM์ด note ๋‚ด์šฉ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ˜„์‹ค์ ์ธ ๊ฐ’์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. LLM ํ˜ธ์ถœ์ด ์‹คํŒจํ•˜๋ฉด ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ํด๋ฐฑํ•ฉ๋‹ˆ๋‹ค.
  3. cone.fixtureGenerator โ€” cone์— fixtureGenerator(Faker ํ‘œํ˜„์‹ ๋“ฑ)๊ฐ€ ์ง€์ •๋œ ๊ฒฝ์šฐ ์ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  4. cone.fixtureDefault โ€” cone์— fixtureDefault ๊ณ ์ •๊ฐ’์ด ์ง€์ •๋œ ๊ฒฝ์šฐ ์ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  5. ํƒ€์ž…๋ณ„ ๊ธฐ๋ณธ ์ƒ์„ฑ โ€” ์œ„ ์–ด๋–ค ์ „๋žต๋„ ํ•ด๋‹นํ•˜์ง€ ์•Š์œผ๋ฉด ํ•„๋“œ ํƒ€์ž…(string, number, date ๋“ฑ)์— ๋”ฐ๋ผ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
--use-llm ์‚ฌ์šฉ ์‹œ cone.note๊ฐ€ ์žˆ๋Š” ํ•„๋“œ๋Š” fixtureGenerator๋ณด๋‹ค LLM์ด ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋„๋ฉ”์ธ์— ๋งž๋Š” ๋” ํ˜„์‹ค์ ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
LLM์ด ์ƒ์„ฑํ•œ ๋ฌธ์ž์—ด์ด ํ•ด๋‹น ํ•„๋“œ์˜ length ์ œ์•ฝ์„ ์ดˆ๊ณผํ•˜๋ฉด ์ž๋™์œผ๋กœ ์ž˜๋ผ๋ƒ…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด varchar(100) ํ•„๋“œ์— ๋Œ€ํ•ด LLM์ด 120์ž๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ์•ž 100์ž๋งŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
Companion Fixture ์ž๋™ ์ƒ์„ฑ: Entity์˜ cone์— fixtureCompanions๊ฐ€ ์„ ์–ธ๋˜์–ด ์žˆ์œผ๋ฉด, ๋ถ€๋ชจ fixture ์ƒ์„ฑ ์‹œ companion Entity์˜ fixture๋„ ํ•จ๊ป˜ ์ž๋™ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด better-auth ๊ธฐ๋ฐ˜ ํ”„๋กœ์ ํŠธ์—์„œ User fixture๋ฅผ ์ƒ์„ฑํ•˜๋ฉด credentials Account๋„ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. fixtureCompanions๋Š” Entity์˜ id prop cone์— ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค:
{
  "name": "id",
  "type": "string",
  "cone": {
    "fixtureStrategy": "sequence",
    "fixtureCompanions": [
      {
        "entity": "Account",
        "overrides": {
          "provider_id": "credential",
          "account_id": "{{email}}",
          "password": "{{email}}"
        }
      }
    ]
  }
}
์†์„ฑ์„ค๋ช…
entityํ•จ๊ป˜ ์ƒ์„ฑํ•  companion Entity ์ด๋ฆ„
overridescompanion fixture์— ์ ์šฉํ•  ๊ณ ์ • ์˜ค๋ฒ„๋ผ์ด๋“œ ๊ฐ’. {{fieldName}} ํ˜•์‹์œผ๋กœ ๋ถ€๋ชจ fixture์˜ ํ•„๋“œ ๊ฐ’์„ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
count๋ถ€๋ชจ 1๊ฐœ๋‹น ์ƒ์„ฑํ•  companion ๊ฐœ์ˆ˜ (๊ธฐ๋ณธ๊ฐ’: 1)
better-auth๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ์กด ํ”„๋กœ์ ํŠธ์— fixtureCompanions๋ฅผ ์†Œ๊ธ‰ ์ ์šฉํ•˜๋ ค๋ฉด:
pnpm sonamu auth add-companions
์ด ๋ช…๋ น์–ด๋Š” better-auth Entity(User ๋“ฑ)์˜ entity.json์— fixtureCompanions ์„ค์ •์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฏธ ์„ค์ •์ด ์žˆ๋Š” Entity๋Š” ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. parentId ์—”ํ‹ฐํ‹ฐ Fixture ์ƒ์„ฑ: parentId๊ฐ€ ์ง€์ •๋œ ์—”ํ‹ฐํ‹ฐ(์ƒ์† ๊ด€๊ณ„์˜ ์„œ๋ธŒํƒ€์ž…)๋Š” gen ์‹œ ํŠน๋ณ„ํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ๋ถ€๋ชจ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋Œ€์‹ , DB์—์„œ ์•„์ง ์„œ๋ธŒํƒ€์ž… ํ–‰์ด ์—†๋Š” ๊ธฐ์กด ๋ถ€๋ชจ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฐพ์•„ ํ•ด๋‹น ID๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด Paper๊ฐ€ Achievement์˜ ์„œ๋ธŒํƒ€์ž…(parentId: "Achievement")์ธ ๊ฒฝ์šฐ:
  1. achievements ํ…Œ์ด๋ธ”์—์„œ ์•„์ง papers ํ…Œ์ด๋ธ”์— ๋Œ€์‘ํ•˜๋Š” ํ–‰์ด ์—†๋Š” ID๋ฅผ ์กฐํšŒ
  2. ํ•ด๋‹น ID๋กœ Paper fixture๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„๋ฅผ ํ˜•์„ฑ
๋ถ€๋ชจ ์กฐํšŒ ์‹œ ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด id prop์˜ cone์— fixtureParentOverrides๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค:
{
  "name": "id",
  "type": "integer",
  "cone": {
    "fixtureParentOverrides": {
      "achievement_type": "PAPER"
    }
  }
}
์†์„ฑ์„ค๋ช…
fixtureParentOverrides๋ถ€๋ชจ ํ…Œ์ด๋ธ”์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ ˆ์ฝ”๋“œ๋ฅผ ์กฐํšŒํ•  ๋•Œ ์ ์šฉํ•  WHERE ์กฐ๊ฑด. ์˜ˆ๋ฅผ ๋“ค์–ด ์„œ๋ธŒํƒ€์ž…๋ณ„๋กœ ๊ตฌ๋ถ„ ์ปฌ๋Ÿผ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ๊ฐ’์œผ๋กœ ํ•„ํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค
๋ถ€๋ชจ ํ…Œ์ด๋ธ”์— ์„œ๋ธŒํƒ€์ž…์ด ์—†๋Š” ๋ ˆ์ฝ”๋“œ๊ฐ€ ๋ถ€์กฑํ•˜๋ฉด ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์˜ fixture ์ƒ์„ฑ์„ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ์‚ฌ์ „์— ๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ์˜ fixture๋ฅผ ์ถฉ๋ถ„ํžˆ ์ƒ์„ฑํ•ด ๋‘์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

fetch - ์šด์˜ DB์—์„œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ

์‹ค์ œ ์šด์˜ DB(๋˜๋Š” ๊ฐœ๋ฐœ DB)์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ Fixture DB์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ํ˜„์‹ค์ ์ธ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
pnpm fixture fetch
๋Œ€ํ™”ํ˜• ๋ชจ๋“œ:
? Importํ•  Entity๋ฅผ ์„ ํƒํ•˜์„ธ์š”: (Use arrow keys)
โฏ โ—ฏ User
  โ—ฏ Post
  โ—ฏ Comment

User, Post import ์ค‘...
  โœ“ User: 10๊ฐœ import ์™„๋ฃŒ
  โœ“ Post: 10๊ฐœ import ์™„๋ฃŒ
CLI ์˜ต์…˜:
์˜ต์…˜์„ค๋ช…์˜ˆ์‹œ
--all๋ชจ๋“  Entity์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐpnpm fixture fetch --all
--includeํŠน์ • Entity๋งŒ ํฌํ•จpnpm fixture fetch --include User,Post
--excludeํŠน์ • Entity ์ œ์™ธpnpm fixture fetch --all --exclude Log
--strategy๋ฐ์ดํ„ฐ ์„ ํƒ ์ „๋žตpnpm fixture fetch --strategy recent
--limit๊ฐ€์ ธ์˜ฌ ๋ ˆ์ฝ”๋“œ ์ˆ˜pnpm fixture fetch --limit 20
๋ฐ์ดํ„ฐ ์„ ํƒ ์ „๋žต:
  • recent (๊ธฐ๋ณธ๊ฐ’): ์ตœ๊ทผ ๋ฐ์ดํ„ฐ๋ถ€ํ„ฐ ๊ฐ€์ ธ์˜ด
  • sample: ๋ฌด์ž‘์œ„ ์ƒ˜ํ”Œ๋ง
# ์˜ˆ์‹œ: User์˜ ์ตœ๊ทผ 20๊ฐœ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
pnpm fixture fetch --include User --strategy recent --limit 20
ํŠน์ง•:
  • ๊ด€๊ณ„๋œ ๋ฐ์ดํ„ฐ๋„ ํ•จ๊ป˜ ์ž๋™์œผ๋กœ ๊ฐ€์ ธ์˜ด (๊นŠ์ด 2 ๋ ˆ๋ฒจ๊นŒ์ง€)
  • ์™ธ๋ž˜ํ‚ค ์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ ์œ ์ง€

explore - DB ๋ฐ์ดํ„ฐ ์กฐํšŒ

DB์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒ๋งŒ ํ•ฉ๋‹ˆ๋‹ค. ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ๋น ๋ฅด๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
pnpm fixture explore
๋Œ€ํ™”ํ˜• ๋ชจ๋“œ:
? ํƒ์ƒ‰ํ•  Entity: User

User 10๊ฐœ ์กฐํšŒ ์™„๋ฃŒ:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ (index) โ”‚ id โ”‚ email              โ”‚ name     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚    0    โ”‚  1 โ”‚ 'user1@example.com'โ”‚ 'ํ™๊ธธ๋™' โ”‚
โ”‚    1    โ”‚  2 โ”‚ 'user2@example.com'โ”‚ '๊น€์ฒ ์ˆ˜' โ”‚
โ”‚   ...   โ”‚... โ”‚ ...                โ”‚ ...      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
CLI ์˜ต์…˜:
์˜ต์…˜์„ค๋ช…์˜ˆ์‹œ
--includeํƒ์ƒ‰ํ•  Entitypnpm fixture explore --include User
--strategy๋ฐ์ดํ„ฐ ์„ ํƒ ์ „๋žตpnpm fixture explore --strategy sample
--limit์กฐํšŒํ•  ๋ ˆ์ฝ”๋“œ ์ˆ˜pnpm fixture explore --limit 5
๋ฐ์ดํ„ฐ ์„ ํƒ ์ „๋žต:
  • sample (๊ธฐ๋ณธ๊ฐ’): ๋ฌด์ž‘์œ„ ์ƒ˜ํ”Œ
  • recent: ์ตœ๊ทผ ๋ฐ์ดํ„ฐ
  • random: ์™„์ „ ๋ฌด์ž‘์œ„
  • ids: ํŠน์ • ID ์ง€์ •
  • query: ์ปค์Šคํ…€ ์ฟผ๋ฆฌ
# ์˜ˆ์‹œ: User ํ…Œ์ด๋ธ”์—์„œ ์ตœ๊ทผ 5๊ฐœ ์กฐํšŒ
pnpm fixture explore --include User --strategy recent --limit 5

์‚ฌ์šฉ ์›Œํฌํ”Œ๋กœ์šฐ

1. ์ดˆ๊ธฐ ์„ค์ •

ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
# ํ…Œ์ŠคํŠธ DB ์ƒ์„ฑ ๋ฐ ์Šคํ‚ค๋งˆ ๋ณต์‚ฌ
pnpm fixture init

2. ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ์„ ํƒ

ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
# User #1, #2, #3 ๊ฐ€์ ธ์˜ค๊ธฐ
pnpm fixture import

# Entity: User
# IDs: 1,2,3

3. ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

user.model.test.ts
import { FixtureManager } from "sonamu/test";

beforeAll(async () => {
  // Fixture ๋™๊ธฐํ™”
  await FixtureManager.sync();
});

test("User๋ฅผ ์ด๋ฉ”์ผ๋กœ ์ฐพ๊ธฐ", async () => {
  // Fixture์˜ User #1 ์‚ฌ์šฉ
  const user = await UserModel.findByEmail("user1@example.com");

  expect(user).toBeDefined();
  expect(user.id).toBe(1);
});

4. ํ…Œ์ŠคํŠธ ์‹คํ–‰

pnpm test
ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค fixture๊ฐ€ ์ž๋™์œผ๋กœ ๋™๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค.

Fixture ํŒŒ์ผ

์ €์žฅ ์œ„์น˜

ํŒŒ์ผ ํ˜•์‹

src/fixtures/users.json
[
  {
    "id": 1,
    "email": "user1@example.com",
    "name": "ํ™๊ธธ๋™",
    "created_at": "2024-01-15T00:00:00.000Z"
  },
  {
    "id": 2,
    "email": "user2@example.com",
    "name": "๊น€์ฒ ์ˆ˜",
    "created_at": "2024-01-16T00:00:00.000Z"
  }
]
ํŠน์ง•:
  • JSON ํ˜•์‹
  • Entity๋ณ„๋กœ ํŒŒ์ผ ๋ถ„๋ฆฌ
  • ๊ด€๊ณ„ ๋ฐ์ดํ„ฐ ์ž๋™ ํฌํ•จ
  • Git์œผ๋กœ ๋ฒ„์ „ ๊ด€๋ฆฌ

๊ด€๊ณ„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

์™ธ๋ž˜ํ‚ค ์ž๋™ ํฌํ•จ

Post๋ฅผ importํ•˜๋ฉด ์—ฐ๊ฒฐ๋œ User๋„ ์ž๋™์œผ๋กœ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
pnpm fixture import

# Entity: Post
# IDs: 1

# ์ž๋™์œผ๋กœ ํฌํ•จ๋จ:
# โœ“ Post #1
# โœ“ User #5 (author)
# โœ“ Category #2 (category)

N:M ๊ด€๊ณ„ ์ฒ˜๋ฆฌ

๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„ ํ…Œ์ด๋ธ”๋„ ์ž๋™์œผ๋กœ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
pnpm fixture import

# Entity: Post
# IDs: 1

# ์ž๋™์œผ๋กœ ํฌํ•จ๋จ:
# โœ“ Post #1
# โœ“ post_tags (์ค‘๊ฐ„ ํ…Œ์ด๋ธ”)
# โœ“ Tag #1, #2, #3 (์—ฐ๊ฒฐ๋œ ํƒœ๊ทธ๋“ค)

ํ…Œ์ŠคํŠธ ๊ฒฉ๋ฆฌ

๊ฐ ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ์ดˆ๊ธฐํ™”

describe("User CRUD", () => {
  beforeEach(async () => {
    // ๊ฐ ํ…Œ์ŠคํŠธ ์ „์— fixture ์žฌ์ ์šฉ
    await FixtureManager.sync();
  });

  test("์‚ฌ์šฉ์ž ์ƒ์„ฑ", async () => {
    const user = await UserModel.create({
      email: "new@example.com",
      name: "์‹ ๊ทœ์‚ฌ์šฉ์ž",
    });

    expect(user.id).toBeDefined();
  });

  test("์‚ฌ์šฉ์ž ์‚ญ์ œ", async () => {
    await UserModel.deleteById(1);

    const user = await UserModel.findById(1);
    expect(user).toBeNull();
  });
});
๊ฒฉ๋ฆฌ ํšจ๊ณผ:
  • ๊ฐ ํ…Œ์ŠคํŠธ๊ฐ€ ๋…๋ฆฝ์ 
  • ํ…Œ์ŠคํŠธ ์ˆœ์„œ ๋ฌด๊ด€
  • ๋ณ‘๋ ฌ ์‹คํ–‰ ๊ฐ€๋Šฅ

ํŠธ๋žœ์žญ์…˜ ๊ฒฉ๋ฆฌ

import { Sonamu } from "sonamu";

test("ํŠธ๋žœ์žญ์…˜ ํ…Œ์ŠคํŠธ", async () => {
  await Sonamu.runScript(async () => {
    // ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์‹คํ–‰
    const user = await UserModel.create({...});
    const post = await PostModel.create({...});

    // ํ…Œ์ŠคํŠธ ์™„๋ฃŒ ํ›„ ์ž๋™ ๋กค๋ฐฑ
  });
});

์‹ค์ „ ์˜ˆ์ œ

๋ณต์žกํ•œ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ

describe("Post with Comments", () => {
  beforeAll(async () => {
    // Post #1 (+ User #1, Comments)
    await FixtureManager.sync();
  });

  test("๋Œ“๊ธ€์ด ์žˆ๋Š” Post ์กฐํšŒ", async () => {
    const post = await PostModel.findById(1, {
      include: ["comments"],
    });

    expect(post.comments).toHaveLength(3);
    expect(post.comments[0].author_id).toBe(2);
  });
});

ํŠน์ • ์‹œ๋‚˜๋ฆฌ์˜ค ๋ฐ์ดํ„ฐ

describe("Premium User Features", () => {
  beforeAll(async () => {
    // Premium user๋งŒ import
    await FixtureManager.importFixture("User", [10, 11, 12]);
    await FixtureManager.sync();
  });

  test("ํ”„๋ฆฌ๋ฏธ์—„ ๊ธฐ๋Šฅ ์ ‘๊ทผ", async () => {
    const user = await UserModel.findById(10);

    expect(user.isPremium).toBe(true);
    expect(await user.canAccessFeature("advanced")).toBe(true);
  });
});

๋ฌธ์ œ ํ•ด๊ฒฐ

Fixture DB ์—ฐ๊ฒฐ ์‹คํŒจ

๋ฌธ์ œ: ์›๊ฒฉ fixture DB์— ์ ‘๊ทผ ๋ถˆ๊ฐ€
Error: connect ECONNREFUSED
ํ•ด๊ฒฐ:
sonamu.config.ts
export default {
  database: {
    fixture: {
      client: "pg",
      connection: {
        host: "fixture-db.example.com", // ์˜ฌ๋ฐ”๋ฅธ ํ˜ธ์ŠคํŠธ
        database: "myapp_fixture",
        user: "postgres",
        password: process.env.FIXTURE_DB_PASSWORD,
      },
    },
  },
};

๊ด€๊ณ„ ๋ฐ์ดํ„ฐ ๋ˆ„๋ฝ

๋ฌธ์ œ: ์™ธ๋ž˜ํ‚ค ์—๋Ÿฌ ๋ฐœ์ƒ
Error: Foreign key constraint violation
ํ•ด๊ฒฐ:
# ๊ด€๋ จ ๋ฐ์ดํ„ฐ๋ฅผ ๋จผ์ € import
pnpm fixture import
# Entity: User
# IDs: 1,2,3

pnpm fixture import
# Entity: Post
# IDs: 1,2,3  # User 1,2,3์„ ์ฐธ์กฐ

Fixture ์ถฉ๋Œ

๋ฌธ์ œ: ID ์ค‘๋ณต
Error: Duplicate entry '1' for key 'PRIMARY'
ํ•ด๊ฒฐ:
# Test DB ์ดˆ๊ธฐํ™”
pnpm fixture init

# Fixture ์žฌ๋™๊ธฐํ™”
pnpm fixture sync

๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

1. ์ตœ์†Œํ•œ์˜ ๋ฐ์ดํ„ฐ

# โŒ ๋ชจ๋“  ๋ฐ์ดํ„ฐ import
pnpm fixture import
# IDs: 1-1000

# โœ… ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ
pnpm fixture import
# IDs: 1,2,3  # ๋Œ€ํ‘œ ์ผ€์ด์Šค๋งŒ

2. ์˜๋ฏธ์žˆ๋Š” ๋ฐ์ดํ„ฐ

// โœ… ํ…Œ์ŠคํŠธ ๋ชฉ์ ์ด ๋ช…ํ™•ํ•œ ๋ฐ์ดํ„ฐ
{
  "id": 1,
  "email": "admin@example.com",
  "role": "admin",
  "name": "๊ด€๋ฆฌ์ž"
}
{
  "id": 2,
  "email": "user@example.com",
  "role": "user",
  "name": "์ผ๋ฐ˜์‚ฌ์šฉ์ž"
}

3. ๋ฒ„์ „ ๊ด€๋ฆฌ

# Fixture ํŒŒ์ผ์„ Git์— ํฌํ•จ
git add src/fixtures/
git commit -m "Update test fixtures"

4. CI/CD ํ†ตํ•ฉ

.github/workflows/test.yml
jobs:
  test:
    steps:
      - name: Setup Database
        run: |
          pnpm fixture init
          pnpm fixture sync

      - name: Run Tests
        run: pnpm test

๋‹ค์Œ ๋‹จ๊ณ„

Testing

Naite ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ ์•Œ์•„๋ณด๊ธฐ

migrate

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ด€๋ฆฌํ•˜๊ธฐ