platform/doc/testing_guide.md
2024-11-25 20:33:45 -07:00

7.5 KiB

Testing Guide

Overview

This guide outlines our testing approach, which follows Laravel's testing patterns while leveraging Dart's testing capabilities. It covers unit testing, integration testing, performance testing, and Laravel-style testing approaches.

Test Types

1. Unit Tests

void main() {
  group('Service Tests', () {
    late Container container;
    late UserService service;
    
    setUp(() {
      container = Container(reflector);
      container.bind<Database>((c) => MockDatabase());
      service = container.make<UserService>();
    });
    
    test('creates user', () async {
      var user = await service.create({
        'name': 'John Doe',
        'email': 'john@example.com'
      });
      
      expect(user.name, equals('John Doe'));
      expect(user.email, equals('john@example.com'));
    });
    
    test('validates user data', () {
      expect(
        () => service.create({'name': 'John Doe'}),
        throwsA(isA<ValidationException>())
      );
    });
  });
}

2. Integration Tests

void main() {
  group('API Integration', () {
    late Application app;
    
    setUp(() async {
      app = await createApplication();
      await app.initialize();
    });
    
    tearDown(() async {
      await app.shutdown();
    });
    
    test('creates user through API', () async {
      var response = await app.post('/users', body: {
        'name': 'John Doe',
        'email': 'john@example.com'
      });
      
      expect(response.statusCode, equals(201));
      expect(response.json['name'], equals('John Doe'));
    });
    
    test('handles validation errors', () async {
      var response = await app.post('/users', body: {
        'name': 'John Doe'
      });
      
      expect(response.statusCode, equals(422));
      expect(response.json['errors'], contains('email'));
    });
  });
}

3. Performance Tests

void main() {
  group('Performance Tests', () {
    late Application app;
    
    setUp(() async {
      app = await createApplication();
      await app.initialize();
    });
    
    test('handles concurrent requests', () async {
      var stopwatch = Stopwatch()..start();
      
      // Create 100 concurrent requests
      var futures = List.generate(100, (i) => 
        app.get('/users')
      );
      
      var responses = await Future.wait(futures);
      stopwatch.stop();
      
      // Verify responses
      expect(responses, everyElement(
        predicate((r) => r.statusCode == 200)
      ));
      
      // Check performance
      expect(
        stopwatch.elapsedMilliseconds / responses.length,
        lessThan(100) // Less than 100ms per request
      );
    });
    
    test('handles database operations efficiently', () async {
      var stopwatch = Stopwatch()..start();
      
      // Create 1000 records
      for (var i = 0; i < 1000; i++) {
        await app.post('/users', body: {
          'name': 'User $i',
          'email': 'user$i@example.com'
        });
      }
      
      stopwatch.stop();
      
      // Check performance
      expect(
        stopwatch.elapsedMilliseconds / 1000,
        lessThan(50) // Less than 50ms per operation
      );
    });
  });
}

4. Laravel-Style Feature Tests

void main() {
  group('Feature Tests', () {
    late TestCase test;
    
    setUp(() {
      test = await TestCase.make();
    });
    
    test('user can register', () async {
      await test
        .post('/register', {
          'name': 'John Doe',
          'email': 'john@example.com',
          'password': 'password',
          'password_confirmation': 'password'
        })
        .assertStatus(302)
        .assertRedirect('/home');
        
      test.assertDatabaseHas('users', {
        'email': 'john@example.com'
      });
    });
    
    test('user can login', () async {
      // Create user
      await test.createUser({
        'email': 'john@example.com',
        'password': 'password'
      });
      
      await test
        .post('/login', {
          'email': 'john@example.com',
          'password': 'password'
        })
        .assertAuthenticated();
    });
  });
}

Performance Testing Tools

1. WRK Benchmarking

# Basic load test
wrk -t12 -c400 -d30s http://localhost:8080/api/endpoint

# Test with custom script
wrk -t12 -c400 -d30s -s script.lua http://localhost:8080/api/endpoint

2. Custom Load Testing

void main() {
  test('load test', () async {
    var client = HttpClient();
    var stopwatch = Stopwatch()..start();
    
    // Configure test
    var duration = Duration(minutes: 1);
    var concurrency = 100;
    var results = <Duration>[];
    
    // Run test
    while (stopwatch.elapsed < duration) {
      var requests = List.generate(concurrency, (i) async {
        var requestWatch = Stopwatch()..start();
        await client.get('localhost', 8080, '/api/endpoint');
        requestWatch.stop();
        results.add(requestWatch.elapsed);
      });
      
      await Future.wait(requests);
    }
    
    // Analyze results
    var average = results.reduce((a, b) => a + b) ~/ results.length;
    var sorted = List.of(results)..sort();
    var p95 = sorted[(sorted.length * 0.95).floor()];
    var p99 = sorted[(sorted.length * 0.99).floor()];
    
    print('Results:');
    print('Average: ${average.inMilliseconds}ms');
    print('P95: ${p95.inMilliseconds}ms');
    print('P99: ${p99.inMilliseconds}ms');
  });
}

Best Practices

1. Test Organization

// Group related tests
group('UserService', () {
  group('creation', () {
    test('creates valid user', () {});
    test('validates input', () {});
    test('handles duplicates', () {});
  });
  
  group('authentication', () {
    test('authenticates valid credentials', () {});
    test('rejects invalid credentials', () {});
  });
});

2. Test Data Management

class TestCase {
  // Create test data
  Future<User> createUser([Map<String, dynamic>? attributes]) async {
    return factory.create(User, attributes);
  }
  
  // Clean up after tests
  Future<void> cleanup() async {
    await database.truncate(['users', 'posts', 'comments']);
  }
}

3. Assertions

// Use descriptive assertions
expect(user.name, equals('John Doe'),
  reason: 'User name should match input');
  
expect(response.statusCode,
  isIn([200, 201]),
  reason: 'Response should indicate success'
);

expect(
  () => service.validateEmail('invalid'),
  throwsA(isA<ValidationException>()),
  reason: 'Should reject invalid email'
);

Performance Benchmarks

1. Response Time Targets

API Endpoints:
- Average: < 100ms
- P95: < 200ms
- P99: < 500ms

Database Operations:
- Simple queries: < 10ms
- Complex queries: < 50ms
- Writes: < 20ms

Cache Operations:
- Reads: < 5ms
- Writes: < 10ms

2. Throughput Targets

API Layer:
- Minimum: 1000 requests/second
- Target: 5000 requests/second

Database Layer:
- Reads: 10000 operations/second
- Writes: 1000 operations/second

Cache Layer:
- Operations: 50000/second

3. Resource Usage Targets

Memory:
- Base: < 100MB
- Under load: < 500MB
- Leak rate: < 1MB/hour

CPU:
- Idle: < 5%
- Average load: < 40%
- Peak load: < 80%

Connections:
- Database: < 100 concurrent
- Cache: < 1000 concurrent
- HTTP: < 10000 concurrent

Next Steps

  1. Implement test helpers
  2. Add more Laravel-style assertions
  3. Create performance test suite
  4. Add continuous benchmarking
  5. Improve test coverage

Would you like me to:

  1. Create more test examples?
  2. Add specific performance tests?
  3. Create Laravel-compatible test helpers?