Flutter 튜토리얼 46편: 통합 테스트

요약#

핵심 요지#

  • 문제 정의: 단위 테스트와 위젯 테스트는 개별 컴포넌트만 검증한다. 전체 앱이 실제 기기에서 어떻게 동작하는지는 확인할 수 없다.
  • 핵심 주장: 통합 테스트는 실제 기기나 에뮬레이터에서 앱 전체 워크플로우를 검증하고 성능을 측정한다.
  • 주요 근거: integration_test1 패키지는 위젯 테스트와 유사한 API를 제공하면서 실제 기기에서 실행된다.
  • 실무 기준: CI/CD 파이프라인에 통합 테스트를 포함하고, Firebase Test Lab으로 여러 기기에서 자동화된 테스트를 실행한다.
  • 한계: 웹에서는 성능 타임라인 기록이 지원되지 않는다.

문서가 설명하는 범위#

  • 통합 테스트의 개념과 용어
  • integration_test 패키지 설정
  • 통합 테스트 작성과 실행
  • 성능 프로파일링과 타임라인 측정
  • Firebase Test Lab 연동

읽는 시간: 20분 | 난이도: 중급


참고 자료#


문제 상황#

이전 튜토리얼에서 단위 테스트와 위젯 테스트를 배웠습니다. 하지만 이 테스트들은 개별 컴포넌트만 검증합니다.

기존 테스트의 한계#

// 단위 테스트: 함수 로직만 검증
test('counter increments', () {
expect(counter.value, 0);
counter.increment();
expect(counter.value, 1);
});
// 위젯 테스트: 단일 위젯만 검증
testWidgets('shows counter value', (tester) async {
await tester.pumpWidget(CounterWidget());
expect(find.text('0'), findsOneWidget);
});
// 문제: 실제 앱에서 어떻게 동작하는지 모름
// - 화면 전환이 제대로 되는가?
// - 네트워크 요청과 UI가 연동되는가?
// - 실제 기기에서 성능은 어떤가?

실제 앱은 여러 화면, 네트워크 요청, 데이터베이스 등이 복합적으로 작동합니다. 이를 검증하려면 통합 테스트가 필요합니다.


해결 방법#

통합 테스트는 앱 전체를 실제 기기에서 실행하고 사용자 워크플로우를 검증합니다.

graph TD A[Flutter 테스트 피라미드] --> B[단위 테스트] A --> C[위젯 테스트] A --> D[통합 테스트] B --> E[빠름, 개별 함수 검증] C --> F[중간, 단일 위젯 검증] D --> G[느림, 전체 앱 검증] D --> H[실제 기기에서 실행] D --> I[성능 측정 가능]

챕터 1: 통합 테스트 기본 개념#

Why#

NOTE

통합 테스트는 E2E(End-to-End) 테스트 또는 GUI 테스트라고도 합니다. 실제 사용자가 앱을 사용하는 것처럼 전체 워크플로우를 테스트합니다.

용어 정리:

  • 호스트 머신: 개발 중인 컴퓨터 (데스크톱)
  • 타겟 디바이스: 앱이 실행되는 기기 (폰, 에뮬레이터, 브라우저)

웹이나 데스크톱 앱의 경우, 호스트 머신과 타겟 디바이스가 같을 수 있습니다.

What#

NOTE

integration_test 패키지는 Flutter SDK에 포함되어 있습니다. 기존 flutter_driver2를 대체하는 최신 방식입니다.

integration_test의 장점:

  • flutter_test API 사용 (위젯 테스트와 유사한 문법)
  • flutter test 명령어로 실행 가능
  • Firebase Test Lab 지원
  • 실제 기기에서 실행

How#

TIP

1단계: 패키지 추가

Terminal window
flutter pub add 'dev:integration_test:{"sdk":"flutter"}'

pubspec.yaml에 다음이 추가됩니다.

dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter

2단계: 디렉토리 구조 생성

