test: add comprehensive test coverage and improve code quality
- Add new test files for auth, service, and handler modules - Improve test organization and coverage - Refactor code for better maintainability - Add captcha, settings, stats, and theme handler tests - Add auth module tests (CAS, OAuth, password, SSO, state) - Add service layer tests for auth, export, permissions, roles - All Go tests pass (exit code 0) - All frontend tests pass (325 tests in 59 files)
This commit is contained in:
@@ -13,19 +13,19 @@ import (
|
||||
// 数据库索引性能测试 - 验证索引使用和查询性能
|
||||
|
||||
type IndexPerformanceMetrics struct {
|
||||
QueryTime time.Duration
|
||||
RowsScanned int64
|
||||
IndexUsed bool
|
||||
IndexName string
|
||||
ExecutionPlan string
|
||||
QueryTime time.Duration
|
||||
RowsScanned int64
|
||||
IndexUsed bool
|
||||
IndexName string
|
||||
ExecutionPlan string
|
||||
}
|
||||
|
||||
func BenchmarkQueryWithIndex(b *testing.B) {
|
||||
// 测试有索引的查询性能
|
||||
userRepo := repository.NewUserRepository(nil)
|
||||
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
start := time.Now()
|
||||
_, _ = userRepo.GetByEmail(context.Background(), "test@example.com")
|
||||
@@ -39,7 +39,7 @@ func BenchmarkQueryWithIndex(b *testing.B) {
|
||||
func BenchmarkQueryWithoutIndex(b *testing.B) {
|
||||
// 测试无索引的查询性能(模拟)
|
||||
b.ResetTimer()
|
||||
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
start := time.Now()
|
||||
// 模拟全表扫描查询
|
||||
@@ -54,7 +54,7 @@ func BenchmarkQueryWithoutIndex(b *testing.B) {
|
||||
func BenchmarkUserIndexLookup(b *testing.B) {
|
||||
// 测试用户表索引查找性能
|
||||
userRepo := repository.NewUserRepository(nil)
|
||||
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
userID int64
|
||||
@@ -65,16 +65,16 @@ func BenchmarkUserIndexLookup(b *testing.B) {
|
||||
{"通过用户名查找", 0, "testuser", ""},
|
||||
{"通过邮箱查找", 0, "", "test@example.com"},
|
||||
}
|
||||
|
||||
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
start := time.Now()
|
||||
var user *domain.User
|
||||
var err error
|
||||
|
||||
|
||||
switch {
|
||||
case tc.userID > 0:
|
||||
user, err = userRepo.GetByID(context.Background(), tc.userID)
|
||||
@@ -83,7 +83,7 @@ func BenchmarkUserIndexLookup(b *testing.B) {
|
||||
case tc.email != "":
|
||||
user, err = userRepo.GetByEmail(context.Background(), tc.email)
|
||||
}
|
||||
|
||||
|
||||
_ = user
|
||||
_ = err
|
||||
duration := time.Since(start)
|
||||
@@ -98,7 +98,7 @@ func BenchmarkUserIndexLookup(b *testing.B) {
|
||||
func BenchmarkJoinQuery(b *testing.B) {
|
||||
// 测试连接查询性能
|
||||
b.ResetTimer()
|
||||
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
start := time.Now()
|
||||
// 模拟连接查询
|
||||
@@ -114,7 +114,7 @@ func BenchmarkJoinQuery(b *testing.B) {
|
||||
func BenchmarkRangeQuery(b *testing.B) {
|
||||
// 测试范围查询性能
|
||||
b.ResetTimer()
|
||||
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
start := time.Now()
|
||||
// 模拟范围查询:SELECT * FROM users WHERE created_at BETWEEN ? AND ?
|
||||
@@ -129,7 +129,7 @@ func BenchmarkRangeQuery(b *testing.B) {
|
||||
func BenchmarkOrderByQuery(b *testing.B) {
|
||||
// 测试排序查询性能
|
||||
b.ResetTimer()
|
||||
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
start := time.Now()
|
||||
// 模拟排序查询:SELECT * FROM users ORDER BY created_at DESC LIMIT 100
|
||||
@@ -144,46 +144,46 @@ func BenchmarkOrderByQuery(b *testing.B) {
|
||||
func TestIndexUsage(t *testing.T) {
|
||||
// 测试索引是否被正确使用
|
||||
testCases := []struct {
|
||||
name string
|
||||
query string
|
||||
expectedIndex string
|
||||
indexExpected bool
|
||||
name string
|
||||
query string
|
||||
expectedIndex string
|
||||
indexExpected bool
|
||||
}{
|
||||
{
|
||||
name: "主键查询应使用主键索引",
|
||||
query: "SELECT * FROM users WHERE id = ?",
|
||||
expectedIndex: "PRIMARY",
|
||||
indexExpected: true,
|
||||
name: "主键查询应使用主键索引",
|
||||
query: "SELECT * FROM users WHERE id = ?",
|
||||
expectedIndex: "PRIMARY",
|
||||
indexExpected: true,
|
||||
},
|
||||
{
|
||||
name: "用户名查询应使用username索引",
|
||||
query: "SELECT * FROM users WHERE username = ?",
|
||||
expectedIndex: "idx_users_username",
|
||||
indexExpected: true,
|
||||
name: "用户名查询应使用username索引",
|
||||
query: "SELECT * FROM users WHERE username = ?",
|
||||
expectedIndex: "idx_users_username",
|
||||
indexExpected: true,
|
||||
},
|
||||
{
|
||||
name: "邮箱查询应使用email索引",
|
||||
query: "SELECT * FROM users WHERE email = ?",
|
||||
expectedIndex: "idx_users_email",
|
||||
indexExpected: true,
|
||||
name: "邮箱查询应使用email索引",
|
||||
query: "SELECT * FROM users WHERE email = ?",
|
||||
expectedIndex: "idx_users_email",
|
||||
indexExpected: true,
|
||||
},
|
||||
{
|
||||
name: "时间范围查询应使用created_at索引",
|
||||
query: "SELECT * FROM users WHERE created_at BETWEEN ? AND ?",
|
||||
expectedIndex: "idx_users_created_at",
|
||||
indexExpected: true,
|
||||
name: "时间范围查询应使用created_at索引",
|
||||
query: "SELECT * FROM users WHERE created_at BETWEEN ? AND ?",
|
||||
expectedIndex: "idx_users_created_at",
|
||||
indexExpected: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// 模拟执行计划分析
|
||||
metrics := analyzeQueryPlan(tc.query)
|
||||
|
||||
|
||||
if tc.indexExpected && !metrics.IndexUsed {
|
||||
t.Errorf("查询应使用索引 '%s', 但实际未使用", tc.expectedIndex)
|
||||
}
|
||||
|
||||
|
||||
if metrics.IndexUsed && metrics.IndexName != tc.expectedIndex {
|
||||
t.Logf("使用索引: %s (期望: %s)", metrics.IndexName, tc.expectedIndex)
|
||||
}
|
||||
@@ -218,14 +218,14 @@ func TestIndexSelectivity(t *testing.T) {
|
||||
distinctRows: 5,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
selectivity := float64(tc.distinctRows) / float64(tc.totalRows) * 100
|
||||
|
||||
|
||||
t.Logf("列 '%s' 的选择性: %.2f%% (%d/%d)",
|
||||
tc.column, selectivity, tc.distinctRows, tc.totalRows)
|
||||
|
||||
|
||||
// ID和username应该有高选择性
|
||||
if tc.column == "id" || tc.column == "username" {
|
||||
if selectivity < 99.0 {
|
||||
@@ -239,10 +239,10 @@ func TestIndexSelectivity(t *testing.T) {
|
||||
func TestIndexCovering(t *testing.T) {
|
||||
// 测试覆盖索引
|
||||
testCases := []struct {
|
||||
name string
|
||||
query string
|
||||
covered bool
|
||||
coveredColumns string
|
||||
name string
|
||||
query string
|
||||
covered bool
|
||||
coveredColumns string
|
||||
}{
|
||||
{
|
||||
name: "覆盖索引查询",
|
||||
@@ -257,7 +257,7 @@ func TestIndexCovering(t *testing.T) {
|
||||
coveredColumns: "",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.covered {
|
||||
@@ -272,33 +272,33 @@ func TestIndexCovering(t *testing.T) {
|
||||
func TestIndexFragmentation(t *testing.T) {
|
||||
// 测试索引碎片化
|
||||
testCases := []struct {
|
||||
name string
|
||||
tableName string
|
||||
indexName string
|
||||
fragmentation float64
|
||||
name string
|
||||
tableName string
|
||||
indexName string
|
||||
fragmentation float64
|
||||
maxFragmentation float64
|
||||
}{
|
||||
{
|
||||
name: "用户表主键索引碎片化",
|
||||
tableName: "users",
|
||||
indexName: "PRIMARY",
|
||||
fragmentation: 2.5,
|
||||
name: "用户表主键索引碎片化",
|
||||
tableName: "users",
|
||||
indexName: "PRIMARY",
|
||||
fragmentation: 2.5,
|
||||
maxFragmentation: 10.0,
|
||||
},
|
||||
{
|
||||
name: "用户表username索引碎片化",
|
||||
tableName: "users",
|
||||
indexName: "idx_users_username",
|
||||
fragmentation: 5.3,
|
||||
name: "用户表username索引碎片化",
|
||||
tableName: "users",
|
||||
indexName: "idx_users_username",
|
||||
fragmentation: 5.3,
|
||||
maxFragmentation: 10.0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Logf("表 '%s' 的索引 '%s' 碎片化率: %.2f%%",
|
||||
tc.tableName, tc.indexName, tc.fragmentation)
|
||||
|
||||
|
||||
if tc.fragmentation > tc.maxFragmentation {
|
||||
t.Logf("警告: 碎片化率 %.2f%% 超过阈值 %.2f%%,建议重建索引",
|
||||
tc.fragmentation, tc.maxFragmentation)
|
||||
@@ -310,29 +310,29 @@ func TestIndexFragmentation(t *testing.T) {
|
||||
func TestIndexSize(t *testing.T) {
|
||||
// 测试索引大小
|
||||
testCases := []struct {
|
||||
name string
|
||||
tableName string
|
||||
indexName string
|
||||
indexSize int64
|
||||
tableSize int64
|
||||
name string
|
||||
tableName string
|
||||
indexName string
|
||||
indexSize int64
|
||||
tableSize int64
|
||||
}{
|
||||
{
|
||||
name: "用户表索引大小",
|
||||
tableName: "users",
|
||||
indexName: "idx_users_username",
|
||||
indexSize: 50 * 1024 * 1024, // 50MB
|
||||
indexSize: 50 * 1024 * 1024, // 50MB
|
||||
tableSize: 200 * 1024 * 1024, // 200MB
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ratio := float64(tc.indexSize) / float64(tc.tableSize) * 100
|
||||
|
||||
|
||||
t.Logf("表 '%s' 的索引 '%s' 大小: %.2f MB, 占比 %.2f%%",
|
||||
tc.tableName, tc.indexName,
|
||||
float64(tc.indexSize)/1024/1024, ratio)
|
||||
|
||||
|
||||
if ratio > 30 {
|
||||
t.Logf("警告: 索引占比 %.2f%% 较高", ratio)
|
||||
}
|
||||
@@ -364,19 +364,19 @@ func TestIndexRebuildPerformance(t *testing.T) {
|
||||
maxTime: 60 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
start := time.Now()
|
||||
|
||||
|
||||
// 模拟索引重建
|
||||
// ALTER TABLE tc.tableName DROP INDEX tc.indexName, ADD INDEX tc.indexName (...)
|
||||
time.Sleep(5 * time.Second) // 模拟
|
||||
|
||||
|
||||
duration := time.Since(start)
|
||||
|
||||
|
||||
t.Logf("重建索引 '%s' 用时: %v (行数: %d)", tc.indexName, duration, tc.rowCount)
|
||||
|
||||
|
||||
if duration > tc.maxTime {
|
||||
t.Errorf("索引重建时间 %v 超过阈值 %v", duration, tc.maxTime)
|
||||
}
|
||||
@@ -403,19 +403,19 @@ func TestQueryPlanStability(t *testing.T) {
|
||||
query: "SELECT * FROM users WHERE email = ?",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// 执行多次查询,验证计划稳定性
|
||||
for _, q := range queries {
|
||||
t.Run(q.name, func(t *testing.T) {
|
||||
plan1 := analyzeQueryPlan(q.query)
|
||||
plan2 := analyzeQueryPlan(q.query)
|
||||
plan3 := analyzeQueryPlan(q.query)
|
||||
|
||||
|
||||
// 验证计划一致
|
||||
if plan1.IndexUsed != plan2.IndexUsed || plan2.IndexUsed != plan3.IndexUsed {
|
||||
t.Errorf("查询计划不稳定: 使用索引不一致")
|
||||
}
|
||||
|
||||
|
||||
if plan1.IndexName != plan2.IndexName || plan2.IndexName != plan3.IndexName {
|
||||
t.Logf("查询计划索引变化: %s -> %s -> %s",
|
||||
plan1.IndexName, plan2.IndexName, plan3.IndexName)
|
||||
@@ -427,9 +427,9 @@ func TestQueryPlanStability(t *testing.T) {
|
||||
func TestFullTableScanDetection(t *testing.T) {
|
||||
// 检测全表扫描
|
||||
testCases := []struct {
|
||||
name string
|
||||
query string
|
||||
hasFullScan bool
|
||||
name string
|
||||
query string
|
||||
hasFullScan bool
|
||||
}{
|
||||
{
|
||||
name: "ID查询不应全表扫描",
|
||||
@@ -452,15 +452,15 @@ func TestFullTableScanDetection(t *testing.T) {
|
||||
hasFullScan: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
plan := analyzeQueryPlan(tc.query)
|
||||
|
||||
|
||||
if tc.hasFullScan && !plan.IndexUsed {
|
||||
t.Logf("查询可能执行全表扫描: %s", tc.query)
|
||||
}
|
||||
|
||||
|
||||
if !tc.hasFullScan && plan.IndexUsed {
|
||||
t.Logf("查询正确使用索引")
|
||||
}
|
||||
@@ -471,11 +471,11 @@ func TestFullTableScanDetection(t *testing.T) {
|
||||
func TestIndexEfficiency(t *testing.T) {
|
||||
// 测试索引效率
|
||||
testCases := []struct {
|
||||
name string
|
||||
query string
|
||||
rowsExpected int64
|
||||
rowsScanned int64
|
||||
rowsReturned int64
|
||||
name string
|
||||
query string
|
||||
rowsExpected int64
|
||||
rowsScanned int64
|
||||
rowsReturned int64
|
||||
}{
|
||||
{
|
||||
name: "精确查询应扫描少量行",
|
||||
@@ -492,14 +492,14 @@ func TestIndexEfficiency(t *testing.T) {
|
||||
rowsReturned: 10000,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
scanRatio := float64(tc.rowsScanned) / float64(tc.rowsReturned)
|
||||
|
||||
|
||||
t.Logf("查询扫描/返回比: %.2f (%d/%d)",
|
||||
scanRatio, tc.rowsScanned, tc.rowsReturned)
|
||||
|
||||
|
||||
if scanRatio > 10 {
|
||||
t.Logf("警告: 扫描/返回比 %.2f 较高,可能需要优化索引", scanRatio)
|
||||
}
|
||||
@@ -510,11 +510,11 @@ func TestIndexEfficiency(t *testing.T) {
|
||||
func TestCompositeIndexOrder(t *testing.T) {
|
||||
// 测试复合索引顺序
|
||||
testCases := []struct {
|
||||
name string
|
||||
indexName string
|
||||
columns []string
|
||||
query string
|
||||
indexUsed bool
|
||||
name string
|
||||
indexName string
|
||||
columns []string
|
||||
query string
|
||||
indexUsed bool
|
||||
}{
|
||||
{
|
||||
name: "复合索引(用户名,邮箱) - 完全匹配",
|
||||
@@ -538,15 +538,15 @@ func TestCompositeIndexOrder(t *testing.T) {
|
||||
indexUsed: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
plan := analyzeQueryPlan(tc.query)
|
||||
|
||||
|
||||
if tc.indexUsed && !plan.IndexUsed {
|
||||
t.Errorf("查询应使用索引 '%s'", tc.indexName)
|
||||
}
|
||||
|
||||
|
||||
if !tc.indexUsed && plan.IndexUsed {
|
||||
t.Logf("查询未使用复合索引 '%s' (列: %v)",
|
||||
tc.indexName, tc.columns)
|
||||
@@ -577,11 +577,11 @@ func TestIndexLocking(t *testing.T) {
|
||||
maxLockTime: 500 * time.Millisecond,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Logf("%s 锁定时间: %v", tc.operation, tc.lockTime)
|
||||
|
||||
|
||||
if tc.lockTime > tc.maxLockTime {
|
||||
t.Logf("警告: 锁定时间 %v 超过阈值 %v", tc.lockTime, tc.maxLockTime)
|
||||
}
|
||||
@@ -594,19 +594,19 @@ func TestIndexLocking(t *testing.T) {
|
||||
func analyzeQueryPlan(query string) *IndexPerformanceMetrics {
|
||||
// 模拟查询计划分析
|
||||
metrics := &IndexPerformanceMetrics{
|
||||
QueryTime: time.Duration(1 + rand.Intn(10)) * time.Millisecond,
|
||||
QueryTime: time.Duration(1+rand.Intn(10)) * time.Millisecond,
|
||||
RowsScanned: int64(1 + rand.Intn(100)),
|
||||
ExecutionPlan: "Index Lookup",
|
||||
}
|
||||
|
||||
|
||||
// 简单判断是否使用索引
|
||||
if containsIndexHint(query) {
|
||||
metrics.IndexUsed = true
|
||||
metrics.IndexName = "idx_users_username"
|
||||
metrics.QueryTime = time.Duration(1 + rand.Intn(5)) * time.Millisecond
|
||||
metrics.QueryTime = time.Duration(1+rand.Intn(5)) * time.Millisecond
|
||||
metrics.RowsScanned = 1
|
||||
}
|
||||
|
||||
|
||||
return metrics
|
||||
}
|
||||
|
||||
@@ -639,12 +639,12 @@ func TestIndexMaintenance(t *testing.T) {
|
||||
// ANALYZE TABLE users - 更新统计信息
|
||||
t.Log("ANALYZE TABLE 执行成功")
|
||||
})
|
||||
|
||||
|
||||
t.Run("OPTIMIZE TABLE", func(t *testing.T) {
|
||||
// OPTIMIZE TABLE users - 优化表和索引
|
||||
t.Log("OPTIMIZE TABLE 执行成功")
|
||||
})
|
||||
|
||||
|
||||
t.Run("CHECK TABLE", func(t *testing.T) {
|
||||
// CHECK TABLE users - 检查表完整性
|
||||
t.Log("CHECK TABLE 执行成功")
|
||||
|
||||
Reference in New Issue
Block a user