VSCode Extension์ธ Naite Viewer๋ฅผ ์ฌ์ฉํ์ฌ ํ
์คํธ ์คํ ์ค ๊ธฐ๋ก๋ ๋ก๊ทธ๋ฅผ ์ค์๊ฐ์ผ๋ก ์๊ฐํํ๋ ๋ฐฉ๋ฒ์ ์์๋ด
๋๋ค.
Naite Viewer ๊ฐ์
์ค์๊ฐ ์๊ฐํ
ํ
์คํธ ์คํ ์ค๋ก๊ทธ ์ฆ์ ํ์
Unix Socket ํต์
ํ๋ก์ธ์ค ๊ฐ ํต์ ๋น ๋ฅธ ์ ์ก
ํ๋ก์ ํธ๋ณ ๊ฒฉ๋ฆฌ
์์ผ ํ์ผ ๋ถ๋ฆฌ๋
๋ฆฝ์ ์ด์
์๋ ์ฐ๊ฒฐ
Extension ์คํ ์์๋ ์์ผ ์์ฑ
Naite Viewer๋?
Naite Viewer๋ Sonamu VSCode Extension์ ํฌํจ๋ ๊ธฐ๋ฅ์ผ๋ก, ํ
์คํธ ์คํ ์ค ๊ธฐ๋ก๋ Naite ๋ก๊ทธ๋ฅผ ์ค์๊ฐ์ผ๋ก ์๊ฐํํฉ๋๋ค.
Naite๋ ํ
์คํธ ์คํ ์ค Naite.t()๋ก ๊ธฐ๋กํ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ VSCode Extension์ผ๋ก ์ ์กํ์ฌ, ๊ฐ๋ฐ์๊ฐ ํ
์คํธ ํ๋ฆ์ ์๊ฐ์ ์ผ๋ก ์ถ์ ํ ์ ์๊ฒ ํฉ๋๋ค. ์ด๋ ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง์ด๋ ์ฌ๋ฌ ํจ์ ํธ์ถ์ด ์ฝํ ์ํฉ์์ ํนํ ์ ์ฉํฉ๋๋ค.
์ฃผ์ ๊ธฐ๋ฅ
์ค์๊ฐ ๋ก๊ทธ ํ์
ํ
์คํธ ์คํ ์ค Naite.t()๋ก ๊ธฐ๋ก๋ ๋ชจ๋ ๋ก๊ทธ๋ฅผ ์ฆ์ VSCode ํจ๋์ ํ์ํฉ๋๋ค. ์ฝ์์์ ๋ก๊ทธ๋ฅผ ์ฐพ์ ํ์ ์์ด ๊ตฌ์กฐํ๋ ํํ๋ก ํ์ธํ ์ ์์ต๋๋ค.
ํ
์คํธ๋ณ ๊ทธ๋ฃนํ
๊ฐ ํ
์คํธ ์ผ์ด์ค๋ณ๋ก ๋ก๊ทธ๋ฅผ ์๋ ๊ทธ๋ฃนํํฉ๋๋ค. Suite ๋จ์๋ก๋ ๋ถ๋ฅ๋์ด ๋๊ท๋ชจ ํ
์คํธ์์๋ ์ํ๋ ๋ก๊ทธ๋ฅผ ๋น ๋ฅด๊ฒ ์ฐพ์ ์ ์์ต๋๋ค.
์ฝ์คํ ์ถ์
๊ฐ ๋ก๊ทธ์ ํธ์ถ ์์น์ ์ ์ฒด ์ฝ์คํ ์ ๋ณด๋ฅผ ํ์ํฉ๋๋ค. ๋ก๊ทธ๋ฅผ ํด๋ฆญํ๋ฉด ํด๋น ์ฝ๋ ์์น๋ก ๋ฐ๋ก ์ด๋ํ ์ ์์ต๋๋ค.
ํํฐ๋ง ๋ฐ ๊ฒ์
wildcard ํจํด(user:*)์ผ๋ก ํน์ ๋ชจ๋์ ๋ก๊ทธ๋ง ํํฐ๋งํ๊ฑฐ๋, ํค์๋๋ก ๋ก๊ทธ ๋ด์ฉ์ ๊ฒ์ํ ์ ์์ต๋๋ค.
์ํคํ
์ฒ
1. Unix Socket ํต์
Naite Viewer๋ Unix Socket์ ํตํด ํ
์คํธ ํ๋ก์ธ์ค์ ํต์ ํฉ๋๋ค. ์ด ๋ฐฉ์์ ํ์ผ ์์คํ
์ ์ฌ์ฉํ์ง ์๊ณ ํ๋ก์ธ์ค ๊ฐ ์ง์ ํต์ ์ด ๊ฐ๋ฅํ์ฌ ๋น ๋ฅด๊ณ ์์ ํฉ๋๋ค.
์์ผ ํต์ ์ ์ด์ :
- ๋น ๋ฅธ ์ ์ก: HTTP๋ ํ์ผ๋ณด๋ค ํจ์ฌ ๋น ๋ฅธ IPC(Inter-Process Communication)
- ์ค์๊ฐ์ฑ: ํ
์คํธ ์คํ ์ฆ์ ๋ก๊ทธ๊ฐ Extension์ ์ ๋ฌ๋จ
- ๊ฒฉ๋ฆฌ: ํ๋ก์ ํธ๋ณ ๋
๋ฆฝ๋ ์์ผ์ผ๋ก ์ถฉ๋ ๋ฐฉ์ง
์์ผ ๊ฒฝ๋ก ๊ท์น:
~/.sonamu/naite-{hash}.sock
{hash}๋ sonamu.config.ts ๊ฒฝ๋ก์ MD5 ํด์ ์ 8์๋ฆฌ์
๋๋ค.Windows Named Pipe๋ฅผ ์ฌ์ฉํฉ๋๋ค.
ํด์ ์์ฑ ๋ฐฉ์ ์์ธํ ๋ณด๊ธฐ
๊ฐ ํ๋ก์ ํธ๋ sonamu.config.ts ํ์ผ์ ์ ๋ ๊ฒฝ๋ก๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ณ ์ ํ ํด์๋ฅผ ์์ฑํฉ๋๋ค:import { createHash } from "crypto";
const configPath = "/project/api/src/sonamu.config.ts";
const hash = createHash("md5")
.update(configPath)
.digest("hex")
.slice(0, 8);
// ์: "a1b2c3d4"
// ์ต์ข
์์ผ ๊ฒฝ๋ก: ~/.sonamu/naite-a1b2c3d4.sock
์ด ๋ฐฉ์์ผ๋ก ์ฌ๋ฌ Sonamu ํ๋ก์ ํธ๋ฅผ ๋์์ ์คํํด๋ ๊ฐ๊ฐ ๋
๋ฆฝ๋ ์์ผ์ ์ฌ์ฉํ์ฌ ๋ก๊ทธ๊ฐ ์์ด์ง ์์ต๋๋ค.
2. ๋ฉ์์ง ํ๋กํ ์ฝ
ํ
์คํธ ํ๋ก์ธ์ค์ Extension ๊ฐ์๋ 3๊ฐ์ง ํ์
์ ๋ฉ์์ง๊ฐ ์ ์ก๋ฉ๋๋ค.
run/start - ํ
์คํธ ๋ฐ ์์
ํ
์คํธ ๋ฐ์ด ์์๋ ๋ ์ ์ก๋ฉ๋๋ค. Extension์ ์ด ๋ฉ์์ง๋ฅผ ๋ฐ์ผ๋ฉด ๊ธฐ์กด ๋ก๊ทธ๋ฅผ ๋ชจ๋ ํด๋ฆฌ์ดํ๊ณ ์๋ก์ด ํ
์คํธ ๋ฐ์ ์ค๋นํฉ๋๋ค.
{
type: "run/start",
startedAt: "2025-01-08T12:34:56.789Z"
}
Watch ๋ชจ๋์์ ํ์ผ์ ์์ ํ๋ฉด ํ
์คํธ๊ฐ ์ฌ์คํ๋๋๋ฐ, ๋งค๋ฒ run/start ๋ฉ์์ง๊ฐ ์ ์ก๋์ด Viewer๊ฐ ์ด๊ธฐํ๋ฉ๋๋ค.
test/result - ํ
์คํธ ๊ฒฐ๊ณผ
๊ฐ ํ
์คํธ ์ผ์ด์ค๊ฐ ์๋ฃ๋ ๋๋ง๋ค ์ ์ก๋ฉ๋๋ค. ํ
์คํธ ๊ฒฐ๊ณผ์ ํจ๊ป Naite ๋ก๊ทธ(traces)๊ฐ ํฌํจ๋ฉ๋๋ค.
{
type: "test/result",
receivedAt: "2025-01-08T12:34:57.123Z",
suiteName: "UserModel",
suiteFilePath: "/Users/.../user.model.test.ts",
testName: "์ฌ์ฉ์ ์์ฑ",
testFilePath: "/Users/.../user.model.test.ts",
testLine: 15,
status: "pass",
duration: 123,
traces: [
{
key: "user:create:input",
value: { username: "john" },
filePath: "/Users/.../user.model.test.ts",
lineNumber: 20,
at: "2025-01-08T12:34:57.100Z"
}
]
}
ํฌํจ ์ ๋ณด:
- ํ
์คํธ ๋ฉํ๋ฐ์ดํฐ: Suite๋ช
, ํ์ผ ๊ฒฝ๋ก, ๋ผ์ธ ๋ฒํธ
- ํ
์คํธ ๊ฒฐ๊ณผ: ์ฑ๊ณต/์คํจ ์ํ, ์์ ์๊ฐ, ์๋ฌ ์ ๋ณด
- Naite traces:
Naite.t()๋ก ๊ธฐ๋ก๋ ๋ชจ๋ ๋ก๊ทธ
run/end - ํ
์คํธ ๋ฐ ์ข
๋ฃ
๋ชจ๋ ํ
์คํธ๊ฐ ์๋ฃ๋๋ฉด ์ ์ก๋ฉ๋๋ค. Extension์ ์ด ๋ฉ์์ง๋ก ํ
์คํธ ๋ฐ์ด ๋๋ฌ์์ ์ธ์งํฉ๋๋ค.
{
type: "run/end",
endedAt: "2025-01-08T12:34:58.789Z"
}
3. ์ ์ก ํ๋ฆ
์ ์ฒด ํ
์คํธ ์คํ ๊ณผ์ ์์ ๋ฉ์์ง๊ฐ ์ ์ก๋๋ ์์๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์ฝ๋๋ก ๋ณด๋ ์ ์ก ํ๋ฆ
// 1. ํ
์คํธ ์์ - ์์ผ ์ฐ๊ฒฐ ๋ฐ run/start ์ ์ก
beforeAll(async () => {
await NaiteReporter.startTestRun();
// โ send({ type: "run/start" })
});
// 2. ํ
์คํธ ์คํ - Naite ๋ก๊ทธ ์์ง
test("์ฌ์ฉ์ ์์ฑ", async () => {
Naite.t("user:create:input", { username: "john" });
const { user } = await userModel.create({ username: "john" });
Naite.t("user:create:output", { userId: user.id });
// ํ
์คํธ ์ข
๋ฃ ํ afterEach ์คํ
});
// 3. ํ
์คํธ ์ข
๋ฃ - traces ์์ง ๋ฐ ์ ์ก
afterEach(async ({ task }) => {
// getAllTraces()๋ก ๋ชจ๋ Naite ๋ก๊ทธ ์์ง
task.meta.traces = Naite.getAllTraces();
// Extension์ผ๋ก ์ ์ก
await NaiteReporter.reportTestResult({
testName: task.name,
status: task.result?.state,
traces: task.meta.traces,
// ...
});
// โ send({ type: "test/result", traces: [...] })
});
// 4. ์ ์ฒด ๋ฐ ์ข
๋ฃ - run/end ์ ์ก ๋ฐ ์์ผ ์ข
๋ฃ
afterAll(() => {
await NaiteReporter.endTestRun();
// โ send({ type: "run/end" })
// โ socket.end()
});
๋ฒํผ๋ง ๋ฉ์ปค๋์ฆ: Extension์ด ์์ง ์์๋์ง ์์๊ฑฐ๋ ์์ผ ์ฐ๊ฒฐ์ด ์ง์ฐ๋๋ ๊ฒฝ์ฐ, NaiteReporter๋ ๋ฉ์์ง๋ฅผ ๋ฒํผ์ ์ ์ฅํ๋ค๊ฐ ์ฐ๊ฒฐ ์ฑ๊ณต ์ ํ ๋ฒ์ ์ ์กํฉ๋๋ค. ์ด๋ก ์ธํด Extension์ ๋ฆ๊ฒ ์ผ๋ ์ด์ ํ
์คํธ ๋ก๊ทธ๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
์ค์น ๋ฐ ์ค์
1. Extension ์ค์น
VSCode Marketplace์์ ์ค์น
- VSCode ์ผ์ชฝ ์ฌ์ด๋๋ฐ์์ Extensions ์์ด์ฝ ํด๋ฆญ
- โSonamuโ ๊ฒ์
- Install ๋ฒํผ ํด๋ฆญ
Extension ํ์ฑํ ํ์ธ
์ค์น ํ VSCode ํ๋จ ์ํ๋ฐ์ Sonamu ์์ด์ฝ์ด ๋ํ๋๋ฉด ์ ์์ ์ผ๋ก ํ์ฑํ๋ ๊ฒ์
๋๋ค.
2. ์๋ ์ฐ๊ฒฐ
Extension์ด ์คํ๋๋ฉด ์๋์ผ๋ก ์์ผ ์๋ฒ๋ฅผ ์์ํฉ๋๋ค:
ํ๋ก์ ํธ ๊ฐ์ง
ํ์ฌ ์ํฌ์คํ์ด์ค์์ sonamu.config.ts ํ์ผ์ ์ฐพ์ต๋๋ค.
ํด์ ๊ณ์ฐ
sonamu.config.ts์ ์ ๋ ๊ฒฝ๋ก๋ก MD5 ํด์๋ฅผ ์์ฑํฉ๋๋ค (์ 8์๋ฆฌ).
์์ผ ์๋ฒ ์์
~/.sonamu/naite-{hash}.sock ๊ฒฝ๋ก์ Unix Socket ์๋ฒ๋ฅผ ์์ํฉ๋๋ค.
ํ
์คํธ ํ๋ก์ธ์ค ๋๊ธฐ
ํ
์คํธ ์คํ ์ NaiteReporter๊ฐ ์ด ์์ผ์ผ๋ก ์ฐ๊ฒฐ๋ฉ๋๋ค.
Extension์ ํ๋ก์ ํธ ๊ฒฝ๋ก๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์ผ์ ์์ฑํ๋ฏ๋ก, ํ๋ก์ ํธ ํด๋๋ฅผ ์ง์ ์ด์ด์ผ ์ ์ ์๋ํฉ๋๋ค. ์์ ํด๋๋ฅผ ์ด๋ฉด ์์ผ ๊ฒฝ๋ก๊ฐ ๋ฌ๋ผ์ ธ ์ฐ๊ฒฐ๋์ง ์์ ์ ์์ต๋๋ค.
3. ํ
์คํธ ์คํ
์ผ๋ฐ ์คํ
Watch ๋ชจ๋
ํน์ ํ์ผ
๋ชจ๋ ํ
์คํธ๋ฅผ ํ ๋ฒ ์คํํ๊ณ ์ข
๋ฃํฉ๋๋ค. ํ์ผ ๋ณ๊ฒฝ ์ ์๋์ผ๋ก ๊ด๋ จ ํ
์คํธ๋ฅผ ์ฌ์คํํฉ๋๋ค. ๊ฐ๋ฐ ์ค ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋ ๋ชจ๋์
๋๋ค. pnpm test user.model.test.ts
ํน์ ํ
์คํธ ํ์ผ๋ง ์คํํฉ๋๋ค.
ํ
์คํธ ์คํ ์ ์๋์ผ๋ก Extension๊ณผ ์ฐ๊ฒฐ๋์ด ๋ก๊ทธ๊ฐ ์ค์๊ฐ์ผ๋ก ํ์๋ฉ๋๋ค.
์ฌ์ฉ๋ฒ
1. Naite Viewer ํจ๋ ์ด๊ธฐ

