1. 소개
Flutter는 하나의 코드베이스를 사용해 모바일, 웹, 데스크톱을 대상으로 아름다운 네이티브 컴파일 애플리케이션을 빌드하기 위한 Google의 UI 툴킷입니다.
이 Codelab에서는 간단한 Flutter 앱을 빌드하고 테스트합니다. 앱은 상태를 관리하는 데 Provider 패키지를 사용합니다.
학습할 내용
- 위젯 테스트 프레임워크를 사용하여 위젯 테스트를 만드는 방법
integration_test
라이브러리를 사용하여 앱의 UI와 성능을 테스트하는 통합 테스트를 만드는 방법- 단위 테스트를 활용하여 데이터 클래스(Provider)를 테스트하는 방법
빌드할 프로그램
이 Codelab에서는 먼저, 항목 목록이 있는 간단한 애플리케이션을 빌드합니다. 테스트에 바로 들어갈 수 있도록 소스 코드를 제공합니다. 앱은 다음과 같은 작업을 지원합니다.
- 즐겨찾기에 항목 추가
- 즐겨찾기 목록 보기
- 즐겨찾기 목록에서 항목 삭제
앱이 완성되면 다음과 같은 테스트를 작성합니다.
| Android에서 실행 중인 앱의 GIF |
이 Codelab에서 학습하고 싶은 내용은 무엇인가요?
2. Flutter 개발 환경 설정
이 실습을 완료하려면 Flutter SDK와 편집기라는 두 가지 소프트웨어가 필요합니다.
다음 기기 중 하나를 사용하여 이 Codelab을 실행할 수 있습니다.
- 컴퓨터에 연결되어 있고 개발자 모드로 설정된 실제 Android 또는 iOS 기기
- iOS 시뮬레이터(Xcode 도구 설치 필요)
- Android Emulator(Android 스튜디오 설정 필요)
- 브라우저(디버깅 시 Chrome 필요)
- Windows, Linux 또는 macOS 데스크톱 애플리케이션. 앱을 배포할 플랫폼에서 개발해야 합니다. 따라서 Windows 데스크톱 앱을 개발하려면 적합한 빌드 체인에 액세스할 수 있도록 Windows에서 개발해야 합니다. 자세한 운영체제별 요구사항은 docs.flutter.dev/desktop을 참고하세요.
3. 시작하기
새 Flutter 앱 만들기 및 종속 항목 업데이트
이 Codelab에서는 Flutter 모바일 앱을 테스트하는 데 중점을 둡니다. 따라서 복사하여 붙여넣은 소스 파일을 사용해서 테스트할 앱을 빠르게 만듭니다. 그런 다음, Codelab의 나머지 부분에서는 다양한 종류의 테스트를 알아보는 데 중점을 둡니다.
첫 번째 Flutter 앱 시작하기의 안내를 사용하거나 명령줄에 다음과 같이 입력하여 간단한 템플릿 형식의 Flutter 앱을 만듭니다.
$ flutter create testing_app
명령줄에 pub 종속 항목을 추가합니다. 상태 관리를 쉽게 하기 위해 provider
를 추가합니다.
$ cd testing_app $ flutter pub add provider Resolving dependencies... collection 1.17.0 (1.17.1 available) js 0.6.5 (0.6.7 available) matcher 0.12.13 (0.12.14 available) meta 1.8.0 (1.9.0 available) + nested 1.0.0 path 1.8.2 (1.8.3 available) + provider 6.0.5 test_api 0.4.16 (0.4.18 available) Changed 2 dependencies!
기기와 에뮬레이터에서 Flutter 코드를 자율적으로 구동하여 테스트하기 위해 integration_test
를 추가합니다.
$ flutter pub add --dev --sdk=flutter integration_test Resolving dependencies... + archive 3.3.2 (3.3.6 available) collection 1.17.0 (1.17.1 available) + crypto 3.0.2 + file 6.1.4 + flutter_driver 0.0.0 from sdk flutter + fuchsia_remote_debug_protocol 0.0.0 from sdk flutter + integration_test 0.0.0 from sdk flutter js 0.6.5 (0.6.7 available) matcher 0.12.13 (0.12.14 available) meta 1.8.0 (1.9.0 available) path 1.8.2 (1.8.3 available) + platform 3.1.0 + process 4.2.4 + sync_http 0.3.1 test_api 0.4.16 (0.4.18 available) + typed_data 1.3.1 + vm_service 9.4.0 (11.0.1 available) + webdriver 3.0.1 (3.0.2 available) Changed 12 dependencies!
실제 기기와 에뮬레이터에서 실행되는 Flutter 애플리케이션을 테스트하는 고급 API를 위해 flutter_driver
를 추가합니다.
$ flutter pub add --dev --sdk=flutter flutter_driver Resolving dependencies... archive 3.3.2 (3.3.6 available) collection 1.17.0 (1.17.1 available) js 0.6.5 (0.6.7 available) matcher 0.12.13 (0.12.14 available) meta 1.8.0 (1.9.0 available) path 1.8.2 (1.8.3 available) test_api 0.4.16 (0.4.18 available) vm_service 9.4.0 (11.0.1 available) webdriver 3.0.1 (3.0.2 available) Got dependencies!
일반적인 테스트 도구를 위해 test
를 추가합니다.
$ flutter pub add --dev test Resolving dependencies... + _fe_analyzer_shared 52.0.0 + analyzer 5.4.0 archive 3.3.2 (3.3.6 available) + args 2.3.2 collection 1.17.0 (1.17.1 available) + convert 3.1.1 + coverage 1.6.3 + frontend_server_client 3.2.0 + glob 2.1.1 + http_multi_server 3.2.1 + http_parser 4.0.2 + io 1.0.4 js 0.6.5 (0.6.7 available) + logging 1.1.1 matcher 0.12.13 (0.12.14 available) meta 1.8.0 (1.9.0 available) + mime 1.0.4 + node_preamble 2.0.1 + package_config 2.1.0 path 1.8.2 (1.8.3 available) + pool 1.5.1 + pub_semver 2.1.3 + shelf 1.4.0 + shelf_packages_handler 3.0.1 + shelf_static 1.1.1 + shelf_web_socket 1.0.3 + source_map_stack_trace 2.1.1 + source_maps 0.10.11 + test 1.22.0 (1.23.0 available) test_api 0.4.16 (0.4.18 available) + test_core 0.4.20 (0.4.23 available) vm_service 9.4.0 (11.0.1 available) + watcher 1.0.2 + web_socket_channel 2.3.0 webdriver 3.0.1 (3.0.2 available) + webkit_inspection_protocol 1.2.0 + yaml 3.1.1 Changed 28 dependencies!
앱 탐색을 처리하기 위해 go_router를 추가합니다.
$ flutter pub add go_router Resolving dependencies... archive 3.3.2 (3.3.6 available) collection 1.17.0 (1.17.1 available) + flutter_web_plugins 0.0.0 from sdk flutter + go_router 6.0.4 js 0.6.5 (0.6.7 available) matcher 0.12.13 (0.12.14 available) meta 1.8.0 (1.9.0 available) path 1.8.2 (1.8.3 available) test 1.22.0 (1.23.0 available) test_api 0.4.16 (0.4.18 available) test_core 0.4.20 (0.4.23 available) vm_service 9.4.0 (11.0.1 available) webdriver 3.0.1 (3.0.2 available) Changed 2 dependencies!
다음 종속 항목이 pubspec.yaml에 추가되어 있어야 합니다.
dependencies
아래는 다음과 같습니다.
dependencies: provider: ^6.0.5 go_router: ^6.0.4
dev_dependencies
아래는 다음과 같습니다.
dev_dependencies: integration_test: sdk: flutter flutter_driver: sdk: flutter test: ^1.22.0
선택한 코드 편집기에서 프로젝트를 열고 앱을 실행합니다. 또는 다음과 같이 명령줄에서 앱을 실행합니다.
$ flutter run
4. 앱 빌드
다음으로, 앱을 테스트할 수 있도록 빌드합니다. 앱에는 다음과 같은 파일이 포함됩니다.
lib/models/favorites.dart
- 즐겨찾기 목록의 모델 클래스를 만듭니다.lib/screens/favorites.dart
- 즐겨찾기 목록의 레이아웃을 만듭니다.lib/screens/home.dart
- 항목 목록을 만듭니다.lib/main.dart
- 앱이 시작되는 기본 파일입니다.
첫째, lib/models/favorites.dart
에 Favorites
모델 만들기
lib
디렉터리에 models
라는 새로운 디렉터리를 만든 다음 favorites.dart
라는 새 파일을 만듭니다. 이 파일에 다음 코드를 추가합니다.
lib/models/favorites.dart
import 'package:flutter/material.dart';
/// The [Favorites] class holds a list of favorite items saved by the user.
class Favorites extends ChangeNotifier {
final List<int> _favoriteItems = [];
List<int> get items => _favoriteItems;
void add(int itemNo) {
_favoriteItems.add(itemNo);
notifyListeners();
}
void remove(int itemNo) {
_favoriteItems.remove(itemNo);
notifyListeners();
}
}
lib/screens/favorites.dart
에 즐겨찾기 페이지 추가하기
lib
디렉터리에 screens
라는 새로운 디렉터리를 만들고 그 디렉터리에 favorites.dart
라는 새 파일을 만듭니다. 이 파일에 다음 코드를 추가합니다.
lib/screens/favorites.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/favorites.dart';
class FavoritesPage extends StatelessWidget {
const FavoritesPage({super.key});
static String routeName = 'favorites_page';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Favorites'),
),
body: Consumer<Favorites>(
builder: (context, value, child) => ListView.builder(
itemCount: value.items.length,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (context, index) => FavoriteItemTile(value.items[index]),
),
),
);
}
}
class FavoriteItemTile extends StatelessWidget {
const FavoriteItemTile(this.itemNo, {super.key});
final int itemNo;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
),
title: Text(
'Item $itemNo',
key: Key('favorites_text_$itemNo'),
),
trailing: IconButton(
key: Key('remove_icon_$itemNo'),
icon: const Icon(Icons.close),
onPressed: () {
Provider.of<Favorites>(context, listen: false).remove(itemNo);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Removed from favorites.'),
duration: Duration(seconds: 1),
),
);
},
),
),
);
}
}
lib/screens/home.dart
에 홈페이지 추가하기
lib/screens
디렉터리에 home.dart
라는 또 다른 파일을 새로 만듭니다. lib/screens/home.dart
에 다음 코드를 추가합니다.
lib/screens/home.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../models/favorites.dart';
import 'favorites.dart';
class HomePage extends StatelessWidget {
static String routeName = '/';
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Testing Sample'),
actions: <Widget>[
TextButton.icon(
onPressed: () {
context.go('/${FavoritesPage.routeName}');
},
icon: const Icon(Icons.favorite_border),
label: const Text('Favorites'),
),
],
),
body: ListView.builder(
itemCount: 100,
cacheExtent: 20.0,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (context, index) => ItemTile(index),
),
);
}
}
class ItemTile extends StatelessWidget {
final int itemNo;
const ItemTile(this.itemNo, {super.key});
@override
Widget build(BuildContext context) {
var favoritesList = Provider.of<Favorites>(context);
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
),
title: Text(
'Item $itemNo',
key: Key('text_$itemNo'),
),
trailing: IconButton(
key: Key('icon_$itemNo'),
icon: favoritesList.items.contains(itemNo)
? const Icon(Icons.favorite)
: const Icon(Icons.favorite_border),
onPressed: () {
!favoritesList.items.contains(itemNo)
? favoritesList.add(itemNo)
: favoritesList.remove(itemNo);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(favoritesList.items.contains(itemNo)
? 'Added to favorites.'
: 'Removed from favorites.'),
duration: const Duration(seconds: 1),
),
);
},
),
),
);
}
}
lib/main.dart
의 내용 바꾸기
lib/main.dart
의 내용을 다음 코드로 바꿉니다.
lib/main.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'models/favorites.dart';
import 'screens/favorites.dart';
import 'screens/home.dart';
void main() {
runApp(const TestingApp());
}
final _router = GoRouter(
routes: [
GoRoute(
path: HomePage.routeName,
builder: (context, state) {
return const HomePage();
},
routes: [
GoRoute(
path: FavoritesPage.routeName,
builder: (context, state) {
return const FavoritesPage();
},
),
],
),
],
);
class TestingApp extends StatelessWidget {
const TestingApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Favorites>(
create: (context) => Favorites(),
child: MaterialApp.router(
title: 'Testing Sample',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
routerConfig: _router,
),
);
}
}
이제 앱이 완성되었지만 테스트는 하지 않았습니다.
앱을 실행합니다. 앱을 실행하면 다음 스크린샷과 같이 표시됩니다.
앱에 항목 목록이 표시됩니다. 아무 행에서나 하트 모양 아이콘을 탭하여 하트를 채우고 항목을 즐겨찾기 목록에 추가합니다. AppBar
의 Favorites 버튼을 탭하면 즐겨찾기 목록이 포함된 두 번째 화면으로 이동합니다.
이제 앱을 테스트할 준비가 되었습니다. 다음 단계에서는 앱 테스트를 시작합니다.
5. Provider 단위 테스트
먼저, favorites
모델에 대한 단위 테스트를 시작해 보겠습니다. 단위 테스트란 무엇인가요? 단위 테스트는 소프트웨어의 모든 개별 단위(함수, 객체 또는 위젯)가 의도한 동작을 올바르게 실행하는지 확인합니다.
Flutter 앱의 모든 테스트 파일은 test
디렉터리에 있습니다(통합 테스트 제외).
test/widget_test.dart
삭제
테스트를 시작하기 전에 widget_test.dart
파일을 삭제합니다. 자체 테스트 파일을 추가하겠습니다.
새 테스트 파일 만들기
먼저, Favorites
모델의 add()
메서드를 테스트하여 새 항목이 목록에 추가되었는지와 목록에 변경사항이 반영되었는지 확인합니다. 규칙에 따라 test
디렉터리의 디렉터리 구조는 lib
디렉터리의 구조를 모방하고, Dart 파일은 같은 이름에 _test
를 추가합니다.
test
디렉터리에 models
디렉터리를 만듭니다. 이 새 디렉터리에 다음과 같은 내용으로 favorites_test.dart
파일을 만듭니다.
test/models/favorites_test.dart
import 'package:test/test.dart';
import 'package:testing_app/models/favorites.dart';
void main() {
group('Testing App Provider', () {
var favorites = Favorites();
test('A new item should be added', () {
var number = 35;
favorites.add(number);
expect(favorites.items.contains(number), true);
});
});
}
Flutter 테스트 프레임워크를 사용하면 그룹 내에서 서로 관련된 유사한 테스트를 결합할 수 있습니다. lib
디렉터리에 있는 상응하는 파일의 다양한 부분을 테스트하기 위한 단일 테스트 파일에는 여러 그룹이 있을 수 있습니다.
test()
메서드는 위치가 지정된 2개의 매개변수를 사용합니다. 하나는 테스트의 description
이며, 다른 하나는 실제로 테스트를 작성하는 callback
입니다.
목록에서 항목을 삭제하는 것을 테스트합니다. 동일한 Testing App Provider
그룹에 다음 테스트를 삽입합니다.
test/models/favorites_test.dart
test('An item should be removed', () {
var number = 45;
favorites.add(number);
expect(favorites.items.contains(number), true);
favorites.remove(number);
expect(favorites.items.contains(number), false);
});
테스트 실행
명령줄에서 프로젝트의 루트 디렉터리로 이동하여 다음 명령어를 입력합니다.
$ flutter test test/models/favorites_test.dart
모든 것이 제대로 작동한다면 다음과 유사한 메시지가 표시됩니다.
00:06 +2: All tests passed!
전체 테스트 파일은 test/models/favorites_test.dart
에서 확인할 수 있습니다.
단위 테스트에 관한 자세한 내용은 단위 테스트 소개를 참고하세요.
6. 위젯 테스트
이 단계에서는 테스트 위젯에 코드를 추가합니다. 위젯 테스트는 Flutter의 고유한 테스트로, 격리된 방식으로 각 위젯을 테스트할 수 있습니다. 이 단계에서는 HomePage
화면과 FavoritesPage
화면을 개별적으로 테스트합니다.
위젯 테스트는 test()
함수 대신 testWidget()
함수를 사용합니다. test()
함수와 마찬가지로 testWidget()
함수는 두 개의 매개변수, description,
과 callback
을 사용하며, 콜백은 WidgetTester
를 인수로 사용합니다.
위젯 테스트는 TestFlutterWidgetsBinding
을 사용합니다. 이 클래스는 실행 중인 앱에서 사용하지만, 앱 내에서 실행하지는 않는 리소스와 동일한 리소스를 위젯에 제공합니다. 예를 들어, 화면 크기 정보, 애니메이션 예약 기능 등입니다. 대신, 위젯을 인스턴스화하는 데는 가상 환경을 사용하고 실행하면서 결과를 테스트합니다. 여기서 pumpWidget
은 애플리케이션에서와 같이 특정 위젯을 마운트하고 측정하도록 프레임워크에 지시함으로써 프로세스를 시작합니다.
위젯 테스트 프레임워크는 위젯을 찾는 파인더(예: text()
, byType()
, byIcon().
)를 제공합니다. 또한, 결과를 확인하기 위해 매처도 제공합니다.
먼저, HomePage
위젯을 테스트합니다.
새 테스트 파일 만들기
첫 번째 테스트는 HomePage
스크롤 기능이 제대로 작동하는지 확인합니다.
test
디렉터리에 새 파일을 만들어 이름을 home_test.dart
로 지정합니다. 새로 만든 파일에서 다음 코드를 추가합니다.
test/home_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:testing_app/models/favorites.dart';
import 'package:testing_app/screens/home.dart';
Widget createHomeScreen() => ChangeNotifierProvider<Favorites>(
create: (context) => Favorites(),
child: const MaterialApp(
home: HomePage(),
),
);
void main() {
group('Home Page Widget Tests', () {
testWidgets('Testing Scrolling', (tester) async {
await tester.pumpWidget(createHomeScreen());
expect(find.text('Item 0'), findsOneWidget);
await tester.fling(
find.byType(ListView),
const Offset(0, -200),
3000,
);
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsNothing);
});
});
}
createHomeScreen()
함수를 사용하여 ChangeNotifierProvider에 래핑된 MaterialApp에서 테스트할 위젯을 로드하는 앱을 만듭니다. 위젯 트리에서 HomePage 위젯의 상위에 이러한 두 위젯이 모두 있어야 합니다. 그래야 HomePage 위젯이 두 위젯에서 상속받고 두 위젯이 제공하는 데이터에 액세스할 수 있습니다. 이 함수를 pumpWidget()
함수에 매개변수로 전달합니다.
그런 다음, 프레임워크가 화면에 렌더링된 ListView
를 찾을 수 있는지 테스트합니다.
다음 코드 스니펫을 home_test.dart
에 추가합니다.
test/home_test.dart
group('Home Page Widget Tests', () {
// BEGINNING OF NEW CONTENT
testWidgets('Testing if ListView shows up', (tester) async {
await tester.pumpWidget(createHomeScreen());
expect(find.byType(ListView), findsOneWidget);
});
// END OF NEW CONTENT
testWidgets('Testing Scrolling', (tester) async {
await tester.pumpWidget(createHomeScreen());
expect(find.text('Item 0'), findsOneWidget);
await tester.fling(
find.byType(ListView),
const Offset(0, -200),
3000,
);
await tester.pumpAndSettle();
expect(find.text('Item 0'), findsNothing);
});
});
테스트 실행
먼저, 단위 테스트를 실행한 것과 동일한 방식으로 명령어를 사용하여 테스트를 실행합니다.
$ flutter test test/home_test.dart
테스트는 신속하게 실행되어야 하며 다음과 같은 메시지가 표시됩니다.
00:02 +2: All tests passed!
또한, 기기나 에뮬레이터를 사용하여 위젯 테스트를 실행할 수 있습니다. 이렇게 하면 테스트가 실행되는 것을 관찰할 수 있습니다. 핫 리스타트 기능도 사용할 수 있습니다.
기기를 연결하거나 에뮬레이터를 시작합니다. 데스크톱 애플리케이션으로 테스트를 실행할 수도 있습니다.
명령줄에서 프로젝트의 루트 디렉터리로 이동하여 다음 명령어를 입력합니다.
$ flutter run test/home_test.dart
테스트를 실행할 기기를 선택해야 할 수도 있습니다. 그런 경우에는 다음 안내에 따라 기기를 선택합니다.
Multiple devices found:
Linux (desktop) • linux • linux-x64 • Ubuntu 22.04.1 LTS 5.15.0-58-generic
Chrome (web) • chrome • web-javascript • Google Chrome 109.0.5414.119
[1]: Linux (linux)
[2]: Chrome (chrome)
Please choose one (To quit, press "q/Q"):
모든 것이 제대로 작동한다면 다음과 비슷한 출력이 표시됩니다.
Launching test/home_test.dart on Linux in debug mode...
Building Linux application...
flutter: 00:00 +0: Home Page Widget Tests Testing if ListView shows up
Syncing files to device Linux... 62ms
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
💪 Running with sound null safety 💪
An Observatory debugger and profiler on Linux is available at: http://127.0.0.1:35583/GCpdLBqf2UI=/
flutter: 00:00 +1: Home Page Widget Tests Testing Scrolling
The Flutter DevTools debugger and profiler on Linux is available at:
http://127.0.0.1:9100?uri=http://127.0.0.1:35583/GCpdLBqf2UI=/
flutter: 00:02 +2: All tests passed!
다음으로, 테스트 파일을 변경하고 Shift + R
키를 눌러 앱을 핫 리스타트하고 모든 테스트를 다시 실행합니다. 애플리케이션을 중지하면 안 됩니다.
HomePage 위젯을 테스트하는 그룹에 테스트를 더 추가합니다. 다음 테스트를 파일에 복사합니다.
test/home_test.dart
testWidgets('Testing IconButtons', (tester) async {
await tester.pumpWidget(createHomeScreen());
expect(find.byIcon(Icons.favorite), findsNothing);
await tester.tap(find.byIcon(Icons.favorite_border).first);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Added to favorites.'), findsOneWidget);
expect(find.byIcon(Icons.favorite), findsWidgets);
await tester.tap(find.byIcon(Icons.favorite).first);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Removed from favorites.'), findsOneWidget);
expect(find.byIcon(Icons.favorite), findsNothing);
});
이 테스트는 IconButton
을 탭하면 Icons.favorite_border
(비어 있는 하트)에서 Icons.favorite
(채워진 하트)으로 변경되고 다시 탭하면 Icons.favorite_border
로 되돌아가는지 확인합니다.
Shift + R
키를 입력합니다. 그러면 앱이 핫 리스타트되고 모든 테스트가 다시 실행됩니다.
전체 테스트 파일은 test/home_test.dart
.
에서 확인할 수 있습니다.
동일한 프로세스를 사용하여 다음 코드로 FavoritesPage
를 테스트합니다. 동일한 단계를 따르고 실행합니다.
test/favorites_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:testing_app/models/favorites.dart';
import 'package:testing_app/screens/favorites.dart';
late Favorites favoritesList;
Widget createFavoritesScreen() => ChangeNotifierProvider<Favorites>(
create: (context) {
favoritesList = Favorites();
return favoritesList;
},
child: const MaterialApp(
home: FavoritesPage(),
),
);
void addItems() {
for (var i = 0; i < 10; i += 2) {
favoritesList.add(i);
}
}
void main() {
group('Favorites Page Widget Tests', () {
testWidgets('Test if ListView shows up', (tester) async {
await tester.pumpWidget(createFavoritesScreen());
addItems();
await tester.pumpAndSettle();
expect(find.byType(ListView), findsOneWidget);
});
testWidgets('Testing Remove Button', (tester) async {
await tester.pumpWidget(createFavoritesScreen());
addItems();
await tester.pumpAndSettle();
var totalItems = tester.widgetList(find.byIcon(Icons.close)).length;
await tester.tap(find.byIcon(Icons.close).first);
await tester.pumpAndSettle();
expect(tester.widgetList(find.byIcon(Icons.close)).length,
lessThan(totalItems));
expect(find.text('Removed from favorites.'), findsOneWidget);
});
});
}
이 테스트는 닫기(삭제) 버튼을 누를 때 항목이 사라지는지 확인합니다.
위젯 테스트에 관한 자세한 내용은 다음을 참고하세요.
7. 통합 테스트로 앱 UI 테스트
통합 테스트는 앱의 개별 요소가 전체적으로 연동되는 방식을 테스트하는 데 사용됩니다. integration_test
라이브러리는 Flutter로 통합 테스트를 실행하는 데 사용됩니다. 이 라이브러리는 Flutter의 Selenium WebDriver, Protractor, Espresso, Earl Gray 버전입니다. 이 패키지는 내부적으로 flutter_driver
를 사용하여 기기에서 테스트를 구동합니다.
Flutter로 통합 테스트를 작성하는 것은 통합 테스트가 대상 기기라고 하는 휴대기기, 브라우저 또는 데스크톱 애플리케이션에서 실행된다는 점을 제외하고 위젯 테스트를 작성하는 것과 비슷합니다.
테스트 작성
프로젝트 루트 디렉터리에 integration_test
라는 디렉터리를 만들고 그 디렉터리에 app_test.dart
라는 새 파일을 만듭니다.
integration_test/app_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:testing_app/main.dart';
void main() {
group('Testing App', () {
testWidgets('Favorites operations test', (tester) async {
await tester.pumpWidget(const TestingApp());
final iconKeys = [
'icon_0',
'icon_1',
'icon_2',
];
for (var icon in iconKeys) {
await tester.tap(find.byKey(ValueKey(icon)));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Added to favorites.'), findsOneWidget);
}
await tester.tap(find.text('Favorites'));
await tester.pumpAndSettle();
final removeIconKeys = [
'remove_icon_0',
'remove_icon_1',
'remove_icon_2',
];
for (final iconKey in removeIconKeys) {
await tester.tap(find.byKey(ValueKey(iconKey)));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Removed from favorites.'), findsOneWidget);
}
});
});
}
테스트 실행
기기를 연결하거나 에뮬레이터를 시작합니다. 데스크톱 애플리케이션으로 테스트를 실행할 수도 있습니다.
명령줄에서 프로젝트의 루트 디렉터리로 이동하여 다음 명령어를 입력합니다.
$ flutter test integration_test/app_test.dart
모든 것이 제대로 작동한다면 다음과 비슷한 출력이 표시됩니다.
Multiple devices found:
Linux (desktop) • linux • linux-x64 • Ubuntu 22.04.1 LTS 5.15.0-58-generic
Chrome (web) • chrome • web-javascript • Google Chrome 109.0.5414.119
[1]: Linux (linux)
[2]: Chrome (chrome)
Please choose one (To quit, press "q/Q"): 1
00:00 +0: loading /home/miquel/tmp/testing_app/integration_test/app_test.dart B00:08 +0: loading /home/miquel/tmp/testing_app/integration_test/app_test.dart
00:26 +1: All tests passed!
8. Flutter Driver를 사용하여 앱 성능 테스트
성능 테스트 작성
integration_test 폴더에 다음 내용을 포함하는 perf_test.dart라는 새로운 테스트 파일을 만듭니다.
integration_test/perf_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:testing_app/main.dart';
void main() {
group('Testing App Performance', () {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
testWidgets('Scrolling test', (tester) async {
await tester.pumpWidget(const TestingApp());
final listFinder = find.byType(ListView);
await binding.traceAction(() async {
await tester.fling(listFinder, const Offset(0, -500), 10000);
await tester.pumpAndSettle();
await tester.fling(listFinder, const Offset(0, 500), 10000);
await tester.pumpAndSettle();
}, reportKey: 'scrolling_summary');
});
});
}
ensureInitialized()
함수는 통합 테스트 드라이버가 초기화되고 필요시 드라이버를 다시 초기화하는지 확인합니다. framePolicy
를 fullyLive
로 설정하면 애니메이션 코드를 테스트하는 데 좋습니다.
이 테스트는 항목 목록을 매우 빠르게 스크롤한 후 위로 끝까지 스크롤합니다. traceAction()
함수는 작업을 기록하고 타임라인 요약을 생성합니다.
성능 결과 캡처
결과를 캡처하려면 perf_driver.dart
라는 파일이 포함된 test_driver
폴더를 만들고 다음 코드를 파일에 추가합니다.
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_summary'] as Map<String, dynamic>);
final summary = driver.TimelineSummary.summarize(timeline);
await summary.writeTimelineToFile(
'scrolling_summary',
pretty: true,
includeSummary: true,
);
}
},
);
}
테스트 실행
기기를 연결하거나 에뮬레이터를 시작합니다.
명령줄에서 프로젝트의 루트 디렉터리로 이동하여 다음 명령어를 입력합니다.
$ flutter drive \
--driver=test_driver/perf_driver.dart \
--target=integration_test/perf_test.dart \
--profile \
--no-dds
모든 것이 제대로 작동한다면 다음과 비슷한 출력이 표시됩니다.
Running "flutter pub get" in testing_app...
Resolving dependencies...
archive 3.3.2 (3.3.6 available)
collection 1.17.0 (1.17.1 available)
js 0.6.5 (0.6.7 available)
matcher 0.12.13 (0.12.14 available)
meta 1.8.0 (1.9.0 available)
path 1.8.2 (1.8.3 available)
test 1.22.0 (1.23.0 available)
test_api 0.4.16 (0.4.18 available)
test_core 0.4.20 (0.4.23 available)
vm_service 9.4.0 (11.0.1 available)
webdriver 3.0.1 (3.0.2 available)
Got dependencies!
Running Gradle task 'assembleProfile'... 1,379ms
✓ Built build/app/outputs/flutter-apk/app-profile.apk (14.9MB).
Installing build/app/outputs/flutter-apk/app-profile.apk... 222ms
I/flutter ( 6125): 00:04 +1: Testing App Performance (tearDownAll)
I/flutter ( 6125): 00:04 +2: All tests passed!
All tests passed.
테스트가 성공적으로 완료되면 프로젝트 루트에 있는 빌드 디렉터리에 두 개의 파일이 생깁니다.
scrolling_summary.timeline_summary.json
: 요약을 포함합니다. 텍스트 편집기로 파일을 열어 안에 포함된 정보를 검토합니다.scrolling_summary.timeline.json
: 전체 타임라인 데이터를 포함합니다.
통합 테스트에 관한 더 자세한 세부정보는 다음을 방문하세요.
9. 축하합니다.
Codelab을 완료하고 Flutter 앱을 테스트하는 다양한 방법을 알아보았습니다.
학습한 내용
- 단위 테스트를 활용하여 Provider를 테스트하는 방법
- 위젯 테스트 프레임워크를 사용하여 위젯을 테스트하는 방법
- 통합 테스트를 사용하여 앱의 UI를 테스트하는 방법
- 통합 테스트를 사용하여 앱 성능을 테스트하는 방법
Flutter에서 테스트하는 방법에 관해 자세히 알아보려면 다음을 참고하세요.