๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Naite๋ฅผ ํ™œ์šฉํ•œ ํšจ๊ณผ์ ์ธ ๋””๋ฒ„๊น… ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

Naite๋ž€?

Naite๋Š” Sonamu์˜ ํ…Œ์ŠคํŠธ ์ „์šฉ ๋กœ๊น… ์‹œ์Šคํ…œ์œผ๋กœ, ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ๋ชจ๋“  ๋กœ๊ทธ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฃผ์š” ํŠน์ง•:
  • ํ…Œ์ŠคํŠธ๋ณ„๋กœ ๋กœ๊ทธ ๊ฒฉ๋ฆฌ
  • ํ‚ค-๊ฐ’ ๊ธฐ๋ฐ˜ ๋กœ๊ทธ ์ €์žฅ
  • ๊ฐ•๋ ฅํ•œ ์ฟผ๋ฆฌ ๊ธฐ๋Šฅ
  • Naite Viewer๋กœ ์‹œ๊ฐํ™”

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

1. ๋กœ๊ทธ ๊ธฐ๋กํ•˜๊ธฐ

import { Naite } from "sonamu";

describe("User API", () => {
  test("์‚ฌ์šฉ์ž ์ƒ์„ฑ", async () => {
    const email = "[email protected]";
    
    // ๋กœ๊ทธ ๊ธฐ๋ก
    Naite.t("user.create.input", { email });
    
    const user = await UserModel.create({ email });
    
    Naite.t("user.create.output", { userId: user.id });
    
    expect(user.email).toBe(email);
  });
});

2. ๋กœ๊ทธ ์กฐํšŒํ•˜๊ธฐ

ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
test("๋””๋ฒ„๊น… ์˜ˆ์ œ", async () => {
  try {
    const result = await complexOperation();
    expect(result).toBe(expectedValue);
  } catch (error) {
    // ์‹คํŒจ ์‹œ ๋กœ๊ทธ ํ™•์ธ
    const logs = Naite.getAll();
    console.log("ํ…Œ์ŠคํŠธ ๋กœ๊ทธ:", logs);
    throw error;
  }
});

๋””๋ฒ„๊น… ํŒจํ„ด

API ์š”์ฒญ/์‘๋‹ต ์ถ”์ 

test("API ํ˜ธ์ถœ ์ถ”์ ", async () => {
  const params = { page: 1, limit: 10 };
  
  // ์š”์ฒญ ๊ธฐ๋ก
  Naite.t("api.request", { 
    method: "findMany", 
    params 
  });
  
  const users = await UserModel.findMany(params);
  
  // ์‘๋‹ต ๊ธฐ๋ก
  Naite.t("api.response", { 
    count: users.length,
    firstUser: users[0]
  });
  
  expect(users.length).toBeGreaterThan(0);
});

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ ์ถ”์ 

test("์ฟผ๋ฆฌ ์ถ”์ ", async () => {
  const userId = 1;
  
  // ์ฟผ๋ฆฌ ์ „
  Naite.t("db.query.before", { userId });
  
  const user = await UserModel.findById(userId);
  
  // ์ฟผ๋ฆฌ ํ›„
  Naite.t("db.query.after", { 
    found: !!user,
    data: user 
  });
  
  expect(user).toBeDefined();
});

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ํ๋ฆ„ ์ถ”์ 

test("์ฃผ๋ฌธ ํ”„๋กœ์„ธ์Šค ์ถ”์ ", async () => {
  const orderId = 1;
  
  Naite.t("order.process.start", { orderId });
  
  // 1. ์ฃผ๋ฌธ ์กฐํšŒ
  const order = await OrderModel.findById(orderId);
  Naite.t("order.found", { status: order.status });
  
  // 2. ๊ฒฐ์ œ ์ฒ˜๋ฆฌ
  const payment = await processPayment(order);
  Naite.t("payment.processed", { 
    paymentId: payment.id,
    amount: payment.amount 
  });
  
  // 3. ์ฃผ๋ฌธ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
  await OrderModel.updateStatus(orderId, "paid");
  Naite.t("order.status.updated", { newStatus: "paid" });
  
  Naite.t("order.process.complete", { orderId });
});

๋กœ๊ทธ ํ‚ค ๋„ค์ด๋ฐ ๊ทœ์น™

๊ณ„์ธต์  ๊ตฌ์กฐ๋กœ ํ‚ค๋ฅผ ์„ค๊ณ„ํ•˜๋ฉด ๋‚˜์ค‘์— ์ฟผ๋ฆฌํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค:
// โœ… ์ข‹์€ ํ‚ค ๋„ค์ด๋ฐ
Naite.t("user.create.input", data);
Naite.t("user.create.validation", result);
Naite.t("user.create.output", user);

