flutter-testing-apps은(는) 무엇을 하나요?
Flutter 앱에 단위, 위젯, 통합 테스트를 구현합니다. 코드 품질을 보장하고 자동화된 테스트를 통해 회귀를 방지할 때 사용하세요.
개요
이 SKILL은 Flutter 애플리케이션을 위한 포괄적인 테스트 전략을 제공합니다. 테스트는 세 가지 주요 수준으로 나뉩니다:
- 단위 테스트(Unit Tests): 개별 함수, 클래스, 또는 메서드를 독립적으로 테스트합니다.
- 위젯 테스트(Widget Tests): 위젯의 UI 렌더링과 상호작용을 테스트합니다.
- 통합 테스트(Integration Tests): 앱의 전체 흐름과 사용자 시나리오를 종단 간 테스트합니다.
시작하기
의존성 추가
pubspec.yaml 파일에 다음 의존성을 추가하세요:
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
mockito: ^5.4.0
build_runner: ^2.4.0
테스트 디렉토리 구조
lib/
...
test/
unit/
model_test.dart
service_test.dart
widget/
my_widget_test.dart
integration/
app_test.dart
단위 테스트 작성하기
test/unit/service_test.dart 예제:
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:my_app/services/api_service.dart';
class MockApiService extends Mock implements ApiService {}
void main() {
group('ApiService', () {
late MockApiService mockService;
setUp(() {
mockService = MockApiService();
});
test('fetchData returns data on success', () async {
when(mockService.fetchData()).thenAnswer((_) async => 'data');
final result = await mockService.fetchData();
expect(result, 'data');
});
test('fetchData throws exception on failure', () async {
when(mockService.fetchData()).thenThrow(Exception('Network error'));
expect(() async => await mockService.fetchData(), throwsException);
});
});
}
위젯 테스트 작성하기
test/widget/my_widget_test.dart 예제:
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/widgets/my_button.dart';
void main() {
testWidgets('MyButton displays correct label', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: MyButton(label: 'Press Me', onPressed: () {}),
),
),
);
expect(find.text('Press Me'), findsOneWidget);
});
testWidgets('MyButton responds to tap', (WidgetTester tester) async {
var tapped = false;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: MyButton(
label: 'Press Me',
onPressed: () => tapped = true,
),
),
),
);
await tester.tap(find.byType(ElevatedButton));
expect(tapped, isTrue);
});
}
통합 테스트 작성하기
test/integration/app_test.dart 예제:
import 'package:integration_test/integration_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('end-to-end test', (WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
// 로그인 화면 확인
expect(find.text('Login'), findsOneWidget);
// 텍스트 필드 입력
await tester.enterText(find.byType(TextField).at(0), '[email protected]');
await tester.enterText(find.byType(TextField).at(1), 'password123');
// 로그인 버튼 탭
await tester.tap(find.text('Login'));
await tester.pumpAndSettle();
// 대시보드 확인
expect(find.text('Welcome'), findsOneWidget);
});
}
테스트 실행하기
단위 및 위젯 테스트
flutter test
특정 테스트 파일 실행
flutter test test/unit/service_test.dart
통합 테스트 실행
flutter test integration_test/app_test.dart -d <device_id>
테스트 커버리지 측정
flutter test --coverage
커버리지 보고서는 coverage/lcov.info에 생성됩니다.
CI/CD 통합
GitHub Actions 예제 (.github/workflows/test.yml):
name: Run Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
- run: flutter pub get
- run: flutter test --coverage
- uses: codecov/codecov-action@v3
모범 사례
- 테스트는 독립적이어야 함: 각 테스트는 상태를 공유하지 않고 독립적으로 실행되어야 합니다.
- 행동 검증: 구현 세부 사항보다는 예상되는 동작을 검증하세요.
- Mock 활용: 외부 의존성(네트워크, 데이터베이스 등)은 Mock 객체로 대체하세요.
- 일관된 명명 규칙:
testDescription_expectedBehavior패턴을 사용하세요. - AAA 패턴 따르기: Arrange (준비), Act (실행), Assert (검증) 단계를 명확히 구분하세요.