Command Palette์์ โNaite: Open Viewerโ๋ฅผ ์คํํ์ฌ ํจ๋์ ์ฌ๋ ๋ชจ์ต
Command Palette
์ฌ์ด๋๋ฐ
๋จ์ถํค
Cmd+Shift+P (macOS) ๋๋ Ctrl+Shift+P (Windows/Linux)
- โNaite: Open Viewerโ ์
๋ ฅ
- Enter
- VSCode ์ผ์ชฝ Activity Bar์์ Sonamu ์์ด์ฝ ํด๋ฆญ
- โNaite Viewerโ ํญ ์ ํ
๋จ์ถํค๋ฅผ ์ค์ ํ๋ฉด ๋ ๋น ๋ฅด๊ฒ ์ด ์ ์์ต๋๋ค:
Cmd+K Cmd+S โ Keyboard Shortcuts
- โNaite: Open Viewerโ ๊ฒ์
- ์ํ๋ ํค ์กฐํฉ ์ค์ (์:
Cmd+Shift+N)
2. ๋ก๊ทธ ํ์ธ
ํ
์คํธ ์คํ ์ Naite Viewer์ ๋ก๊ทธ๊ฐ ์๋์ผ๋ก ํ์๋ฉ๋๋ค.
Viewer ํ๋ฉด ๊ตฌ์ฑ
Naite Viewer๋ ํ
์คํธ๋ฅผ 3๋จ๊ณ ๊ณ์ธต์ผ๋ก ํ์ํฉ๋๋ค:
๊ณ์ธต ๊ตฌ์กฐ
์ค์ ํ๋ฉด
๊ตฌ์กฐ ์ค๋ช
๐ UserModel (5 tests, 4 passed, 1 failed)
โ ์ฌ์ฉ์ ์์ฑ (123ms)
โ ์ฌ์ฉ์ ์
๋ฐ์ดํธ (98ms)
{
"userId": 123,
"username": "jane"
}
โ ์ฌ์ฉ์ ์ญ์ (45ms)
โ ์ฌ์ฉ์ ์กฐํ (32ms)
โ ์ค๋ณต ์ฌ์ฉ์ ์์ฑ ์คํจ (67ms)
| ๋ ๋ฒจ | ์ค๋ช
| ์์ |
|---|
| Suite | ํ
์คํธ ํ์ผ ๋จ์๋ก ๊ทธ๋ฃนํ | UserModel, PostModel |
| Test | ๊ฐ๋ณ ํ
์คํธ ์ผ์ด์ค | โ ์ฌ์ฉ์ ์์ฑ (123ms) |
| Trace | Naite.t()๋ก ๊ธฐ๋ก๋ ๋ก๊ทธ | user:create:input |
์ํ ์์ด์ฝ:
- โ Pass: ํ
์คํธ ์ฑ๊ณต
- โ Fail: ํ
์คํธ ์คํจ
- โ Skip: ํ
์คํธ ๊ฑด๋๋
- โ Pending: ๋๊ธฐ ์ค
์ธํฐ๋์
:
- Suite/Test๋ฅผ ํด๋ฆญํ๋ฉด ์ ์๋ค ํผ์น ์ ์์
- Trace๋ฅผ ํด๋ฆญํ๋ฉด ์ฝ์คํ ์ ๋ณด ํ์
- ๊ฐ ์ฝ์คํ ํ๋ ์ ํด๋ฆญ ์ ์ฝ๋ ์์น๋ก ์ด๋

