440 lines
8.5 KiB
Go
440 lines
8.5 KiB
Go
|
|
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"
|
|||
|
|
)
|