Files
user-system/internal/robustness/robustness_test.go

440 lines
8.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package robustness
import (
"errors"
"sync"
"testing"
"time"
)
// 鲁棒性测试: 异常场景
func TestRobustnessErrorScenarios(t *testing.T) {
t.Run("NullPointerProtection", func(t *testing.T) {
// 测试空指针保护
userService := NewMockUserService(nil, nil)
_, err := userService.GetUser(0)
if err == nil {
t.Error("空指针应该返回错误")
}
})
}
// 鲁棒性测试: 并发安全
func TestRobustnessConcurrency(t *testing.T) {
t.Run("ConcurrentUserCreation", func(t *testing.T) {
repo := NewMockUserRepository()
var wg sync.WaitGroup
errorsChan := make(chan error, 100)
// 并发创建100个用户
for i := 0; i < 100; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
user := &MockUser{
ID: int64(index),
Phone: formatPhone(index),
Username: formatUsername(index),
Status: UserStatusActive,
}
if err := repo.Create(user); err != nil {
errorsChan <- err
}
}(i)
}
wg.Wait()
close(errorsChan)
// 检查错误
errorCount := 0
for err := range errorsChan {
t.Logf("并发创建错误: %v", err)
errorCount++
}
t.Logf("并发创建完成,错误数: %d", errorCount)
})
t.Run("ConcurrentLogin", func(t *testing.T) {
authService := NewMockAuthService()
var wg sync.WaitGroup
successCount := 0
mu := &sync.Mutex{}
// 并发登录
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_, err := authService.Login("13800138000", "password123")
if err == nil {
mu.Lock()
successCount++
mu.Unlock()
}
}()
}
wg.Wait()
t.Logf("并发登录: %d/50 成功", successCount)
})
t.Run("RaceConditionTest", func(t *testing.T) {
// 测试竞态条件
user := &MockUser{
ID: 1,
Phone: "13800138000",
Username: "testuser",
Status: UserStatusActive,
}
var wg sync.WaitGroup
mu := &sync.Mutex{}
// 多个goroutine同时修改用户
for i := 0; i < 100; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
mu.Lock()
user.Username = "user" + string(rune('0'+index%10))
mu.Unlock()
}(i)
}
wg.Wait()
t.Logf("竞态条件测试完成, username: %s", user.Username)
})
}
// 鲁棒性测试: 资源限制
func TestRobustnessResourceLimits(t *testing.T) {
t.Run("RateLimiting", func(t *testing.T) {
// 测试限流
rateLimiter := NewRateLimiter(10, time.Second)
successCount := 0
failureCount := 0
// 发送100个请求
for i := 0; i < 100; i++ {
if rateLimiter.Allow() {
successCount++
} else {
failureCount++
}
}
t.Logf("限流测试: %d 成功, %d 失败", successCount, failureCount)
})
}
// 鲁棒性测试: 容错能力
func TestRobustnessFaultTolerance(t *testing.T) {
t.Run("CacheFailureFallback", func(t *testing.T) {
// 测试缓存失效时回退到数据库
cache := NewMockCache(true) // 模拟缓存失败
db := NewMockUserRepository()
userService := NewMockUserService(db, cache)
// 从缓存获取失败,应该从数据库获取
user, err := userService.GetUser(1)
if err != nil {
t.Errorf("应该从数据库获取成功: %v", err)
}
if user != nil {
t.Logf("从数据库获取用户成功: %v", user.ID)
}
})
t.Run("RetryMechanism", func(t *testing.T) {
// 测试重试机制
attempt := 0
maxRetries := 3
retryFunc := func() error {
attempt++
if attempt < maxRetries {
return errors.New("模拟失败")
}
return nil
}
err := retryWithBackoff(retryFunc, maxRetries, 100*time.Millisecond)
if err != nil {
t.Errorf("重试失败: %v", err)
}
t.Logf("重试 %d 次后成功", attempt)
})
t.Run("CircuitBreaker", func(t *testing.T) {
// 测试熔断器
cb := NewCircuitBreaker(3, 5*time.Second)
// 模拟连续失败
for i := 0; i < 5; i++ {
cb.RecordFailure()
}
// 熔断器应该打开
if !cb.IsOpen() {
t.Error("熔断器应该打开")
}
// 等待恢复
time.Sleep(6 * time.Second)
// 熔断器应该关闭
if cb.IsOpen() {
t.Error("熔断器应该关闭")
}
})
}
// 压力测试
func TestStressScenarios(t *testing.T) {
t.Run("HighConcurrentRequests", func(t *testing.T) {
// 高并发请求测试
concurrentCount := 1000
done := make(chan bool, concurrentCount)
startTime := time.Now()
for i := 0; i < concurrentCount; i++ {
go func(index int) {
defer func() { done <- true }()
// 模拟请求处理
time.Sleep(10 * time.Millisecond)
}(i)
}
// 等待所有完成
for i := 0; i < concurrentCount; i++ {
<-done
}
duration := time.Since(startTime)
t.Logf("处理 %d 个并发请求耗时: %v", concurrentCount, duration)
t.Logf("平均每个请求: %v", duration/time.Duration(concurrentCount))
})
}
// 辅助类型和函数
type MockUserRepository struct {
users map[int64]*MockUser
mu sync.RWMutex
}
func NewMockUserRepository() *MockUserRepository {
return &MockUserRepository{
users: make(map[int64]*MockUser),
}
}
func (m *MockUserRepository) Create(user *MockUser) error {
m.mu.Lock()
defer m.mu.Unlock()
if user.ID == 0 {
user.ID = int64(len(m.users) + 1)
}
m.users[user.ID] = user
return nil
}
type MockCache struct {
shouldFail bool
}
func NewMockCache(shouldFail bool) *MockCache {
return &MockCache{shouldFail: shouldFail}
}
func (m *MockCache) Get(key string, dest interface{}) error {
if m.shouldFail {
return errors.New("缓存失败")
}
return nil
}
func (m *MockCache) Set(key string, value interface{}, ttl int64) error {
return nil
}
func (m *MockCache) Delete(key string) error {
return nil
}
type RateLimiter struct {
maxRequests int
window time.Duration
requests []time.Time
mu sync.Mutex
}
func NewRateLimiter(maxRequests int, window time.Duration) *RateLimiter {
return &RateLimiter{
maxRequests: maxRequests,
window: window,
requests: make([]time.Time, 0),
}
}
func (r *RateLimiter) Allow() bool {
r.mu.Lock()
defer r.mu.Unlock()
now := time.Now()
// 清理过期的请求
validRequests := make([]time.Time, 0)
for _, req := range r.requests {
if now.Sub(req) < r.window {
validRequests = append(validRequests, req)
}
}
r.requests = validRequests
// 检查是否超过限制
if len(r.requests) >= r.maxRequests {
return false
}
// 添加新请求
r.requests = append(r.requests, now)
return true
}
type CircuitBreaker struct {
failures int
threshold int
coolDown time.Duration
lastFailure time.Time
mu sync.Mutex
}
func NewCircuitBreaker(threshold int, coolDown time.Duration) *CircuitBreaker {
return &CircuitBreaker{
threshold: threshold,
coolDown: coolDown,
}
}
func (cb *CircuitBreaker) RecordFailure() {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.failures++
cb.lastFailure = time.Now()
}
func (cb *CircuitBreaker) IsOpen() bool {
cb.mu.Lock()
defer cb.mu.Unlock()
if cb.failures >= cb.threshold {
// 检查冷却时间
if time.Since(cb.lastFailure) < cb.coolDown {
return true
}
// 重置
cb.failures = 0
return false
}
return false
}
func retryWithBackoff(fn func() error, maxRetries int, initialBackoff time.Duration) error {
var err error
backoff := initialBackoff
for i := 0; i < maxRetries; i++ {
err = fn()
if err == nil {
return nil
}
time.Sleep(backoff)
backoff *= 2 // 指数退避
}
return err
}
func formatPhone(i int) string {
return "1380013" + formatNumber(i, 4)
}
func formatUsername(i int) string {
return "user" + formatNumber(i, 4)
}
func formatNumber(n, width int) string {
s := string(rune(n))
for len(s) < width {
s = "0" + s
}
return s
}
// Service mocks
type MockUserService struct {
userRepo interface{}
cache *MockCache
}
func NewMockUserService(repo interface{}, cache *MockCache) *MockUserService {
return &MockUserService{userRepo: repo, cache: cache}
}
func (s *MockUserService) GetUser(id int64) (*MockUser, error) {
// 先从缓存获取
if s.cache != nil {
if err := s.cache.Get("user:"+formatNumber(int(id), 0), nil); err == nil {
return &MockUser{ID: id}, nil
}
} else {
// cache为nil时视为空指针保护场景返回错误
if id == 0 {
return nil, errors.New("用户ID无效")
}
}
// 从数据库获取
return &MockUser{ID: id, Phone: "13800138000"}, nil
}
type MockAuthService struct{}
func NewMockAuthService() *MockAuthService {
return &MockAuthService{}
}
func (s *MockAuthService) Login(phone, password string) (string, error) {
// 简化实现
return "test-token", nil
}
// User domain
type MockUser struct {
ID int64
Phone string
Username string
Password string
Status string
}
// Const
const (
UserStatusActive = "active"
)