// โœ… ๋„๋ฉ”์ธ๋ณ„ ๋ถ„๋ฆฌ
Naite.t("order.payment.start", params);
Naite.t("order.payment.complete", result);

// โŒ ๋‚˜์œ ํ‚ค ๋„ค์ด๋ฐ
Naite.t("data1", data);
Naite.t("result", result);
Naite.t("test", value);
๊ถŒ์žฅ ํŒจํ„ด:
{domain}.{action}.{stage}
์˜ˆ: user.create.input, order.payment.complete

์กฐ๊ฑด๋ถ€ ๋กœ๊น…

๋””๋ฒ„๊น…์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ๋กœ๊ทธ๋ฅผ ํ™œ์„ฑํ™”:
const DEBUG = process.env.DEBUG === "true";

test("์กฐ๊ฑด๋ถ€ ๋กœ๊น…", async () => {
  if (DEBUG) {
    Naite.t("debug.start", { timestamp: Date.now() });
  }
  
  const result = await someOperation();
  
  if (DEBUG) {
    Naite.t("debug.result", result);
  }
  
  expect(result).toBeDefined();
});

Model์—์„œ Naite ์‚ฌ์šฉ

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ Naite๋กœ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
class UserModelClass extends BaseModel {
  @api()
  async createUser(name: string, email: string) {
    // Naite.t()๋กœ ๋กœ๊ทธ ๊ธฐ๋ก
    Naite.t("user.input", { name, email });
    
    const user = await this.save({ name, email });
    
    Naite.t("user.created", user);
    
    return user;
  }
}
ํ…Œ์ŠคํŠธ์—์„œ ํ™•์ธ:
test("Naite ๋กœ๊ทธ ํ™•์ธ", async () => {
  await UserModel.createUser("Alice", "[email protected]");
  
  // .first()๋กœ ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ ์ถ”์ถœ
  const input = Naite.get("user.input").first();
  const created = Naite.get("user.created").first();
  
  expect(input.name).toBe("Alice");
  expect(created.id).toBeDefined();
});

๋กœ๊ทธ ์ฟผ๋ฆฌ

Naite๋Š” ๊ฐ•๋ ฅํ•œ ์ฟผ๋ฆฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:
test("๋กœ๊ทธ ์ฟผ๋ฆฌ", async () => {
  // ์—ฌ๋Ÿฌ ์ž‘์—… ์ˆ˜ํ–‰
  await createUsers();
  await processOrders();
  
  // ํŠน์ • ํ‚ค๋กœ ์กฐํšŒ
  const userLogs = Naite.get("user").result();
  
  // ํŒจํ„ด์œผ๋กœ ์กฐํšŒ (wildcard ์ง€์›)
  const createLogs = Naite.get("*.create.*").result();
  
  // ์ „์ฒด ๋กœ๊ทธ
  const allLogs = Naite.getAll();
  
  console.log("User logs:", userLogs);
  console.log("Create logs:", createLogs);
  console.log("All logs:", allLogs);
});

NaiteQuery ๋ฉ”์„œ๋“œ

Naite.get()์€ NaiteQuery ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค:
test("NaiteQuery ๋ฉ”์„œ๋“œ", async () => {
  Naite.t("test.data", { value: 1 });
  Naite.t("test.data", { value: 2 });
  Naite.t("test.data", { value: 3 });
  
  // ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ
  const first = Naite.get("test.data").first();
  console.log(first); // { value: 1 }
  
  // ๋งˆ์ง€๋ง‰ ๋ฐ์ดํ„ฐ
  const last = Naite.get("test.data").last();
  console.log(last); // { value: 3 }
  
  // ๋ชจ๋“  ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด
  const all = Naite.get("test.data").result();
  console.log(all); // [{ value: 1 }, { value: 2 }, { value: 3 }]
});

Naite Viewer ์‚ฌ์šฉ

ํ…Œ์ŠคํŠธ ์‹คํ–‰ ํ›„ Naite Viewer๋กœ ๋กœ๊ทธ๋ฅผ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค:
# ํ…Œ์ŠคํŠธ ์‹คํ–‰
pnpm test

# Naite Viewer ์‹คํ–‰
pnpm naite:view
Viewer ๊ธฐ๋Šฅ:
  • ํ…Œ์ŠคํŠธ๋ณ„ ๋กœ๊ทธ ํ•„ํ„ฐ๋ง
  • ํ‚ค ๊ธฐ๋ฐ˜ ๊ฒ€์ƒ‰
  • ํƒ€์ž„๋ผ์ธ ๋ทฐ
  • ๋กœ๊ทธ ๋‚ด๋ณด๋‚ด๊ธฐ

๋””๋ฒ„๊น… ์›Œํฌํ”Œ๋กœ์šฐ

1. ํ…Œ์ŠคํŠธ ์‹คํŒจ ์‹œ