counter_app/
lib/
main.dart
integration_test/ # 통합 테스트 디렉토리
app_test.dart # 테스트 파일
test/
unit_test.dart # 단위/위젯 테스트

integration_test 디렉토리를 프로젝트 루트에 생성합니다.

Watch out#

WARNING

flutter_driver 마이그레이션: 기존 flutter_driver를 사용하던 프로젝트는 마이그레이션이 필요합니다. Migrating from flutter_driver 가이드를 참고하세요.


챕터 2: 통합 테스트 작성하기#

Why#

NOTE

통합 테스트는 위젯 테스트와 문법이 거의 같습니다. WidgetTester를 사용해서 탭, 스크롤, 텍스트 입력 등을 시뮬레이션합니다.

차이점은 실제 기기에서 실행된다는 것입니다.

What#

NOTE

통합 테스트 파일의 기본 구조입니다.

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:counter_app/main.dart';
void main() {
// 통합 테스트 바인딩 초기화 (필수!)
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end-to-end test', () {
testWidgets('tap on FAB, verify counter', (tester) async {
// 앱 로드
await tester.pumpWidget(const MyApp());
// 초기 상태 검증
expect(find.text('0'), findsOneWidget);
// FAB 찾기 (Key 사용)
final fab = find.byKey(const ValueKey('increment'));
// 탭 시뮬레이션
await tester.tap(fab);
// 프레임 갱신 대기
await tester.pumpAndSettle();
// 결과 검증
expect(find.text('1'), findsOneWidget);
});
});
}

핵심 포인트:

  1. IntegrationTestWidgetsFlutterBinding.ensureInitialized() 호출 필수
  2. pumpWidget()으로 앱 로드
  3. pumpAndSettle()로 모든 애니메이션 완료 대기

How#

TIP

테스트 대상 앱에 Key를 추가하면 요소를 쉽게 찾을 수 있습니다.

lib/main.dart
floatingActionButton: FloatingActionButton(
key: const Key('increment'), // 테스트에서 사용할 키
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),

테스트에서 Key로 요소를 찾습니다.

final fab = find.byKey(const ValueKey('increment'));
await tester.tap(fab);

다양한 찾기 방법:

// 텍스트로 찾기
find.text('Submit');
// 아이콘으로 찾기
find.byIcon(Icons.add);
// 타입으로 찾기
find.byType(FloatingActionButton);
// Semantics 라벨로 찾기
find.bySemanticsLabel('Increment');

Watch out#

WARNING

Key 충돌 주의: 같은 화면에 동일한 Key가 있으면 테스트가 실패합니다.

// 잘못된 예: 같은 Key 사용
ListView.builder(
itemBuilder: (context, index) {
return ListTile(
key: const Key('item'), // 모든 아이템에 같은 Key!
);
},
);
// 올바른 예: 고유한 Key 사용
ListView.builder(
itemBuilder: (context, index) {
return ListTile(
key: Key('item_$index'), // 인덱스로 구분
);
},
);

챕터 3: 통합 테스트 실행하기#

Why#

NOTE

통합 테스트는 다양한 플랫폼에서 실행할 수 있습니다.

  • 데스크톱 (macOS, Windows, Linux)
  • 모바일 (Android, iOS)
  • 웹 브라우저
  • Firebase Test Lab (클라우드)

What#

NOTE

기본 실행 명령어입니다.

Terminal window
flutter test integration_test/app_test.dart

여러 기기가 연결된 경우 선택 프롬프트가 표시됩니다.

Connected devices:
macOS (desktop) • macos • darwin-arm64
iPhone 15 (simulator) • ios
[1]: macOS (macos)
[2]: iPhone 15 (ios)
Please choose one (or "q" to quit): 1

How#

TIP

플랫폼별 실행 방법

데스크톱/모바일:

