Browse Source

♻️ refactor(tests): 重构测试文件结构到统一的tests目录

- 移动单元测试到tests/unit目录,集成测试到tests/integration目录
- 更新测试文件导入路径以适应新目录结构
- 修改vitest配置以匹配新的测试文件位置

📝 docs(tests): 更新测试策略文档

- 更新测试文件位置说明,反映新的目录结构
- 添加单元测试命令文档说明
- 更新文档版本到2.5并添加变更记录

✅ test(config): 添加单元测试命令到配置文件

- 在package.json中添加test:unit命令
- 为pre-commit钩子添加pnpm test:unit:*命令
- 优化测试脚本分类,提高测试执行清晰度
yourname 1 month ago
parent
commit
c6c34f49c9

+ 2 - 1
.claude/settings.local.json

@@ -31,7 +31,8 @@
       "Bash(do sed -i '8d' \"$file\")",
       "Bash(pnpm typecheck)",
       "Bash(pnpm test:api:*)",
-      "Bash(pnpm test:components:*)"
+      "Bash(pnpm test:components:*)",
+      "Bash(pnpm test:unit:*)"
     ],
     "deny": [],
     "ask": []

+ 7 - 8
docs/architecture/testing-strategy.md

@@ -3,6 +3,7 @@
 ## 版本信息
 | 版本 | 日期 | 描述 | 作者 |
 |------|------|------|------|
+| 2.5 | 2025-10-14 | 更新测试文件位置到统一的tests目录结构 | Claude |
 | 2.4 | 2025-09-20 | 更新测试策略与主架构文档版本一致 | Winston |
 
 ## 概述
@@ -14,7 +15,7 @@
 ### 单元测试 (Unit Tests)
 - **范围**: 单个函数、类或组件
 - **目标**: 验证独立单元的correctness
-- **位置**: `src/**/__tests__/**/*.test.{ts,tsx}`
+- **位置**: `tests/unit/**/*.test.{ts,tsx}`
 - **框架**: Vitest
 - **覆盖率目标**: ≥ 80%
 - **执行频率**: 每次代码变更
@@ -22,7 +23,7 @@
 ### 集成测试 (Integration Tests)
 - **范围**: 多个组件/服务协作
 - **目标**: 验证模块间集成和交互
-- **位置**: `src/**/__integration_tests__/**/*.integration.test.{ts,tsx}`
+- **位置**: `tests/integration/**/*.test.{ts,tsx}`
 - **框架**: Vitest + Testing Library + hono/testing
 - **覆盖率目标**: ≥ 60%
 - **执行频率**: 每次API变更
@@ -139,11 +140,8 @@ const inactiveUser = createTestUser({ active: false });
 # 运行所有测试
 npm test
 
-# 运行API测试
-npm run test:api
-
-# 运行组件测试
-npm run test:components
+# 运行单元测试
+npm run test:unit
 
 # 运行集成测试
 npm run test:integration
