플러터 Testing - Widget(4)

Inyeong Kang·2023년 7월 9일
0

Flutter Cookbook 링크

Tap, drag, and enter text

많은 위젯은 정보를 표시할 뿐만 아니라 사용자 상호 작용에 응답한다. 여기에는 누를 수 있는 버튼과 텍스트 입력을 위한 TextField이 포함된다.

이러한 상호 작용을 테스트하려면 테스트 환경에서 시뮬레이션하는 방법이 필요하다. 이를 위해 WidgetTester 라이브러리를 사용하라.

WidgetTester는 텍스트 입력, 탭, 드래그 방법을 제공한다.

  • enterText()
  • tap()
  • drag()
    대부분의 경우 사용자 상호 작용은 앱의 상태를 업데이트한다. 테스트 환경에서 Flutter는 상태가 변경될 때 위젯을 자동으로 다시 빌드하지 않는다. 사용자 상호 작용을 시뮬레이션한 후 위젯 트리가 다시 빌드되도록 하려면 WidgetTester에서 제공하는 pump()또는 pumpAndSettle() 메서드를 호출하라. 이 레시피는 다음 단계를 사용한다.
  1. 테스트할 위젯 생성
  2. 텍스트 필드에 텍스트 입력
  3. 버튼 탭으로 todo 추가
  4. 스와이프 닫기로 todo 제거

1. 테스트할 위젯 생성

이 예에서는 세 가지 기능을 테스트하는 기본 todo 앱을 만든다.

  1. TextField에 텍스트 입력
  2. todo 목록에 텍스트를 추가하기 위해 FloatingActionButton 탭
  3. 목록에서 항목 제거를 위해 스와이프 닫기
    테스트에 집중하기 위해 이 레시피는 할 일 앱을 빌드하는 방법에 대한 자세한 가이드를 제공하지 않는다. 이 앱이 어떻게 구축되었는지 자세히 알아보려면 관련 레시피를 참조하세요.
  • Create and style a text field
  • Handle taps
  • Create a basic list
  • Implement swipe to dismiss
class TodoList extends StatefulWidget {
  const TodoList({super.key});

  
  State<TodoList> createState() => _TodoListState();
}

class _TodoListState extends State<TodoList> {
  static const _appTitle = 'Todo List';
  final todos = <String>[];
  final controller = TextEditingController();

  
  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. 텍스트 필드에 텍스트 입력

이제 todo 앱이 있으니 테스트 작성을 시작한다. TextField에 텍스트를 입력하여 시작한다.
다음과 같이 이 작업을 수행한다.
1. 테스트 환경에서 위젯을 빌드한다.
2. WidgetTester에서 enterText() 함수를 사용한다.

testWidgets('Add and remove a todo', (tester) async {
  // Build the widget
  await tester.pumpWidget(const TodoList());

  // Enter 'hi' into the TextField.
  await tester.enterText(find.byType(TextField), 'hi');
});

참고: 이 레시피는 이전 위젯 테스트 레시피를 기반으로 한다. 위젯 테스트의 핵심 개념을 알아보려면 다음 레시피를 참조하라.

  • Introduction to widget testing
  • Finding widgets in a widget test

3. 버튼 탭으로 todo 추가

TextField에 텍스트를 입력한 후 FloatingActionButton을 탭하면 항목이 목록에 추가되는지 확인하라.

여기에는 세 단계가 포함된다.
1. tap() 함수를 사용하여 추가 버튼을 누른다.
2. pump() 함수를 사용하여 상태가 변경된 후 위젯을 다시 빌드한다.
3. 목록 항목이 화면에 나타나는지 확인한다.

testWidgets('Add and remove a todo', (tester) async {
  // Enter text code...

  // Tap the add button.
  await tester.tap(find.byType(FloatingActionButton));

  // Rebuild the widget after the state has changed.
  await tester.pump();

  // Expect to find the item on screen.
  expect(find.text('hi'), findsOneWidget);
});

4. 스와이프 닫기로 todo 제거

마지막으로 todo 항목에 대해 스와이프하여 닫기 작업을 수행하면 목록에서 해당 항목이 제거되는지 확인한다. 여기에는 세 단계가 포함된다.
1. drag() 함수를 사용하여 스와이프하여 닫기 작업을 수행한다.
2. 닫기 애니메이션이 완료될 때까지 위젯 트리를 계속해서 다시 빌드하는 pumpAndSettle() 함수를 사용한다.
3. 항목이 더 이상 화면에 나타나지 않는지 확인한다.

testWidgets('Add and remove a todo', (tester) async {
  // Enter text and add the item...

  // Swipe the item to dismiss it.
  await tester.drag(find.byType(Dismissible), const Offset(500, 0));

  // Build the widget until the dismiss animation ends.
  await tester.pumpAndSettle();

  // Ensure that the item is no longer on screen.
  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 {
    // Build the widget.
    await tester.pumpWidget(const TodoList());

    // Enter 'hi' into the TextField.
    await tester.enterText(find.byType(TextField), 'hi');

    // Tap the add button.
    await tester.tap(find.byType(FloatingActionButton));

    // Rebuild the widget with the new item.
    await tester.pump();

    // Expect to find the item on screen.
    expect(find.text('hi'), findsOneWidget);

    // Swipe the item to dismiss it.
    await tester.drag(find.byType(Dismissible), const Offset(500, 0));

    // Build the widget until the dismiss animation ends.
    await tester.pumpAndSettle();

    // Ensure that the item is no longer on screen.
    expect(find.text('hi'), findsNothing);
  });
}

class TodoList extends StatefulWidget {
  const TodoList({super.key});

  
  State<TodoList> createState() => _TodoListState();
}

class _TodoListState extends State<TodoList> {
  static const _appTitle = 'Todo List';
  final todos = <String>[];
  final controller = TextEditingController();

  
  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),
        ),
      ),
    );
  }
}
profile
안녕하세요. 강인영입니다. GDSC에서 필요한 것들을 작업하고 업로드하려고 합니다!

0개의 댓글