Terminal window
# 기본 실행
flutter test integration_test/app_test.dart
# 특정 디바이스 지정
flutter test integration_test/app_test.dart -d ios
flutter test integration_test/app_test.dart -d android

웹 브라우저:

ChromeDriver가 필요합니다.

Terminal window
# ChromeDriver 설치
npx @puppeteer/browsers install chromedriver@stable
# ChromeDriver 실행 (별도 터미널)
chromedriver --port=4444
# 테스트 드라이버 파일 생성: test_driver/integration_test.dart
test_driver/integration_test.dart
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();
Terminal window
# 웹 테스트 실행
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/app_test.dart \
-d chrome

헤드리스 모드 (CI 환경):

Terminal window
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/app_test.dart \
-d web-server

Watch out#

WARNING

테스트 후 앱 정리: 모바일에서 테스트가 끝나면 앱이 자동으로 삭제되지 않을 수 있습니다. 수동으로 제거하지 않으면 다음 테스트가 실패할 수 있습니다.


챕터 4: 성능 프로파일링#

Why#

NOTE

60fps를 유지하려면 각 프레임이 16ms 안에 렌더링되어야 합니다. 성능 프로파일링은 프레임 드랍이 발생하는 구간을 찾아줍니다.

jank(버벅거림): 프레임이 16ms를 초과해서 화면이 끊기는 현상

What#

NOTE

traceAction()3 메서드로 특정 작업의 성능을 기록합니다.

await binding.traceAction(() async {
// 성능을 측정할 작업
await tester.scrollUntilVisible(
itemFinder,
500.0,
scrollable: listFinder,
);
}, reportKey: 'scrolling_timeline');

reportKey로 여러 측정을 구분할 수 있습니다.

How#

TIP

전체 프로파일링 예제

integration_test/scrolling_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:your_package/main.dart';
void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('scrolling performance test', (tester) async {
await tester.pumpWidget(
MyApp(items: List<String>.generate(10000, (i) => 'Item $i')),
);
final listFinder = find.byType(Scrollable);
final itemFinder = find.byKey(const ValueKey('item_50_text'));
// 스크롤 성능 측정
await binding.traceAction(() async {
await tester.scrollUntilVisible(
itemFinder,
500.0,
scrollable: listFinder,
);
}, reportKey: 'scrolling_timeline');
});
}

결과 저장을 위한 드라이버 파일:

test_driver/perf_driver.dart
import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() {
return integrationDriver(
responseDataCallback: (data) async {
if (data != null) {
final timeline = driver.Timeline.fromJson(
data['scrolling_timeline'] as Map<String, dynamic>,
);
final summary = driver.TimelineSummary.summarize(timeline);
// 타임라인과 요약을 파일로 저장
await summary.writeTimelineToFile(
'scrolling_timeline',
pretty: true,
includeSummary: true,
);
}
},
);
}

실행:

Terminal window
flutter drive \
--driver=test_driver/perf_driver.dart \
--target=integration_test/scrolling_test.dart \
--profile # 프로필 모드로 빌드

Watch out#

WARNING

—profile 옵션 필수: 디버그 모드는 성능이 낮으므로 정확한 측정이 안 됩니다. 항상 --profile 옵션을 사용하세요.

모바일에서 —no-dds 옵션: 모바일 기기에서 실행할 때 DDS 비활성화가 필요할 수 있습니다.

Terminal window
flutter drive --no-dds --profile ...

챕터 5: 결과 분석#

Why#

NOTE

프로파일링 결과는 두 가지 파일로 저장됩니다.

  1. *_summary.timeline_summary.json: 성능 요약
  2. *_timeline.timeline.json: 상세 타임라인 데이터

What#

NOTE

성능 요약 예시:

{
"average_frame_build_time_millis": 4.26,
"worst_frame_build_time_millis": 21.0,
"missed_frame_build_budget_count": 2,
"average_frame_rasterizer_time_millis": 5.52,
"worst_frame_rasterizer_time_millis": 51.0,
"missed_frame_rasterizer_budget_count": 10,
"frame_count": 54
}