@@ -274,8 +272,9 @@ describe('UserService', () => {
 ### 更新日志
 | 日期 | 版本 | 描述 |
 |------|------|------|
-| 2025-09-19 | 1.0 | 初始版本,基于现有测试基础设施 |
+| 2025-10-14 | 2.5 | 重构测试文件结构,统一到tests目录 |
 | 2025-09-20 | 2.4 | 更新版本与主架构文档一致 |
+| 2025-09-19 | 1.0 | 初始版本,基于现有测试基础设施 |
 
 ---
 

+ 3 - 0
package.json

@@ -16,6 +16,9 @@
     "test:integration": "vitest run --project=happy-dom",
     "test:api:coverage": "vitest run --coverage --project=node",
     "test:components:coverage": "vitest run --coverage --project=happy-dom",
+    "test:unit": "vitest run tests/unit",
+    "test:integration:server": "vitest run tests/integration/server",
+    "test:integration:client": "vitest run tests/integration/client",
     "test:e2e": "playwright test --config=tests/e2e/playwright.config.ts",
     "test:e2e:ui": "playwright test --config=tests/e2e/playwright.config.ts --ui",
     "test:e2e:debug": "playwright test --config=tests/e2e/playwright.config.ts --debug",

+ 1 - 1
src/client/__integration_tests__/admin/dashboard.test.tsx → tests/integration/client/admin/dashboard.test.tsx

@@ -1,7 +1,7 @@
 import { describe, it, expect, vi, beforeEach } from 'vitest';
 import { render, screen } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
-import { DashboardPage } from '@/client/admin/pages/Dashboard';
+import { DashboardPage } from '../../../../src/client/admin/pages/Dashboard';
 
 // Mock 导航功能
 const mockNavigate = vi.fn();

+ 3 - 3
src/client/__integration_tests__/admin/login.test.tsx → tests/integration/client/admin/login.test.tsx

@@ -2,14 +2,14 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
 import { render, screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import '@testing-library/jest-dom';
-import { LoginPage } from '@/client/admin/pages/Login';
-import { TestWrapper } from '@/client/__test_utils__/test-render';
+import { LoginPage } from '../../../../src/client/admin/pages/Login';
+import { TestWrapper } from '../../../../src/client/__test_utils__/test-render';
 
 // Mock useAuth钩子
 const mockLogin = vi.fn();
 const mockNavigate = vi.fn();
 
-vi.mock('@/client/admin/hooks/AuthProvider', () => ({
+vi.mock('../../../../src/client/admin/hooks/AuthProvider', () => ({
   useAuth: () => ({
     login: mockLogin,
     user: null,

+ 4 - 4
src/client/__integration_tests__/admin/users.test.tsx → tests/integration/client/admin/users.test.tsx

@@ -1,15 +1,15 @@
 import { describe, it, expect, vi, beforeEach } from 'vitest';
 import { render, screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
-import { UsersPage } from '@/client/admin/pages/Users';
-import { TestWrapper } from '@/client/__test_utils__/test-render';
+import { UsersPage } from '../../../../src/client/admin/pages/Users';
+import { TestWrapper } from '../../../../src/client/__test_utils__/test-render';
 
 // Import mocked modules
-import { userClient } from '@/client/api';
+import { userClient } from '../../../../src/client/api';
 import { toast } from 'sonner';
 
 // Mock API 客户端
-vi.mock('@/client/api', () => ({
+vi.mock('../../../../src/client/api', () => ({
   userClient: {
     $get: vi.fn().mockResolvedValue({
       status: 200,

+ 6 - 6
src/server/api/auth/__tests__/auth.integration.test.ts → tests/integration/server/auth.integration.test.ts

@@ -4,12 +4,12 @@ import {
   IntegrationTestDatabase,
   setupIntegrationDatabaseHooks,
   TestDataFactory
-} from '../../../__test_utils__/integration-test-db';
-import { UserEntity } from '../../../modules/users/user.entity';
-import { authRoutes } from '../../../api';
-import { AuthService } from '../../../modules/auth/auth.service';
-import { UserService } from '../../../modules/users/user.service';
-import { DisabledStatus } from '@/share/types';
+} from '../../../src/server/__test_utils__/integration-test-db';
+import { UserEntity } from '../../../src/server/modules/users/user.entity';
+import { authRoutes } from '../../../src/server/api';
+import { AuthService } from '../../../src/server/modules/auth/auth.service';
+import { UserService } from '../../../src/server/modules/users/user.service';
+import { DisabledStatus } from '../../../src/share/types';
 
 // 设置集成测试钩子
 setupIntegrationDatabaseHooks()

+ 3 - 3
src/server/utils/__integration_tests__/backup.integration.test.ts → tests/integration/server/backup.integration.test.ts

@@ -1,6 +1,6 @@
 import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
-import { databaseBackup } from '../backup'
-import { databaseRestore } from '../restore'
+import { databaseBackup } from '../../../src/server/utils/backup'
+import { databaseRestore } from '../../../src/server/utils/restore'
 import path from 'path'
 
 // Mock pg-dump-restore for integration tests
@@ -43,7 +43,7 @@ vi.mock('node-cron', async (importOriginal) => {
 })
 
 // Mock logger
-vi.mock('../logger', () => ({
+vi.mock('../../../src/server/utils/logger', () => ({
   logger: {
     db: vi.fn(),
     error: vi.fn(),

+ 5 - 5
src/server/api/users/__tests__/users.integration.test.ts → tests/integration/server/users.integration.test.ts

@@ -4,11 +4,11 @@ import {
   IntegrationTestDatabase,
   setupIntegrationDatabaseHooks,
   TestDataFactory
-} from '../../../__test_utils__/integration-test-db';
-import { IntegrationTestAssertions } from '../../../__test_utils__/integration-test-utils';
-import { userRoutes } from '../../../api';
-import { AuthService } from '../../../modules/auth/auth.service';
-import { UserService } from '../../../modules/users/user.service';
+} from '../../../src/server/__test_utils__/integration-test-db';
+import { IntegrationTestAssertions } from '../../../src/server/__test_utils__/integration-test-utils';
+import { userRoutes } from '../../../src/server/api';
+import { AuthService } from '../../../src/server/modules/auth/auth.service';
+import { UserService } from '../../../src/server/modules/users/user.service';
 
 
 // 设置集成测试钩子

+ 4 - 4
src/client/admin/pages/__tests__/Users.test.tsx → tests/unit/client/pages/Users.test.tsx

@@ -1,12 +1,12 @@
 import { describe, it, expect, vi, beforeEach } from 'vitest';
 import { render, screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
-import { TestWrapper } from '@/client/__test_utils__/test-render';
-import { UsersPage } from '../Users';
-import { userClient } from '@/client/api';
+import { TestWrapper } from '../../../../src/client/__test_utils__/test-render';
+import { UsersPage } from '../../../../src/client/admin/pages/Users';
+import { userClient } from '../../../../src/client/api';
 
 // Mock the API client
-vi.mock('@/client/api', () => ({
+vi.mock('../../../../src/client/api', () => ({
   userClient: {
     $get: vi.fn(),
     $post: vi.fn(),

+ 4 - 4
src/client/admin/pages/__tests__/debug.test.tsx → tests/unit/client/pages/debug.test.tsx

@@ -1,11 +1,11 @@
 import { describe, it, expect, vi } from 'vitest';
 import { render, screen } from '@testing-library/react';
-import { TestWrapper } from '@/client/__test_utils__/test-render';
-import { UsersPage } from '../Users';
-import { userClient } from '@/client/api';
+import { TestWrapper } from '../../../../src/client/__test_utils__/test-render';
+import { UsersPage } from '../../../../src/client/admin/pages/Users';
+import { userClient } from '../../../../src/client/api';
 
 // Mock the API client
-vi.mock('@/client/api', () => ({
+vi.mock('../../../../src/client/api', () => ({
   userClient: {
     $get: vi.fn(),
     $post: vi.fn(),

+ 2 - 2
src/server/modules/users/__tests__/user.service.test.ts → tests/unit/server/modules/user.service.test.ts

@@ -1,5 +1,5 @@
-import { UserService } from '../user.service';
-import { UserEntity as User } from '../user.entity';
+import { UserService } from '../../../../src/server/modules/users/user.service';
+import { UserEntity as User } from '../../../../src/server/modules/users/user.entity';
 import * as bcrypt from 'bcrypt';
 import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
 

+ 7 - 7
src/server/utils/__tests__/backup.test.ts → tests/unit/server/utils/backup.test.ts

@@ -1,5 +1,5 @@
 import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
-import { DatabaseBackup } from '../backup'
+import { DatabaseBackup } from '../../../../src/server/utils/backup'
 import path from 'path'
 
 // Mock pg-dump-restore
@@ -25,7 +25,7 @@ vi.mock('fs', () => ({
 }))
 
 // Mock logger
-vi.mock('../logger', () => ({
+vi.mock('../../../../src/server/utils/logger', () => ({
   logger: {
     db: vi.fn(),
     error: vi.fn(),
@@ -66,7 +66,7 @@ describe('DatabaseBackup', () => {
 
     it('应该在创建目录失败时抛出错误', async () => {
       const fs = await import('fs')
-      const { logger } = await import('../logger')
+      const { logger } = await import('../../../../src/server/utils/logger')
 
       vi.mocked(fs.promises.mkdir).mockRejectedValueOnce(new Error('创建目录失败'))
 
@@ -147,7 +147,7 @@ describe('DatabaseBackup', () => {
   describe('cleanupOldBackups', () => {
     it('应该清理7天前的旧备份', async () => {
       const fs = await import('fs')
-      const { logger } = await import('../logger')
+      const { logger } = await import('../../../../src/server/utils/logger')
 
       const now = Date.now()
       const oldFileTime = now - (8 * 24 * 60 * 60 * 1000) // 8天前
@@ -167,7 +167,7 @@ describe('DatabaseBackup', () => {
 
     it('应该在清理失败时记录错误但不抛出', async () => {
       const fs = await import('fs')
-      const { logger } = await import('../logger')
+      const { logger } = await import('../../../../src/server/utils/logger')
 
       vi.mocked(fs.promises.readdir).mockRejectedValueOnce(new Error('读取目录失败'))
 
@@ -178,7 +178,7 @@ describe('DatabaseBackup', () => {
 
   describe('startScheduledBackups', () => {
     it('应该启动定时备份任务', async () => {
-      const { logger } = await import('../logger')
+      const { logger } = await import('../../../../src/server/utils/logger')
 
       backup.startScheduledBackups()
 
@@ -189,7 +189,7 @@ describe('DatabaseBackup', () => {
 
   describe('stopScheduledBackups', () => {
     it('应该停止定时备份任务', async () => {
-      const { logger } = await import('../logger')
+      const { logger } = await import('../../../../src/server/utils/logger')
 
       // 先启动再停止
       backup.startScheduledBackups()

+ 4 - 4
src/server/utils/__tests__/restore.test.ts → tests/unit/server/utils/restore.test.ts

@@ -1,5 +1,5 @@
 import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
-import { DatabaseRestore } from '../restore'
+import { DatabaseRestore } from '../../../../src/server/utils/restore'
 import path from 'path'
 
 // Mock pg-dump-restore
@@ -22,7 +22,7 @@ vi.mock('fs', async (importOriginal) => {
 })
 
 // Mock logger
-vi.mock('../logger', () => ({
+vi.mock('../../../../src/server/utils/logger', () => ({
   logger: {
     db: vi.fn(),
     error: vi.fn(),
@@ -110,7 +110,7 @@ describe('DatabaseRestore', () => {
 
     it('应该在读取目录失败时返回null', async () => {
       const fs = await import('fs')
-      const { logger } = await import('../logger')
+      const { logger } = await import('../../../../src/server/utils/logger')
 
       vi.mocked(fs.promises.readdir).mockRejectedValueOnce(new Error('读取目录失败'))
 
@@ -141,7 +141,7 @@ describe('DatabaseRestore', () => {
 
     it('应该在读取目录失败时返回空数组', async () => {
       const fs = await import('fs')
-      const { logger } = await import('../logger')
+      const { logger } = await import('../../../../src/server/utils/logger')
 
       vi.mocked(fs.promises.readdir).mockRejectedValueOnce(new Error('读取目录失败'))
 

+ 4 - 4
vitest.config.ts

@@ -10,8 +10,8 @@ export default defineConfig({
           name: 'node',
           environment: 'node',
           include: [
-            'src/server/**/*.test.{ts,js}',
-            'src/server/**/*.integration.test.{ts,js}'
+            'tests/unit/server/**/*.test.{ts,js}',
+            'tests/integration/server/**/*.test.{ts,js}'
           ],
           exclude: [
             '**/node_modules/**',
@@ -36,8 +36,8 @@ export default defineConfig({
           name: 'happy-dom',
           environment: 'happy-dom',
           include: [
-            'src/client/**/*.test.{ts,js,tsx,jsx}',
-            'src/client/**/*.integration.test.{ts,js,tsx,jsx}'
+            'tests/unit/client/**/*.test.{ts,js,tsx,jsx}',
+            'tests/integration/client/**/*.test.{ts,js,tsx,jsx}'
           ],
           exclude: [
             '**/node_modules/**',