© Khmer Angkor Academy - sophearithput168

Testing

Testing ក្នុង Flutter

Testing ជួយធានាថា code ដំណើរការត្រឹមត្រូវ និងកាត់បន្ថយ bugs។ Flutter support 3 levels of testing: Unit Tests, Widget Tests, និង Integration Tests។

📚 Testing Pyramid Theory

Testing Pyramid (Bottom to Top)

                    /\
                   /  \
                  / E2E\          ← តិចបំផុត (slow, expensive)
                 /------\
                /        \
               / Widget  \        ← មធ្យម
              /  Tests    \
             /------------\
            /              \
           /  Unit Tests   \     ← ច្រើនបំផុត (fast, cheap)
          /________________\

70% Unit Tests    - Test business logic
20% Widget Tests  - Test UI components
10% E2E Tests     - Test full user flows

Testing Types:

Test Type What to Test Speed Cost
Unit Tests Functions, classes, methods ⚡ Very Fast 💰 Cheap
Widget Tests UI components, user interactions ⚡⚡ Medium 💰💰 Medium
Integration Tests Complete app flows, E2E scenarios 🐌 Slow 💰💰💰 Expensive

✅ Unit Testing (Business Logic)

Test pure Dart code without UI dependencies.

# pubspec.yaml
dev_dependencies:
  test: ^1.24.0
import 'package:test/test.dart';

void main() {
  test('Counter increments', () {
    final counter = Counter();
    counter.increment();
    expect(counter.count, 1);
  });
  
  group('Counter class', () {
    late Counter counter;
    
    setUp(() {
      // Run before each test
      counter = Counter();
    });
    
    test('starts at 0', () {
      expect(counter.count, 0);
    });
    
    test('increments by 1', () {
      counter.increment();
      expect(counter.count, 1);
    });
    
    test('decrements by 1', () {
      counter.increment();
      counter.decrement();
      expect(counter.count, 0);
    });
    
    test('cannot go below 0', () {
      counter.decrement();
      expect(counter.count, 0);
    });
  });
}

// Run tests:
// flutter test test/counter_test.dart

Common Matchers:

// Equality
expect(actual, equals(expected));
expect(value, 42);
expect(name, 'John');

// Types
expect(value, isA<int>());
expect(list, isList);

// Numbers
expect(value, greaterThan(10));
expect(value, lessThanOrEqualTo(100));
expect(value, closeTo(3.14, 0.01));

// Strings
expect(text, contains('hello'));
expect(text, startsWith('Hi'));
expect(text, matches(r'\d+'));

// Collections
expect(list, isEmpty);
expect(list, hasLength(3));
expect(list, contains(5));

// Booleans
expect(value, isTrue);
expect(value, isFalse);
expect(value, isNull);

// Exceptions
expect(() => throwError(), throwsException);
expect(() => divide(10, 0), throwsA(isA<ArgumentError>()));

🎨 Widget Testing (UI Components)

Test Flutter widgets and user interactions.

# pubspec.yaml (already included)
dev_dependencies:
  flutter_test:
    sdk: flutter
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('Counter widget test', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());
    
    expect(find.text('0'), findsOneWidget);
    
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();
    
    expect(find.text('1'), findsOneWidget);
  });
  
  testWidgets('Button displays correct text', (tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: ElevatedButton(
            onPressed: () {},
            child: Text('Click Me'),
          ),
        ),
      ),
    );
    
    expect(find.text('Click Me'), findsOneWidget);
    expect(find.byType(ElevatedButton), findsOneWidget);
  });
  
  testWidgets('TextField input works', (tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: TextField(
            key: Key('email_field'),
          ),
        ),
      ),
    );
    
    // Enter text
    await tester.enterText(find.byKey(Key('email_field')), 'test@example.com');
    await tester.pump();
    
    expect(find.text('test@example.com'), findsOneWidget);
  });
}

// test/widget_test.dart

Widget Test Utilities:

// Finders
find.text('Hello');                    // Find by text
find.byKey(Key('my_widget'));          // Find by key
find.byType(ElevatedButton);           // Find by widget type
find.byIcon(Icons.add);                // Find by icon
find.widgetWithText(ElevatedButton, 'Submit');  // Specific widget with text

