Testing Flutter apps翻译-点击,拖动和输入文本

原文链接: https://juejin.im/post/5cbd885f6fb9a032341647fc

翻译首页

许多我们build的Widget不仅仅显示信息,也响应用户交互。包括可点击的按钮,在屏幕拖动控件,或者在文本框里输入文本。

为了测试那些互动,我们需要一种在测试环境模拟它们的方法。为了这么做,我们需要使用flutter_test类库的WidgetTester类。

这个WidgetTester提供了输入文本,点击,拖拽的方法。

  • enterText
  • tap
  • drag

在很多情况下,用户交互将会更新我们app的状态。在测试环境里,在状态改变后Flutter不会自动重新构建Widgets。为了确保我们的Widget树在我们模拟用户交互以后重新build,我们必须调用WidgetTester提供的pump或者pumpAndSettle方法。

步骤:

  1. 创建一个Widget用于测试
  2. 在输入框里输入文本
  3. 确保点击按钮会添加todo
  4. 确保滑动删除todo

1. 创建一个Widget用于测试

在这个例子里,我们将会创建一个基础的todo app。它将会有3个需要我们测试的主要功能:

  1. TextField里输入文本
  2. 点击 FloatingActionButton 按钮在todo列表里添加文本
  3. 滑动从列表中删除一个item

为了保持焦点在测试上,这个例子将不会提供详细的构建todo app的界面。如果想学习更多关于如何构建app,请查看以下相关文章:

  • Create and style a text field
  • Handling Taps
  • Create a basic list
  • Implement Swipe to Dismiss
class TodoList extends StatefulWidget {
  @override
  _TodoListState createState() => _TodoListState();
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _appTitle,
      home: Scaffold(
        appBar: AppBar(
          title: Text(_appTitle),
        ),
        body: Column(
          children: [
            TextField(
              controller: controller,
            ),
            Expanded(
              child: ListView.builder(
                itemCount: todos.length,
                itemBuilder: (BuildContext context, int index) {
                  final todo = todos[index];

                  return Dismissible(
                    key: Key('$todo$index'),
                    onDismissed: (direction) => todos.removeAt(index),
                    child: ListTile(title: Text(todo)),
                    background: Container(color: Colors.red),
                  );
                },
              ),
            ),
          ],
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              todos.add(controller.text);
              controller.clear();
            });
          },
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}
复制代码

2. 在输入框里输入文本

现在我们有了一个todo app,我们可以开始写我们的测试了!在这个示例里,我们将会从输入文本到TextField开始。

我们可以这样完成任务:

  • 在测试环境build一个Widget
  • 使用WidgetTesterenterText 方法
testWidgets('Add and remove a todo', (WidgetTester tester) async {
  // Build the Widget
  await tester.pumpWidget(TodoList());

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

注意: 这段代码基于上一段测试的代码。如果想学习Widget测试的核心概念,请查看下面的文章:

  • Widget测试介绍
  • 使用Finder查找child widget

3. 确保点击按钮会添加todo

当我们在TextField里输入文本之后,我们希望点击FloatingActionButton会添加 item 到列表里。

这些步骤包括3步:

  1. 使用 tap 方法点击按钮。
  2. 状态改变后使用pump 方法重新build Widget。
  3. 确保 item 出现在屏幕的列表里。
testWidgets('Add and remove a todo', (WidgetTester 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);
});
复制代码

3. 滑动从列表中删除一个item

最后,我们要确保在滑动删除一条todo后可以从列表中删除它。这将包括以下3个步骤:

  1. 使用 drag 方法执行滑动删除操作。
  2. 使用 pumpAndSettle 方法不断的重新build我们的Widget树直到dismiss动画完成。
  3. 确保 item 从屏幕中消失。
testWidgets('Add and remove a todo', (WidgetTester tester) async {
  // Enter text and add the item...

  // Swipe the item to dismiss it
  await tester.drag(find.byType(Dismissible), Offset(500.0, 0.0));

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

  // Ensure 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', (WidgetTester tester) async {
    // Build the Widget
    await tester.pumpWidget(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), Offset(500.0, 0.0));

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

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

class TodoList extends StatefulWidget {
  @override
  _TodoListState createState() => _TodoListState();
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _appTitle,
      home: Scaffold(
        appBar: AppBar(
          title: Text(_appTitle),
        ),
        body: Column(
          children: [
            TextField(
              controller: controller,
            ),
            Expanded(
              child: ListView.builder(
                itemCount: todos.length,
                itemBuilder: (BuildContext context, int index) {
                  final todo = todos[index];

                  return Dismissible(
                    key: Key('$todo$index'),
                    onDismissed: (direction) => todos.removeAt(index),
                    child: ListTile(title: Text(todo)),
                    background: Container(color: Colors.red),
                  );
                },
              ),
            ),
          ],
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              todos.add(controller.text);
              controller.clear();
            });
          },
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}
复制代码

你可能感兴趣的:(Testing Flutter apps翻译-点击,拖动和输入文本)