주요 지표:

지표의미목표
average_frame_build_time평균 빌드 시간<8ms
worst_frame_build_time최악의 빌드 시간<16ms
missed_frame_build_budget_count예산 초과 프레임 수0에 가깝게
average_frame_rasterizer_time평균 래스터라이저 시간<8ms

How#

TIP

상세 타임라인 분석

chrome://tracing에서 타임라인 JSON 파일을 열 수 있습니다.

  1. Chrome 브라우저에서 chrome://tracing 열기
  2. “Load” 버튼 클릭
  3. *_timeline.timeline.json 파일 선택
  4. 타임라인에서 긴 프레임 찾기

CI에서 성능 회귀 감지:

// 성능 기준값 검사
final summary = driver.TimelineSummary.summarize(timeline);
final avgBuildTime = summary.computeAverageFrameBuildTimeMillis();
if (avgBuildTime > 8.0) {
throw Exception('Performance regression: avg build time is $avgBuildTime ms');
}

챕터 6: Firebase Test Lab 연동#

Why#

NOTE

실제 기기가 없어도 다양한 디바이스에서 테스트할 수 있습니다. Firebase Test Lab은 수백 종류의 실제 Android/iOS 기기를 제공합니다.

What#

NOTE

Android APK 빌드:

Terminal window
cd android
# 디버그 APK 빌드
flutter build apk --debug
# 테스트 APK 빌드
./gradlew app:assembleAndroidTest
# 통합 테스트용 APK 빌드
./gradlew app:assembleDebug -Ptarget=integration_test/app_test.dart

Firebase Console 업로드:

  1. Firebase Console → Quality → Test Lab
  2. “Run a test” 클릭
  3. Instrumentation 선택
  4. App APK와 Test APK 업로드

How#

TIP

Firebase CLI로 자동화:

Terminal window
# Firebase CLI 설치
npm install -g firebase-tools
# 로그인
firebase login
# 테스트 실행
gcloud firebase test android run \
--type instrumentation \
--app build/app/outputs/apk/debug/app-debug.apk \
--test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk \
--timeout 5m

CI/CD 통합 (GitHub Actions 예시):

- name: Run integration tests on Firebase Test Lab
run: |
gcloud firebase test android run \
--type instrumentation \
--app app-debug.apk \
--test app-debug-androidTest.apk \
--device model=Pixel6,version=33

한계#

통합 테스트는 강력하지만 몇 가지 제한이 있습니다.

실행 시간: 단위/위젯 테스트보다 훨씬 느립니다. 모든 테스트를 통합 테스트로 작성하면 안 됩니다.

웹 성능 프로파일링: 웹에서는 traceAction() 성능 측정이 지원되지 않습니다. 웹 성능은 Chrome DevTools를 사용하세요.

테스트 피라미드 권장 비율:

/\
/ \ 통합 테스트 (10%)
/----\
/ \ 위젯 테스트 (30%)
/--------\
단위 테스트 (60%)

다음 튜토리얼에서는 플러그인 코드를 테스트하는 방법을 알아봅니다.

Footnotes#

  1. integration_test(인티그레이션 테스트): Flutter SDK에 포함된 통합 테스트 패키지로, 실제 기기에서 앱 전체를 테스트한다.

  2. flutter_driver(플러터 드라이버): 통합 테스트를 위한 이전 방식의 패키지로, 현재는 integration_test로 대체되었다.

  3. traceAction(트레이스 액션): 특정 작업 중의 성능 타임라인을 기록하는 메서드다.

공유

이 글이 도움이 되었다면 다른 사람과 공유해주세요!

Flutter 튜토리얼 46편: 통합 테스트
https://moodturnpost.net/posts/flutter/flutter-integration-testing/
작성자
Moodturn
게시일
2026-01-08
Moodturn

목차