์ค์ Naite Viewer์์ ํ
์คํธ ๋ก๊ทธ๊ฐ ํ์๋๋ ๋ชจ์ต (Suite > Test > Trace ๊ณ์ธต ๊ตฌ์กฐ)
์ค์๊ฐ ์
๋ฐ์ดํธ: Watch ๋ชจ๋์์ ํ์ผ์ ์์ ํ๋ฉด ํ
์คํธ๊ฐ ์๋ ์ฌ์คํ๋๊ณ , Viewer๋ ์ฆ์ ์
๋ฐ์ดํธ๋ฉ๋๋ค. ์ด๋ฅผ ํตํด ์ฝ๋ ๋ณ๊ฒฝ์ ์ํฅ์ ๋ฐ๋ก ํ์ธํ ์ ์์ต๋๋ค.
3. ์ฝ์คํ ํ์ธ
๋ก๊ทธ๋ฅผ ํด๋ฆญํ๋ฉด ํด๋น Naite.t() ํธ์ถ์ ์์ธ ์ ๋ณด๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
user:create:input
{ username: "john", email: "[email protected]" }
๐ ์ง์ ํธ์ถ ์์น:
/Users/.../user.model.test.ts:20
๐ ์ ์ฒด ์ฝ์คํ:
1. test (user.model.test.ts:20)
โ ํด๋ฆญํ๋ฉด ์ด ์์น๋ก ์ด๋
2. createUser (user.model.ts:45)
3. runWithMockContext (bootstrap.ts:58)
์ฝ์คํ์ ์๋ฏธ:
test (20๋ฒ์งธ ์ค): ํ
์คํธ ์ฝ๋์์ Naite.t()๋ฅผ ํธ์ถํ ์์น
createUser (45๋ฒ์งธ ์ค): ํ
์คํธ๊ฐ ํธ์ถํ ์ค์ ๋น์ฆ๋์ค ๋ก์ง
runWithMockContext: Sonamu์ Context ๋ํผ (์ฌ๊ธฐ๊น์ง๋ง ํ์)

