티스토리 뷰
많은 위젯들은 정보를 표시할 뿐만 아니라 사용자 상호작용에도 반응합니다. 여기에는 탭할 수 있는 버튼이나 텍스트를 입력할 수 있는 TextField 같은 위젯들이 포함됩니다.
이러한 상호작용을 테스트하기 위해서는 테스트 환경에서 이를 시뮬레이션할 수 있는 방법이 필요합니다. 이를 위해 WidgetTester 라이브러리를 사용합니다.
WidgetTester는 다음과 같은 메서드를 제공합니다:
- enterText()
- tap()
- drag()
많은 경우 사용자 상호작용은 앱의 상태를 업데이트합니다. 테스트 환경에서는 상태가 변경되더라도 Flutter가 자동으로 위젯을 다시 빌드하지 않습니다. 사용자 상호작용을 시뮬레이션한 후 위젯 트리를 다시 빌드하기 위해서는 WidgetTester가 제공하는 pump() 또는 pumpAndSettle() 메서드를 호출해야 합니다.
이 레시피는 다음과 같은 단계를 따릅니다:
1. 테스트할 위젯 생성
2. 텍스트 필드에 텍스트 입력
3. 버튼 탭 시 할 일이 추가되는지 확인
4. 스와이프하여 삭제 시 리스트에서 제거되는지 확인
1. 테스트할 위젯 생성
이 예제에서는 세 가지 기능을 테스트하는 기본적인 할 일(todo) 앱을 생성합니다:
1. TextField에 텍스트 입력
2. FloatingActionButton을 탭하여 텍스트를 할 일 리스트에 추가
3. 스와이프하여 아이템을 삭제
테스트에 집중하기 위해 이 레시피에서는 할 일 앱을 만드는 자세한 가이드를 제공하지 않습니다.
class TodoList extends StatefulWidget {
const TodoList({super.key});
@override
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
static const _appTitle = 'Todo List';
final todos = <String>[];
final controller = TextEditingController();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _appTitle,
home: Scaffold(
appBar: AppBar(title: const Text(_appTitle)),
body: Column(
children: [
TextField(controller: controller),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return Dismissible(
key: Key('$todo$index'),
onDismissed: (direction) => todos.removeAt(index),
background: Container(color: Colors.red),
child: ListTile(title: Text(todo)),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
todos.add(controller.text);
controller.clear();
});
},
child: const Icon(Icons.add),
),
),
);
}
}
2. 텍스트 필드에 텍스트 입력
이제 할 일 앱이 준비되었으니, 테스트 작성을 시작합니다. 먼저 TextField에 텍스트를 입력합니다.
다음과 같은 절차로 이 작업을 수행합니다:
테스트 환경에서 위젯을 빌드합니다.
WidgetTester의 enterText() 메서드를 사용합니다.
testWidgets('Add and remove a todo', (tester) async {
// 위젯을 빌드합니다
await tester.pumpWidget(const TodoList());
// TextField에 'hi'를 입력합니다.
await tester.enterText(find.byType(TextField), 'hi');
});
3. 버튼 탭 시 할 일이 추가되는지 확인
TextField에 텍스트를 입력한 후, FloatingActionButton을 탭하면 항목이 리스트에 추가되는지 확인합니다.
이 작업은 세 단계로 이루어집니다:
1. tap() 메서드를 사용하여 추가 버튼을 탭합니다.
2. 상태가 변경된 후 pump() 메서드를 사용하여 위젯을 다시 빌드합니다.
3. 리스트 항목이 화면에 표시되는지 확인합니다.
testWidgets('Add and remove a todo', (tester) async {
// Enter text code...
// 추가 버튼을 탭합니다.
await tester.tap(find.byType(FloatingActionButton));
// 상태가 변경된 후 위젯을 다시 빌드합니다.
await tester.pump();
// 항목이 화면에 표시되는지 확인합니다.
expect(find.text('hi'), findsOneWidget);
});
4. 스와이프하여 할 일을 삭제할 수 있는지 확인
마지막으로, 할 일 항목에 대해 스와이프-투-디스미스(Swipe-to-dismiss) 동작을 수행하면 해당 항목이 리스트에서 제거되는지 확인합니다. 이 작업 역시 세 단계로 구성됩니다:
1. drag() 메서드를 사용하여 스와이프-투-디스미스 동작을 수행합니다.
2. pumpAndSettle() 메서드를 사용하여 디스미스 애니메이션이 완료될 때까지 위젯 트리를 계속 다시 빌드합니다.
3. 항목이 더 이상 화면에 표시되지 않는지 확인합니다.
testWidgets('Add and remove a todo', (tester) async {
// Enter text and add the item...
// 항목을 스와이프하여 삭제합니다.
await tester.drag(find.byType(Dismissible), const Offset(500, 0));
// 디스미스 애니메이션이 끝날 때까지 위젯을 계속 빌드합니다.
await tester.pumpAndSettle();
// 항목이 더 이상 화면에 표시되지 않는지 확인합니다.
expect(find.text('hi'), findsNothing);
});
전체 예제
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Add and remove a todo', (tester) async {
// 위젯을 빌드합니다.
await tester.pumpWidget(const TodoList());
// TextField에 'hi'를 입력합니다.
await tester.enterText(find.byType(TextField), 'hi');
// 추가 버튼을 탭합니다.
await tester.tap(find.byType(FloatingActionButton));
// 새로운 항목과 함께 위젯을 다시 빌드합니다.
await tester.pump();
// 항목이 화면에 표시되는지 확인합니다.
expect(find.text('hi'), findsOneWidget);
// 항목을 스와이프하여 삭제합니다.
await tester.drag(find.byType(Dismissible), const Offset(500, 0));
// 디스미스 애니메이션이 끝날 때까지 위젯을 계속 빌드합니다.
await tester.pumpAndSettle();
// 항목이 더 이상 화면에 표시되지 않는지 확인합니다.
expect(find.text('hi'), findsNothing);
});
}
class TodoList extends StatefulWidget {
const TodoList({super.key});
@override
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
static const _appTitle = 'Todo List';
final todos = <String>[];
final controller = TextEditingController();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _appTitle,
home: Scaffold(
appBar: AppBar(title: const Text(_appTitle)),
body: Column(
children: [
TextField(controller: controller),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return Dismissible(
key: Key('$todo$index'),
onDismissed: (direction) => todos.removeAt(index),
background: Container(color: Colors.red),
child: ListTile(title: Text(todo)),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
todos.add(controller.text);
controller.clear();
});
},
child: const Icon(Icons.add),
),
),
);
}
}
'Flutter > 테스팅' 카테고리의 다른 글
플러터 통합 테스트 _ 02. 통합 테스트로 앱 기능 확인하기 (1) | 2025.04.21 |
---|---|
플러터 통합 테스트 _ 01. 개요 (0) | 2025.04.21 |
플러터 위젯 테스트 _ 3. 스크롤 처리하기 (0) | 2025.04.21 |
플러터 위젯 테스팅 _ 1. 개요 (0) | 2025.04.21 |
플러터] Mockito를 사용한 의존성 모킹하기 (0) | 2024.09.21 |