// Actions
await tester.tap(find.byIcon(Icons.add));       // Tap widget
await tester.longPress(find.text('Item'));      // Long press
await tester.drag(find.byType(ListView), Offset(0, -200));  // Scroll
await tester.enterText(find.byType(TextField), 'Text');     // Type text

// Waiting
await tester.pump();                   // Rebuild once
await tester.pumpAndSettle();          // Wait for animations
await tester.pump(Duration(seconds: 1));  // Wait duration

// Matchers
expect(find.text('Hello'), findsOneWidget);      // Finds exactly one
expect(find.byType(Text), findsWidgets);         // Finds at least one
expect(find.text('Missing'), findsNothing);      // Finds none
expect(find.byType(ListTile), findsNWidgets(5)); // Finds exactly N

🔗 Integration Testing (E2E)

Test complete app flows on real devices or emulators.

# pubspec.yaml
dev_dependencies:
  integration_test:
    sdk: flutter
// integration_test/app_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  testWidgets('Complete login flow', (tester) async {
    app.main();
    await tester.pumpAndSettle();
    
    // Navigate to login
    await tester.tap(find.text('Login'));
    await tester.pumpAndSettle();
    
    // Enter credentials
    await tester.enterText(find.byKey(Key('email')), 'user@example.com');
    await tester.enterText(find.byKey(Key('password')), 'password123');
    
    // Submit
    await tester.tap(find.text('Submit'));
    await tester.pumpAndSettle();
    
    // Verify navigation to home
    expect(find.text('Welcome'), findsOneWidget);
  });
}

// Run: flutter test integration_test/app_test.dart

🎯 Mocking & Test Doubles

# pubspec.yaml
dev_dependencies:
  mockito: ^5.4.0
  build_runner: ^2.4.0
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';

// 1. Create mock class
@GenerateMocks([ApiService])
void main() {}

// 2. Run code generation
// flutter pub run build_runner build

// 3. Use mock in tests
import 'api_test.mocks.dart';

void main() {
  test('fetchUser returns user data', () async {
    // Create mock
    final mockApi = MockApiService();
    
    // Setup mock behavior
    when(mockApi.fetchUser(1)).thenAnswer(
      (_) async => User(id: 1, name: 'John'),
    );
    
    // Test
    final user = await mockApi.fetchUser(1);
    
    // Verify
    expect(user.name, 'John');
    verify(mockApi.fetchUser(1)).called(1);
  });
}

📊 Test Coverage

# Generate coverage report
flutter test --coverage

# View coverage (requires lcov)
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html

✅ Testing Best Practices

1. AAA Pattern (Arrange-Act-Assert)

test('calculator adds two numbers', () {
  // Arrange - Setup
  final calculator = Calculator();
  
  // Act - Execute
  final result = calculator.add(2, 3);
  
  // Assert - Verify
  expect(result, 5);
});

2. Test Naming Convention

// ❌ Bad
test('test1', () {});

// ✅ Good
test('Counter increments when button is tapped', () {});
test('Login fails with invalid email', () {});
test('Product list shows 10 items', () {});

3. Golden Tests (Screenshot Testing)

testWidgets('Golden test for MyWidget', (tester) async {
  await tester.pumpWidget(MyWidget());
  
  await expectLater(
    find.byType(MyWidget),
    matchesGoldenFile('goldens/my_widget.png'),
  );
});

// Update goldens:
// flutter test --update-goldens

💡 Best Practices:

  • ✅ Write tests before fixing bugs (regression tests)
  • ✅ Aim for 70%+ code coverage
  • ✅ Test edge cases and error conditions
  • ✅ Keep tests simple and focused (one concept per test)
  • ✅ Use descriptive test names
  • ✅ Run tests in CI/CD pipeline
  • ✅ Mock external dependencies (APIs, databases)

⚠️ Testing Mistakes:

  • ❌ Testing implementation details instead of behavior
  • ❌ Writing tests that depend on other tests
  • ❌ Not testing error cases
  • ❌ Slow tests (optimize with mocks)
  • ❌ Testing private methods (test public API)

💡 ជំនួយ: សរសេរ tests ដើម្បីកាត់បន្ថយ bugs។ Unit tests ពិនិត្យ business logic។ Widget tests ពិនិត្យ UI។ Integration tests ពិនិត្យ full user flows។