๋ก๊ทธ๋ฅผ ํด๋ฆญํ์ ๋ ํ์๋๋ ์ฝ์คํ ์ ๋ณด์ ์ฝ๋ ์์น๋ก ์ด๋ํ๋ ๊ธฐ๋ฅ
์ฝ์คํ์ ๊ฐ ํญ๋ชฉ์ ํด๋ฆญํ๋ฉด ํด๋น ํ์ผ์ ์ ํํ ๋ผ์ธ์ผ๋ก ๋ฐ๋ก ์ด๋ํฉ๋๋ค. ์ด๋ ๋ณต์กํ ํธ์ถ ์ฒด์ธ์ ๋๋ฒ๊น
ํ ๋ ํนํ ์ ์ฉํฉ๋๋ค.
4. ํํฐ๋ง ๋ฐ ๊ฒ์
๋๊ท๋ชจ ํ
์คํธ์์๋ ์๋ฐฑ ๊ฐ์ ๋ก๊ทธ๊ฐ ์์ฑ๋ ์ ์์ต๋๋ค. ํํฐ๋ง ๊ธฐ๋ฅ์ผ๋ก ์ํ๋ ๋ก๊ทธ๋ง ๋น ๋ฅด๊ฒ ์ฐพ์ ์ ์์ต๋๋ค.
ํค ํจํด ํํฐ
ํ
์คํธ ๊ฒ์
์ํ ํํฐ
wildcard ํจํด์ผ๋ก ํน์ ๋ชจ๋์ ๋ก๊ทธ๋ง ํ์:user:* โ user:create, user:update, user:delete
syncer:* โ syncer:renderTemplate, syncer:writeFile
*:create โ user:create, post:create
syncer:*:user โ syncer:renderTemplate:user, syncer:writeFile:user
์
๋ ฅ์ฐฝ์ ํจํด์ ์
๋ ฅํ๋ฉด ์ค์๊ฐ์ผ๋ก ํํฐ๋ง๋ฉ๋๋ค. ๋ก๊ทธ ๋ด์ฉ, ํ์ผ ๊ฒฝ๋ก, ํจ์๋ช
์ผ๋ก ๊ฒ์:
- ํค ๊ฒ์:
user:create
- ๊ฐ ๊ฒ์:
"john"
- ํ์ผ ๊ฒ์:
user.model.test.ts
- ํจ์ ๊ฒ์:
createUser
๊ฒ์ ๊ฒฐ๊ณผ๋ ํ์ด๋ผ์ดํธ๋ก ํ์๋ฉ๋๋ค. ํ
์คํธ ์ํ๋ก ํํฐ๋ง:
- โ Passed only: ์ฑ๊ณตํ ํ
์คํธ๋ง
- โ Failed only: ์คํจํ ํ
์คํธ๋ง
- โ Skipped: ๊ฑด๋๋ด ํ
์คํธ
- โ Pending: ๋๊ธฐ ์ค์ธ ํ
์คํธ
์ค์ ํ์ฉ ์ฌ๋ก
1. ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง ๋๋ฒ๊น
์ฌ๋ฌ ๋จ๊ณ๋ฅผ ๊ฑฐ์น๋ ๋ณต์กํ ๋ก์ง์ ๋๋ฒ๊น
ํ ๋ ๊ฐ ๋จ๊ณ์ ์ํ๋ฅผ ์ถ์ ํ ์ ์์ต๋๋ค.
test("์ฃผ๋ฌธ ์ฒ๋ฆฌ ์ ์ฒด ํ๋ฆ", async () => {
// 1๋จ๊ณ: ์ฃผ๋ฌธ ๊ฒ์ฆ
Naite.t("order:validate:start", { orderId: 123 });
await validateOrder(123);
Naite.t("order:validate:done", { valid: true });
// 2๋จ๊ณ: ์ฌ๊ณ ํ์ธ
Naite.t("order:inventory:check", { productId: 456 });
await checkInventory(456);
Naite.t("order:inventory:available", { quantity: 10 });
// 3๋จ๊ณ: ๊ฒฐ์ ์ฒ๋ฆฌ
Naite.t("order:payment:start", { amount: 50000 });
await processPayment({ orderId: 123, amount: 50000 });
Naite.t("order:payment:done", { transactionId: "tx_789" });
// 4๋จ๊ณ: ๋ฐฐ์ก ์์
Naite.t("order:shipping:start", { orderId: 123 });
await startShipping(123);
Naite.t("order:shipping:done", { trackingNumber: "TRK_001" });
});
Viewer์์ ํ์ธํ๊ธฐ
Viewer์์๋ ์ด๋ ๊ฒ ํ์๋ฉ๋๋ค:โ ์ฃผ๋ฌธ ์ฒ๋ฆฌ ์ ์ฒด ํ๋ฆ (456ms)
โโ order:validate:start { orderId: 123 }
โโ order:validate:done { valid: true }
โโ order:inventory:check { productId: 456 }
โโ order:inventory:available { quantity: 10 }
โโ order:payment:start { amount: 50000 }
โโ order:payment:done { transactionId: "tx_789" }
โโ order:shipping:start { orderId: 123 }
โโ order:shipping:done { trackingNumber: "TRK_001" }
๋ง์ฝ ๊ฒฐ์ ๋จ๊ณ์์ ์คํจํ๋ค๋ฉด:
order:payment:start๊น์ง๋ง ๋ก๊ทธ๊ฐ ์์
- ์ด๋ ๋จ๊ณ๊น์ง ์ ์ ์งํ๋์๋์ง ๋ช
ํํ ํ์
- ์ฝ์คํ์ผ๋ก ์ ํํ ์คํจ ์์น ํ์ธ
ํํฐ ํ์ฉ:
order:payment:* โ ๊ฒฐ์ ๊ด๋ จ ๋ก๊ทธ๋ง ํ์
order:*:start โ ๋ชจ๋ ๋จ๊ณ์ ์์ ๋ก๊ทธ๋ง ํ์
2. Syncer ์ฝ๋ ์์ฑ ์ถ์
Sonamu์ Syncer๊ฐ ์ด๋ค ํ
ํ๋ฆฟ์ ๋ ๋๋งํ๊ณ ์ด๋ค ํ์ผ์ ์์ฑํ๋์ง ์ถ์ ํ ์ ์์ต๋๋ค.
test("User ์ํฐํฐ ์ ์ฒด ์์ฑ", async () => {
await Sonamu.syncer.generateAll({ entityId: "User" });
// syncer:* ํํฐ๋ก Syncer ๊ด๋ จ ๋ก๊ทธ๋ง ํ์ธ
const syncerLogs = Naite.get("syncer:*").result();
expect(syncerLogs.length).toBeGreaterThan(0);
});
Viewer์๋ Syncer๊ฐ ์ํํ ๋ชจ๋ ์์
์ด ํ์๋ฉ๋๋ค:โ User ์ํฐํฐ ์ ์ฒด ์์ฑ (2.3s)
โโ syncer:generateAll:start { entityId: "User" }
โโ syncer:renderTemplate { template: "model", entityId: "User" }
โโ syncer:writeFile { path: "user.model.ts" }
โโ syncer:renderTemplate { template: "types", entityId: "User" }
โโ syncer:writeFile { path: "user.types.ts" }
โโ syncer:renderTemplate { template: "service", entityId: "User" }
โโ syncer:writeFile { path: "user.service.ts" }
โโ syncer:generateAll:done { filesCreated: 3 }
๋ถ์:
- ์ด๋ค ์์๋ก ํ์ผ์ด ์์ฑ๋์๋์ง ํ์ธ
- ๊ฐ ํ
ํ๋ฆฟ ๋ ๋๋ง ์๊ฐ ์ธก์
- ํน์ ํ
ํ๋ฆฟ๋ง ํํฐ๋งํ์ฌ ํ์ธ (
syncer:renderTemplate:*)
3. API ํธ์ถ ์ฒด์ธ ์ถ์
์ฌ๋ฌ API๋ฅผ ์ฐ์์ ์ผ๋ก ํธ์ถํ๋ ๊ฒฝ์ฐ, ๊ฐ API์ ์
์ถ๋ ฅ์ ์ถ์ ํ ์ ์์ต๋๋ค.
test("๊ฒ์๊ธ ์์ฑ โ ๋๊ธ ์ถ๊ฐ โ ์๋ฆผ ๋ฐ์ก", async () => {
// 1. ๊ฒ์๊ธ ์์ฑ
Naite.t("api:post:create:request", { title: "Hello" });
const { post } = await postModel.create({ title: "Hello" });
Naite.t("api:post:create:response", { postId: post.id });
// 2. ๋๊ธ ์ถ๊ฐ
Naite.t("api:comment:create:request", { postId: post.id, content: "Nice!" });
const { comment } = await commentModel.create({
post_id: post.id,
content: "Nice!"
});
Naite.t("api:comment:create:response", { commentId: comment.id });
// 3. ์์ฑ์์๊ฒ ์๋ฆผ
Naite.t("api:notification:send:request", {
userId: post.author_id,
type: "new_comment"
});
await notificationService.send({
userId: post.author_id,
type: "new_comment",
data: { commentId: comment.id }
});
Naite.t("api:notification:send:response", { sent: true });
});
request/response ํจํด: API ํธ์ถ ์ ํ๋ก :request์ :response ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๋ฉด, ๊ฐ API์ ์
์ถ๋ ฅ์ ๋ช
ํํ๊ฒ ์ถ์ ํ ์ ์์ต๋๋ค. ์ด๋ ํตํฉ ํ
์คํธ์์ ํนํ ์ ์ฉํฉ๋๋ค.
4. ์ฑ๋ฅ ๋ณ๋ชฉ ์ง์ ํ์
์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ๊ตฌ๊ฐ์ ์ฐพ์ ์ต์ ํํ ์ ์์ต๋๋ค.
test("๋์ฉ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ฑ๋ฅ", async () => {
Naite.t("perf:start", { timestamp: Date.now() });
// ๋จ๊ณ๋ณ ์๊ฐ ์ธก์
Naite.t("perf:fetch:start", { timestamp: Date.now() });
const data = await fetchLargeData();
Naite.t("perf:fetch:done", {
timestamp: Date.now(),
dataSize: data.length
});
Naite.t("perf:process:start", { timestamp: Date.now() });
const processed = await processData(data);
Naite.t("perf:process:done", {
timestamp: Date.now(),
processedCount: processed.length
});
Naite.t("perf:save:start", { timestamp: Date.now() });
await saveToDatabase(processed);
Naite.t("perf:save:done", { timestamp: Date.now() });
Naite.t("perf:end", { timestamp: Date.now() });
});
Viewer์์ ๊ฐ ๋จ๊ณ์ timestamp๋ฅผ ๋น๊ตํ๋ฉด:โ ๋์ฉ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ฑ๋ฅ (5.2s)
perf:start 12:34:56.000
perf:fetch:start 12:34:56.001
perf:fetch:done 12:34:57.500 โ 1.5์ด ์์
perf:process:start 12:34:57.501
perf:process:done 12:35:00.800 โ 3.3์ด ์์ (๋ณ๋ชฉ!)
perf:save:start 12:35:00.801
perf:save:done 12:35:01.200 โ 0.4์ด ์์
perf:end 12:35:01.201
๋ถ์ ๊ฒฐ๊ณผ:
processData๊ฐ 3.3์ด๋ก ๊ฐ์ฅ ์ค๋ ๊ฑธ๋ฆผ (๋ณ๋ชฉ)
fetchLargeData๋ 1.5์ด๋ก ์ํธ
saveToDatabase๋ 0.4์ด๋ก ๋น ๋ฆ
โ processData ์ต์ ํ์ ์ง์คํด์ผ ํจ
5. ์๋ฌ ์์ธ ์ถ์
์๋ฌ ๋ฐ์ ์ ์ด๋ ๋จ๊ณ์์, ์ด๋ค ๋ฐ์ดํฐ๋ก ์๋ฌ๊ฐ ๋ฐ์ํ๋์ง ์ถ์ ํ ์ ์์ต๋๋ค.
test("์๋ชป๋ ์
๋ ฅ๊ฐ ์ฒ๋ฆฌ", async () => {
try {
Naite.t("user:create:input", { username: "" }); // ๋น ๊ฐ
await userModel.create({ username: "", email: "[email protected]" });
} catch (error) {
Naite.t("user:create:error", {
errorType: error.constructor.name,
errorMessage: error.message,
inputData: { username: "" }
});
// ์๋ฌ ๋ก๊ทธ์ ์ฝ์คํ์ ๋ณด๋ฉด ์ ํํ ์คํจ ์์น๋ฅผ ์ ์ ์์
}
});
์๋ฌ ์ ์ฝ์คํ์ ์ค์์ฑ: Viewer์ ์ฝ์คํ ์ ๋ณด๋ ์๋ฌ๊ฐ ๋ฐ์ํ ์ ํํ ์ฝ๋ ๋ผ์ธ์ ๋ณด์ฌ์ค๋๋ค. ์ด๋ console.log๋ก๋ ํ์
ํ๊ธฐ ์ด๋ ค์ด ๋ณต์กํ ํธ์ถ ์ฒด์ธ์์ ํนํ ์ ์ฉํฉ๋๋ค.
๋ด๋ถ ๊ตฌ์กฐ ๊น์ด ์ดํดํ๊ธฐ
NaiteReporter์ ์ฐ๊ฒฐ ๊ด๋ฆฌ
NaiteReporter๋ ์์ผ ์ฐ๊ฒฐ์ ์์ ์ ์ผ๋ก ๊ด๋ฆฌํ๊ธฐ ์ํด ๋ฒํผ๋ง๊ณผ ์ฌ์ฐ๊ฒฐ ๋ก์ง์ ํฌํจํฉ๋๋ค.
NaiteReporter ์ ์ฒด ๊ตฌ์กฐ
class NaiteReporterClass {
private socketPath: string | null = null;
private socket: Socket | null = null;
private connected = false;
private buffer: string[] = [];
/**
* ์์ผ ์ฐ๊ฒฐ ํ๋ณด
* - ์ด๋ฏธ ์ฐ๊ฒฐ๋์ด ์์ผ๋ฉด ์ฆ์ ๋ฐํ
* - ์ฐ๊ฒฐ ์ค์ด๋ฉด ๋ฒํผ์ ๋ฉ์์ง ์ ์ฅ
* - ์ฐ๊ฒฐ ์คํจ๋ ๋ฌด์ (Extension์ด ๊บผ์ ธ์์ ์ ์์)
*/
private async ensureConnection(): Promise<void> {
if (this.connected) return;
return new Promise((resolve, reject) => {
this.socketPath = getSocketPath();
this.socket = connect(this.socketPath);
this.socket.on("connect", () => {
this.connected = true;
// ๋ฒํผ์ ์์ธ ๋ฉ์์ง ์ ์ก
for (const msg of this.buffer) {
this.socket?.write(msg);
}
this.buffer = [];
resolve();
});
this.socket.on("error", () => {
// Extension์ด ๊บผ์ ธ์์ ์ ์์ผ๋ฏ๋ก ์๋ฌ ๋ฌด์
this.connected = false;
this.socket = null;
reject();
});
this.socket.on("close", () => {
this.connected = false;
this.socket = null;
});
});
}
/**
* ๋ฉ์์ง ์ ์ก
* - ์ฐ๊ฒฐ๋์ด ์์ผ๋ฉด ์ฆ์ ์ ์ก
* - ์ฐ๊ฒฐ ์ค์ด๋ฉด ๋ฒํผ์ ์ ์ฅ
*/
private async send(data: NaiteMessage): Promise<void> {
const msg = `${JSON.stringify(data)}\n`;
await this.ensureConnection().catch(() => {});
if (this.connected && this.socket) {
this.socket.write(msg);
} else {
this.buffer.push(msg);
}
}
async startTestRun() {
if (process.env.CI) return; // CI์์๋ ๋ฌด์
await this.send({
type: "run/start",
startedAt: new Date().toISOString(),
});
}
async reportTestResult(result: TestResult) {
if (process.env.CI) return;
await this.send({
type: "test/result",
receivedAt: new Date().toISOString(),
...result,
});
}
async endTestRun() {
if (process.env.CI) return;
await this.send({
type: "run/end",
endedAt: new Date().toISOString(),
});
// ์์ผ ์ข
๋ฃ
if (this.socket) {
this.socket.end();
this.socket = null;
this.connected = false;
}
}
}
ํต์ฌ ๋ฉ์ปค๋์ฆ:
- ๋ฒํผ๋ง: Extension์ด ์์ง ์ค๋น๋์ง ์์์ด๋ ๋ฉ์์ง๋ฅผ ๋ฒํผ์ ์ ์ฅ
- ๋๊ธํ ์ฐ๊ฒฐ: ์ฐ๊ฒฐ ์คํจ๋ฅผ ์๋ฌ๋ก ์ฒ๋ฆฌํ์ง ์์
- ์๋ ์ฌ์ ์ก: ์ฐ๊ฒฐ ์ฑ๊ณต ์ ๋ฒํผ์ ๋ชจ๋ ๋ฉ์์ง๋ฅผ ์ ์ก
- CI ๊ฐ์ง: CI ํ๊ฒฝ์์๋ ์์ผ ํต์ ์ ๊ฑด๋๋
ํ๋ก์ ํธ๋ณ ์์ผ ๊ฒฉ๋ฆฌ
์ฌ๋ฌ Sonamu ํ๋ก์ ํธ๋ฅผ ๋์์ ์์
ํ ๋ ๋ก๊ทธ๊ฐ ์์ด์ง ์๋๋ก ๊ฒฉ๋ฆฌํฉ๋๋ค.
ํด์ ์์ฑ
์์ผ ๊ฒฝ๋ก
๊ฒฐ๊ณผ
import { createHash } from "crypto";
import { join } from "path";
function getProjectHash(configPath: string): string {
return createHash("md5")
.update(configPath)
.digest("hex")
.slice(0, 8);
}
// ์์
const projectA = "/Users/noa/project-a/api/src/sonamu.config.ts";
const hashA = getProjectHash(projectA);
// โ "a1b2c3d4"
const projectB = "/Users/noa/project-b/api/src/sonamu.config.ts";
const hashB = getProjectHash(projectB);
// โ "e5f6g7h8"
function getSocketPath(): string {
const configPath = join(
findApiRootPath(),
"src",
"sonamu.config.ts"
);
const hash = getProjectHash(configPath);
return process.platform === "win32"
? `\\\\.\\pipe\\naite-${hash}`
: join(homedir(), ".sonamu", `naite-${hash}.sock`);
}
# Project A
/Users/noa/project-a/api/src/sonamu.config.ts
โ ~/.sonamu/naite-a1b2c3d4.sock
# Project B
/Users/noa/project-b/api/src/sonamu.config.ts
โ ~/.sonamu/naite-e5f6g7h8.sock
# ๊ฐ๊ฐ ๋
๋ฆฝ๋ ์์ผ ์ฌ์ฉ
# Extension๋ ํ๋ก์ ํธ๋ณ๋ก ๋ค๋ฅธ ์์ผ์ ์ฐ๊ฒฐ
์ฅ์ :
- ํ๋ก์ ํธ ๊ฐ ๋ก๊ทธ ์์ ๊ฒฉ๋ฆฌ
- ๋์ ์คํ ์ง์ (A ํ
์คํธ ์ค B ํ
์คํธ ๊ฐ๋ฅ)
- ์ถฉ๋ ์๋ ์์ ์ ์ด์
bootstrap.ts์์ ํตํฉ
Sonamu์ ํ
์คํธ bootstrap์ ๊ฐ ํ
์คํธ๋ง๋ค Naite.getAllTraces()๋ฅผ ํธ์ถํ์ฌ ๋ก๊ทธ๋ฅผ ์์งํฉ๋๋ค.
bootstrap.ts์ Naite ํตํฉ
export const test = Object.assign(
async (title: string, fn: TestFunction<object>, options?: TestOptions) => {
return vitestTest(title, options, async (context) => {
await runWithMockContext(async () => {
try {
// ํ
์คํธ ์คํ
await fn(context);
// ์ฑ๊ณต ์์๋ traces ์์ง
context.task.meta.traces = Naite.getAllTraces();
} catch (e: unknown) {
// ์คํจ ์์๋ traces ์์ง (์๋ฌ ์ถ์ ์ฉ)
context.task.meta.traces = Naite.getAllTraces();
throw e;
}
});
});
},
// ... skip, only, todo ๋ฑ๋ ๋์ผํ๊ฒ ์ฒ๋ฆฌ
);
// afterEach์์ Extension์ผ๋ก ์ ์ก
afterEach(async ({ task }) => {
await NaiteReporter.reportTestResult({
suiteName: task.suite?.name ?? "(no suite)",
suiteFilePath: task.file?.filepath,
testName: task.name,
testFilePath: task.file?.filepath ?? "",
testLine: task.location?.line ?? 0,
status: task.result?.state ?? "pass",
duration: task.result?.duration ?? 0,
error: task.result?.errors?.[0]
? {
message: task.result.errors[0].message,
stack: task.result.errors[0].stack,
}
: undefined,
traces: task.meta?.traces ?? [],
});
});
ํต์ฌ ํฌ์ธํธ:
- test() ๋ํผ: Vitest์
test()๋ฅผ ๋ํํ์ฌ ์๋์ผ๋ก traces ์์ง
- try-catch ์์ชฝ: ์ฑ๊ณต/์คํจ ๋ชจ๋ traces๋ฅผ ์์งํ์ฌ ์๋ฌ ๋๋ฒ๊น
์ง์
- task.meta ํ์ฉ: Vitest์ ๋ฉํ๋ฐ์ดํฐ ์์คํ
์ ํ์ฉํ์ฌ traces ์ ๋ฌ
- afterEach ์ ์ก: ๊ฐ ํ
์คํธ ์ข
๋ฃ ํ Reporter๋ก ์ ์ก
๋ฌธ์ ํด๊ฒฐ
Extension์ด ๋ก๊ทธ๋ฅผ ๋ฐ์ง ๋ชปํจ
Extension์ด ์คํ๋์ง ์์
์ฆ์:
- VSCode ํ๋จ ์ํ๋ฐ์ Sonamu ์์ด์ฝ์ด ์์
- Naite Viewer ํจ๋์ ์ด ์ ์์
ํด๊ฒฐ:
- Extensions ํญ์์ Sonamu Extension ํ์ธ
- โEnableโ ๋๋ โReloadโ ๋ฒํผ ํด๋ฆญ
- VSCode ์ฌ์์:
Cmd+Shift+P โ โReload Windowโ
์ฆ์:
- Extension์ ์คํ ์ค
- ํ
์คํธ๋ ์ฑ๊ณตํ์ง๋ง ๋ก๊ทธ๊ฐ ์ ๋ณด์
- ์ฝ์์ โENOENTโ ๋๋ โECONNREFUSEDโ ์๋ฌ
์์ธ:
ํ๋ก์ ํธ ํด๋๊ฐ ์๋ ์์ ํด๋๋ฅผ ์ด์ด ์์ผ ๊ฒฝ๋ก๊ฐ ๋ฌ๋ผ์งํด๊ฒฐ:
- VSCode์์ ํ๋ก์ ํธ ํด๋ ์ง์ ์ด๊ธฐ
- Extension ์ฌ์์
# โ ์๋ชป๋ ๋ฐฉ๋ฒ
code ~/projects # ์์ ํด๋ ์ด๊ธฐ
# โ
์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ
code ~/projects/my-sonamu-app # ํ๋ก์ ํธ ํด๋ ์ง์ ์ด๊ธฐ
์์ผ ํ์ผ ๊ถํ ๋ฌธ์
์ฆ์:
- โPermission deniedโ ์๋ฌ
- ์์ผ ํ์ผ์ด ์กด์ฌํ์ง๋ง ์ ๊ทผ ๋ถ๊ฐ
ํด๊ฒฐ:# ์์ผ ํ์ผ ํ์ธ
ls -la ~/.sonamu/
# ๋ฌธ์ ๊ฐ ์์ผ๋ฉด ์ญ์
rm ~/.sonamu/naite-*.sock
# Extension๊ณผ ํ
์คํธ ์ฌ์์
๋ก๊ทธ๊ฐ ๋๋ฌด ๋ง์ ๋๋ฆผ
๋ก๊น
์ ๋ต: Naite๋ ์ฑ๋ฅ์ ์ํด ์ค๊ณ๋์์ง๋ง, ๊ณผ๋ํ ๋ก๊น
์ ํผํด์ผ ํฉ๋๋ค.
๋์ ์
์ข์ ์
์กฐ๊ฑด๋ถ ๋ก๊น
// โ ๋ฃจํ ์์์ ๋ก๊น
for (let i = 0; i < 10000; i++) {
Naite.t("loop:iteration", { i });
// 10,000๊ฐ์ trace ์์ฑ!
}
// โ ๋ชจ๋ ์ค๊ฐ ๋จ๊ณ ๋ก๊น
function processData(data: any[]) {
for (const item of data) {
Naite.t("process:step1", item);
Naite.t("process:step2", item);
Naite.t("process:step3", item);
// ๋๋ฌด ์์ธํจ
}
}
// โ
์์ฝ ์ ๋ณด๋ง
Naite.t("loop:start", { count: 10000 });
for (let i = 0; i < 10000; i++) {
// ๋ก๊น
์์
}
Naite.t("loop:done", { count: 10000, duration: 123 });
// โ
์๋ฏธ ์๋ ๊ตฌ๊ฐ๋ง
function processData(data: any[]) {
Naite.t("process:start", { count: data.length });
const results = data.map(processItem);
Naite.t("process:done", {
successCount: results.filter(r => r.success).length,
failureCount: results.filter(r => !r.success).length
});
return results;
}
// โ
๋๋ฒ๊น
๋ชจ๋์์๋ง
const DEBUG_NAITE = process.env.DEBUG_NAITE === "true";
function processItem(item: any) {
if (DEBUG_NAITE) {
Naite.t("process:detail", item);
}
// ์ฒ๋ฆฌ ๋ก์ง
}
// ์ฌ์ฉ: DEBUG_NAITE=true pnpm test
CI ํ๊ฒฝ์์ ์์ผ ์ฐ๊ฒฐ ์๋ฌ
์๋ ๋นํ์ฑํ: NaiteReporter๋ CI ํ๊ฒฝ์ ์๋ ๊ฐ์งํ์ฌ ์์ผ ํต์ ์ ๊ฑด๋๋๋๋ค.
// NaiteReporter ๋ด๋ถ
async startTestRun() {
if (process.env.CI) {
return; // CI์์๋ ์๋ฌด๊ฒ๋ ํ์ง ์์
}
await this.send({ type: "run/start" });
}
CI ๊ฐ์ง ์กฐ๊ฑด:
process.env.CI === "true"
- GitHub Actions, GitLab CI, CircleCI ๋ฑ์์ ์๋ ์ค์ ๋จ
๋ง์ฝ ๋ก์ปฌ์์ CI ๋ชจ๋๋ฅผ ํ
์คํธํ๋ ค๋ฉด:
Watch ๋ชจ๋์์ ๋ก๊ทธ๊ฐ ์์
Watch ๋ชจ๋์์ ํ์ผ์ ์ฌ๋ฌ ๋ฒ ์์ ํ๋ฉด ์ด์ ํ
์คํธ ๋ก๊ทธ๊ฐ ๋จ์์์ ์ ์์ต๋๋ค.
์๋ ํด๋ฆฌ์ด: run/start ๋ฉ์์ง๊ฐ ์ ์ก๋๋ฉด Extension์ด ์๋์ผ๋ก ์ด์ ๋ก๊ทธ๋ฅผ ํด๋ฆฌ์ดํฉ๋๋ค. ํ์ง๋ง ์ฌ๋ฌ ํ๋ก์ ํธ๋ฅผ ๋์์ ์์
ํ๋ ๊ฒฝ์ฐ ์๋ ํด๋ฆฌ์ด๊ฐ ํ์ํ ์ ์์ต๋๋ค.
์๋ ํด๋ฆฌ์ด ๋ฐฉ๋ฒ:
- Naite Viewer ํจ๋ ์๋จ์ โClear Allโ ๋ฒํผ ํด๋ฆญ
- ๋๋ Command Palette โ โNaite: Clear Logsโ
์ฃผ์์ฌํญ
Naite Viewer ์ฌ์ฉ ์ ์ฃผ์์ฌํญ:
-
Extension ํ์: VSCode Extension์ด ์คํ ์ค์ด์ด์ผ ํฉ๋๋ค. Extension ์์ด ํ
์คํธ๋ง ์คํํ๋ฉด ๋ก๊ทธ๊ฐ ๋ฒํผ์ ์์๋ค๊ฐ ์ฌ๋ผ์ง๋๋ค.
-
๋ก์ปฌ ์ ์ฉ: ์๊ฒฉ ์๋ฒ(SSH, Codespaces ๋ฑ)์์๋ ์์ผ ํต์ ์ด ์ ํ๋ ์ ์์ต๋๋ค. ๋ก์ปฌ ๊ฐ๋ฐ ํ๊ฒฝ์์ ์ฌ์ฉํ์ธ์.
-
CI ์๋ ๋นํ์ฑํ: CI ํ๊ฒฝ(
process.env.CI)์์๋ ์์ผ ํต์ ์ด ์๋์ผ๋ก ๋นํ์ฑํ๋ฉ๋๋ค. ์๋ฌ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.
-
ํ๋ก์ ํธ๋ณ ๋
๋ฆฝ: ๊ฐ ํ๋ก์ ํธ๋ ๋
๋ฆฝ๋ ์์ผ์ ์ฌ์ฉํฉ๋๋ค. ์ฌ๋ฌ ํ๋ก์ ํธ๋ฅผ ๋์์ ์์
ํ ๋ ์ฌ๋ฐ๋ฅธ Viewer ์ฐฝ์ ํ์ธํ์ธ์.
-
์ง๋ ฌํ ํ์: Extension์ผ๋ก ์ ์ก๋๋ ๊ฐ์ JSON์ผ๋ก ์ง๋ ฌํ๋ฉ๋๋ค.
Naite.t()์ ํจ์๋ ์ํ ์ฐธ์กฐ ๊ฐ์ฒด๋ฅผ ์ ๋ฌํ๋ฉด ๊ฒฝ๊ณ ๊ฐ ํ์๋ฉ๋๋ค.
-
๊ณผ๋ํ ๋ก๊น
์ง์: ๋ฃจํ ์์์
Naite.t()๋ฅผ ํธ์ถํ๋ฉด ์์ฒ ๊ฐ์ ๋ก๊ทธ๊ฐ ์์ฑ๋์ด ์ฑ๋ฅ์ด ์ ํ๋ ์ ์์ต๋๋ค.
๋ค์ ๋จ๊ณ