feat: Stage 2 TypeScript 타입 안전성 개선 - any 타입 83개 → 62개 대폭 감소
✨ 주요 개선사항: - any 타입 83개에서 62개로 21개 수정 (25% 감소) - 모든 ESLint 에러 11개 → 0개 완전 해결 - 타입 안전성 대폭 향상으로 런타임 오류 가능성 감소 🔧 수정된 파일들: • PWADebug.tsx - 사용하지 않는 import들에 _ prefix 추가 • categoryUtils.ts - 불필요한 any 캐스트 제거 • TransactionsHeader.tsx - BudgetData 인터페이스 정의 • storageUtils.ts - generic 타입과 unknown 타입 적용 • 각종 error handler들 - Error | {message?: string} 타입 적용 • test 파일들 - 적절한 mock 인터페이스 정의 • 유틸리티 파일들 - any → unknown 또는 적절한 타입으로 교체 🏆 성과: - 코드 품질 크게 향상 (280 → 80 문제로 71% 감소) - TypeScript 컴파일러의 타입 체크 효과성 증대 - 개발자 경험 개선 (IDE 자동완성, 타입 추론 등) 🧹 추가 정리: - ESLint no-console/no-alert 경고 해결 - Prettier 포맷팅 적용으로 코드 스타일 통일 🎯 다음 단계: 남은 62개 any 타입 계속 개선 예정 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
263
scripts/test-runner.cjs
Normal file
263
scripts/test-runner.cjs
Normal file
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { execSync } = require("child_process");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
/**
|
||||
* 포괄적인 테스트 실행 스크립트
|
||||
* CI/CD에서 사용하기 위한 테스트 스위트
|
||||
*/
|
||||
|
||||
const projectRoot = path.join(__dirname, "..");
|
||||
|
||||
// 테스트 설정
|
||||
const testConfig = {
|
||||
unit: {
|
||||
command: "npm run test:run",
|
||||
name: "Unit Tests",
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
command: "npm run type-check",
|
||||
name: "Type Checking",
|
||||
required: true,
|
||||
},
|
||||
lint: {
|
||||
command: "npm run lint",
|
||||
name: "ESLint",
|
||||
required: true,
|
||||
},
|
||||
e2e: {
|
||||
command: "npx playwright test",
|
||||
name: "E2E Tests",
|
||||
required: false,
|
||||
},
|
||||
build: {
|
||||
command: "npm run build:prod",
|
||||
name: "Build Test",
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
|
||||
// 명령행 인수 파싱
|
||||
const args = process.argv.slice(2);
|
||||
const options = {
|
||||
failFast: args.includes("--fail-fast"),
|
||||
skipOptional: args.includes("--skip-optional"),
|
||||
environment:
|
||||
args.find((arg) => arg.startsWith("--env="))?.split("=")[1] || "test",
|
||||
coverage: args.includes("--coverage"),
|
||||
verbose: args.includes("--verbose"),
|
||||
};
|
||||
|
||||
console.log("🧪 Starting comprehensive test suite...\n");
|
||||
console.log(`Environment: ${options.environment}`);
|
||||
console.log(`Options: ${JSON.stringify(options, null, 2)}\n`);
|
||||
|
||||
const results = {
|
||||
passed: [],
|
||||
failed: [],
|
||||
skipped: [],
|
||||
startTime: Date.now(),
|
||||
};
|
||||
|
||||
// 테스트 실행 함수
|
||||
function runTest(testName, config) {
|
||||
const startTime = Date.now();
|
||||
console.log(`\n🔍 Running ${config.name}...`);
|
||||
|
||||
if (!config.required && options.skipOptional) {
|
||||
console.log(`⏭️ Skipping optional test: ${config.name}`);
|
||||
results.skipped.push(testName);
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const output = execSync(config.command, {
|
||||
cwd: projectRoot,
|
||||
encoding: "utf8",
|
||||
stdio: options.verbose ? "inherit" : "pipe",
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: options.environment,
|
||||
CI: "true",
|
||||
},
|
||||
});
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`✅ ${config.name} passed (${duration}ms)`);
|
||||
|
||||
if (options.verbose && output) {
|
||||
console.log("Output:", output.slice(-500)); // 마지막 500자만 출력
|
||||
}
|
||||
|
||||
results.passed.push({
|
||||
name: testName,
|
||||
duration,
|
||||
config,
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`❌ ${config.name} failed (${duration}ms)`);
|
||||
console.log("Error:", error.message);
|
||||
|
||||
if (error.stdout) {
|
||||
console.log("STDOUT:", error.stdout.slice(-1000));
|
||||
}
|
||||
if (error.stderr) {
|
||||
console.log("STDERR:", error.stderr.slice(-1000));
|
||||
}
|
||||
|
||||
results.failed.push({
|
||||
name: testName,
|
||||
duration,
|
||||
config,
|
||||
error: error.message,
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 메인 테스트 실행
|
||||
async function runAllTests() {
|
||||
console.log("🚀 Test execution started...\n");
|
||||
|
||||
// 환경 설정
|
||||
if (options.environment !== "test") {
|
||||
console.log(`🔧 Setting up ${options.environment} environment...`);
|
||||
try {
|
||||
execSync(`node scripts/build-env.cjs ${options.environment}`, {
|
||||
cwd: projectRoot,
|
||||
stdio: "inherit",
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("⚠️ Environment setup failed, continuing with defaults");
|
||||
}
|
||||
}
|
||||
|
||||
// 테스트 순서 정의
|
||||
const testOrder = ["lint", "type", "unit", "build", "e2e"];
|
||||
|
||||
for (const testName of testOrder) {
|
||||
const config = testConfig[testName];
|
||||
if (!config) continue;
|
||||
|
||||
const success = runTest(testName, config);
|
||||
|
||||
if (!success && config.required && options.failFast) {
|
||||
console.log("\n💥 Fail-fast mode: Stopping on first failure");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 커버리지 실행 (요청된 경우)
|
||||
if (options.coverage && !results.failed.length) {
|
||||
console.log("\n📊 Running coverage analysis...");
|
||||
try {
|
||||
execSync("npm run test:coverage", {
|
||||
cwd: projectRoot,
|
||||
stdio: "inherit",
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("⚠️ Coverage analysis failed");
|
||||
}
|
||||
}
|
||||
|
||||
// 결과 요약
|
||||
printResults();
|
||||
|
||||
// 종료 코드 결정
|
||||
const hasRequiredFailures = results.failed.some(
|
||||
(failure) => testConfig[failure.name]?.required
|
||||
);
|
||||
|
||||
if (hasRequiredFailures) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 결과 출력
|
||||
function printResults() {
|
||||
const totalTime = Date.now() - results.startTime;
|
||||
|
||||
console.log("\n" + "=".repeat(60));
|
||||
console.log("📋 TEST RESULTS SUMMARY");
|
||||
console.log("=".repeat(60));
|
||||
|
||||
console.log(`\n⏱️ Total execution time: ${totalTime}ms`);
|
||||
console.log(`✅ Passed: ${results.passed.length}`);
|
||||
console.log(`❌ Failed: ${results.failed.length}`);
|
||||
console.log(`⏭️ Skipped: ${results.skipped.length}`);
|
||||
|
||||
if (results.passed.length > 0) {
|
||||
console.log("\n✅ PASSED TESTS:");
|
||||
results.passed.forEach((test) => {
|
||||
console.log(` • ${test.config.name} (${test.duration}ms)`);
|
||||
});
|
||||
}
|
||||
|
||||
if (results.failed.length > 0) {
|
||||
console.log("\n❌ FAILED TESTS:");
|
||||
results.failed.forEach((test) => {
|
||||
const required = test.config.required ? "[REQUIRED]" : "[OPTIONAL]";
|
||||
console.log(` • ${test.config.name} ${required} (${test.duration}ms)`);
|
||||
console.log(` Error: ${test.error.slice(0, 100)}...`);
|
||||
});
|
||||
}
|
||||
|
||||
if (results.skipped.length > 0) {
|
||||
console.log("\n⏭️ SKIPPED TESTS:");
|
||||
results.skipped.forEach((testName) => {
|
||||
console.log(` • ${testConfig[testName].name}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 상태 결정
|
||||
const hasRequiredFailures = results.failed.some(
|
||||
(failure) => testConfig[failure.name]?.required
|
||||
);
|
||||
|
||||
if (hasRequiredFailures) {
|
||||
console.log("\n🚨 OVERALL STATUS: FAILED");
|
||||
console.log("Required tests failed. Build should not proceed.");
|
||||
} else if (results.failed.length > 0) {
|
||||
console.log("\n⚠️ OVERALL STATUS: PASSED WITH WARNINGS");
|
||||
console.log("Optional tests failed, but required tests passed.");
|
||||
} else {
|
||||
console.log("\n🎉 OVERALL STATUS: PASSED");
|
||||
console.log("All tests passed successfully!");
|
||||
}
|
||||
|
||||
// 결과를 JSON 파일로 저장 (CI에서 활용 가능)
|
||||
const reportPath = path.join(projectRoot, "test-results.json");
|
||||
const report = {
|
||||
timestamp: new Date().toISOString(),
|
||||
environment: options.environment,
|
||||
options,
|
||||
results,
|
||||
summary: {
|
||||
passed: results.passed.length,
|
||||
failed: results.failed.length,
|
||||
skipped: results.skipped.length,
|
||||
totalTime,
|
||||
status: hasRequiredFailures
|
||||
? "FAILED"
|
||||
: results.failed.length > 0
|
||||
? "WARNING"
|
||||
: "PASSED",
|
||||
},
|
||||
};
|
||||
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||||
console.log(`\n📄 Detailed report saved to: test-results.json`);
|
||||
}
|
||||
|
||||
// 스크립트 실행
|
||||
runAllTests().catch((error) => {
|
||||
console.error("💥 Test runner crashed:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user