test("์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ", async () => {
  // ์ถฉ๋ถ„ํ•œ ๋กœ๊ทธ ์ถ”๊ฐ€
  Naite.t("test.start", { timestamp: Date.now() });
  
  try {
    const result = await problematicFunction();
    Naite.t("test.result", result);
    expect(result).toBe(expectedValue);
  } catch (error) {
    // ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์ „์ฒด ๋กœ๊ทธ ์ถœ๋ ฅ
    console.log("=== Naite Logs ===");
    console.log(JSON.stringify(Naite.getAll(), null, 2));
    throw error;
  }
});

2. ๋กœ๊ทธ ๋ถ„์„

afterEach(() => {
  // ํ…Œ์ŠคํŠธ ํ›„ ๋กœ๊ทธ ์š”์•ฝ
  const allLogs = Naite.getAll();
  const errorKeys = Object.keys(allLogs).filter(key => 
    key.includes("error") || key.includes("fail")
  );
  
  if (errorKeys.length > 0) {
    const errorLogs = errorKeys.map(key => ({ key, data: allLogs[key] }));
    console.log("โš ๏ธ ์—๋Ÿฌ ๋กœ๊ทธ:", errorLogs);
  }
});

3. ํŠน์ • ์‹œ๋‚˜๋ฆฌ์˜ค ์žฌํ˜„

test("๋ฒ„๊ทธ ์žฌํ˜„", async () => {
  // ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค ์žฌํ˜„
  Naite.t("bug.reproduction.start", {
    scenario: "์‚ฌ์šฉ์ž ์ƒ์„ฑ ํ›„ ์ฆ‰์‹œ ์‚ญ์ œ"
  });
  
  const user = await UserModel.create({ email: "[email protected]" });
  Naite.t("bug.user.created", user);
  
  await UserModel.del(user.id);
  Naite.t("bug.user.deleted", { userId: user.id });
  
  // ๋‹ค์‹œ ์กฐํšŒ ์‹œ๋„
  const deletedUser = await UserModel.findById(user.id);
  Naite.t("bug.user.found", { found: !!deletedUser });
  
  expect(deletedUser).toBeNull();
});

์„ฑ๋Šฅ ๋””๋ฒ„๊น…

๋กœ๊ทธ์— ํƒ€์ž„์Šคํƒฌํ”„๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์„ฑ๋Šฅ ๋ถ„์„:
test("์„ฑ๋Šฅ ์ธก์ •", async () => {
  const start = Date.now();
  Naite.t("perf.start", { timestamp: start });
  
  // ๋ฌด๊ฑฐ์šด ์ž‘์—…
  const result = await heavyOperation();
  
  const end = Date.now();
  Naite.t("perf.end", { 
    timestamp: end,
    duration: end - start,
    result 
  });
  
  // ์„ฑ๋Šฅ ๊ธฐ์ค€ ๊ฒ€์ฆ
  expect(end - start).toBeLessThan(1000); // 1์ดˆ ์ด๋‚ด
});

์ฃผ์˜์‚ฌํ•ญ

1. ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉ ๊ธˆ์ง€

// โŒ ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์—์„œ Naite ์‚ฌ์šฉํ•˜์ง€ ๋ง ๊ฒƒ
class UserModelClass extends BaseModel {
  async createUser() {
    Naite.t("user.create", data); // โŒ ํ…Œ์ŠคํŠธ ์ „์šฉ!
    return this.save(data);
  }
}

2. ๋ฏผ๊ฐํ•œ ์ •๋ณด ๋กœ๊น… ์ฃผ์˜

test("๋กœ๊ทธ ์ฃผ์˜์‚ฌํ•ญ", async () => {
  // โŒ ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ™์€ ๋ฏผ๊ฐ ์ •๋ณด ๋กœ๊น…ํ•˜์ง€ ๋ง ๊ฒƒ
  Naite.t("user.create", { 
    email,
    password  // โŒ
  });
  
  // โœ… ๋ฏผ๊ฐ ์ •๋ณด ์ œ์™ธ
  Naite.t("user.create", { 
    email,
    hasPassword: !!password  // โœ…
  });
});

3. ๊ณผ๋„ํ•œ ๋กœ๊น… ํ”ผํ•˜๊ธฐ

// โŒ ๋ฃจํ”„ ์•ˆ์—์„œ ๊ณผ๋„ํ•œ ๋กœ๊น…
for (let i = 0; i < 1000; i++) {
  Naite.t(`item.${i}`, data); // โŒ ๋„ˆ๋ฌด ๋งŽ์Œ
}

// โœ… ์š”์•ฝ ๋กœ๊น…
Naite.t("items.processed", { 
  count: 1000,
  sample: items.slice(0, 3)  // โœ… ์ƒ˜ํ”Œ๋งŒ
});

๊ด€๋ จ ๋ฌธ์„œ