七爪源码:使用 Flutter 进行测试驱动开发

七爪源码:使用 Flutter 进行测试驱动开发

了解如何使用测试驱动开发方法构建 Flutter 应用程序。 习惯于使用 Red-Green-Refactor 节奏的 TDD,永远不要害怕对代码进行更改。

“如果您希望您的系统灵活,请编写测试。 如果您希望您的系统可重用,请编写测试。 如果您希望您的系统可维护,请编写测试。”


测试驱动的开发

TDD 是通过预先编写测试以证明实现满足要求来创建软件的过程。 对于大多数开发人员来说,这不是自然而然的事情,最难的是习惯它。 但好消息是 TDD 的过程是有节奏的,正因为如此,我将尝试通过示例向您展示当您感觉到节奏时是多么容易。 基本上,通过所有工作,您需要重复 3 个步骤/颜色:

  1. 编写一个失败的测试(红色阶段)
  2. 使测试通过——编写足够的代码以通过测试(绿色阶段)
  3. 改进代码——清理混乱(重构阶段)
七爪源码:使用 Flutter 进行测试驱动开发

FLUTTER 自动化测试

Flutter 有 3 类测试:

  1. 小部件测试(测试单个小部件)
  2. 单元测试(测试单个函数、方法或类)
  3. 集成测试(测试完整的应用程序或应用程序的大部分)

在本文中,我将介绍小部件测试,以及如何确保所有内容都按您的意愿显示给用户。 您可以检查小部件的类型、文本、颜色、填充、点击检测等。

单元和集成测试将包含在下一篇文章中,敬请期待!


我们的任务是实现一个简单的 UI。 我们想要构建一个包含 Text 和 Button 小部件的屏幕。

七爪源码:使用 Flutter 进行测试驱动开发

不要担心屏幕的简单性,因为我们的目标是习惯 TDD 的节奏。 开始吧。


如果你已经打开了你的项目并且你从其他任何东西开始而不是创建一个测试文件,那就停在那里!

在实现任何东西之前,我们将编写第一个测试来检查我们的屏幕是否已创建。 请记住,红绿重构。


══╡RED:首页

testWidgets('home page is created', (WidgetTester tester) async {
  final testWidget = MaterialApp(
    home: HomePage(),
  );

  await tester.pumpWidget(testWidget);
  await tester.pumpAndSettle();
});

我们的第一个测试已经编写完成,并且会显示语法错误,因为我们根本没有创建主页小部件。 我们处于红色区域的深处,如果我们想进入绿色区域,下一步就是创建主页。


══╡GREEN:主页

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

如果我们检查我们的测试,语法错误就消失了。 使用命令 flutter test 运行测试并检查我们是否在绿色区域中。 太好了,我们做到了!


══╡REFACTOR:主页

由于没有要重构的东西,我们的第一个 Red-Green-Refactor 循环就完成了。 够容易吗?


现在,当我们有了基础后,下一步是创建一个测试,检查屏幕上是否有 Text 小部件以及文本是否正确。


══╡RED:文字

testWidgets('home page contains hello world text',
    (WidgetTester tester) async {
  final testWidget = MaterialApp(
    home: HomePage(),
  );

  await tester.pumpWidget(testWidget);
  await tester.pumpAndSettle();

  expect(find.text('Hello World!'), findsOneWidget);
});

如果您运行命令 flutter test,Flutter 测试框架捕获的异常将是:

The following TestFailure object was thrown running a test:
 Expected: exactly one matching node in the widget tree
 Actual: _TextFinder:
 Which: means none were found but one was expected

测试失败了,因为根本没有文字,太好了! 我们需要在主页中添加文本以通过测试并进入绿色区域。


══╡GREEN:文字

规则是添加足够的代码以通过测试。

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Hello World!');
  }
}

运行颤振测试,我们成功完成了绿色步骤。


══╡REFACTOR:文本

我们需要重构这个吗? 通常,是的! 可能你需要做一些事情,比如添加本地化、格式化代码等。但现在,因为这是一个小示例应用程序,我们的代码已经格式化,我们可以开始了。 我们的第二个红绿重构周期完成了!


是时候在我们的主页中实现一个按钮了。

首先,我们需要测试一些东西:

  • 按钮已创建
  • 显示图标和文本
  • 背景颜色是蓝色
  • 正确调用按下时的回调

通常,最好对所有内容进行单独的测试,但为了简短起见,我们将创建一个包含按钮创建、图标、文本和背景颜色的测试,以及第二个用于 onPressed 回调的测试。


══╡RED:按钮、图标、文本、背景色

testWidgets('home page contains button', (WidgetTester tester) async {
  final testWidget = MaterialApp(
    home: HomePage(),
  );

  await tester.pumpWidget(testWidget);
  await tester.pumpAndSettle();

  final buttonMaterial = find.descendant(
    of: find.byType(ElevatedButton),
    matching: find.byType(Material),
  );

  final materialButton = tester.widget(buttonMaterial);

  expect(materialButton.color, Colors.blue);
  expect(find.text('Weather today'), findsOneWidget);
  expect(find.byKey(Key('icon_weather')), findsOneWidget);
});

运行flutter测试,Flutter测试框架捕捉到的异常是:

The following StateError was thrown running a test:
Bad state: No element

让我们创建按钮并前往绿色天堂。


══╡GREEN:按钮、图标、文本、背景色

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(children: [
      Text('Hello World!'),
      ElevatedButton(
          onPressed: () {},
          style: ElevatedButton.styleFrom(primary: Colors.blue),
          child: Row(children: [
            Icon(
              Icons.wb_sunny,
              key: Key('icon_weather'),
            ),
            Text('Weather today')
          ])),
    ]);
  }
}

运行颤振测试,然后……测试通过了! 是时候进行第三步了。


══╡REFACTOR:按钮、图标、文本、背景色

这是我们收拾刚刚造成的烂摊子的时候了。

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Hello World!'),
        ElevatedButton(
          onPressed: () {},
          style: ElevatedButton.styleFrom(primary: Colors.blue),
          child: Row(
            children: [
              Icon(
                Icons.wb_sunny,
                key: Key('icon_weather'),
              ),
              Text('Weather today'),
            ],
          ),
        ),
      ],
    );
  }

格式化代码并使其美观。


让我们从 onPressed 回调的最后一个 Red-Green-Refactor 节奏开始,我们就完成了! 或者可能不是?

好的,我们完成了小部件测试和 UI。 在下一篇文章中,我们将进行单元和集成测试,我们的按钮将在对话框中显示今天的天气。 但首先,让我们完成这个。


══╡RED:正确调用按下时的回调

我们需要将 onPressed 回调转发到我们的主页类,所以在编写测试时,会显示语法错误,这没关系。 我们将在前往绿区的路上解决这个问题。

testWidgets('notify when button is pressed', (WidgetTester tester) async {
  var pressed = false;
  final testWidget = MaterialApp(
    home: HomePage(
      onPressed: () => pressed = true,
    ),
  );

  await tester.pumpWidget(testWidget);
  await tester.pumpAndSettle();

  await tester.tap(find.byType(ElevatedButton));
  await tester.pumpAndSettle();

  expect(pressed, isTrue);
});

我们得到的错误是:

Error: No named parameter with the name ‘onPressed’.

所以,让我们做一些改变,让这个测试通过。


══╡GREEN:正确调用按下时的回调

class HomePage extends StatelessWidget {
  const HomePage({Key key, this.onPressed}) : super(key: key);

  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    ...
     ElevatedButton(
       onPressed: () => onPressed?.call(),
    ...
  }

我们不知道我们的回调是否为空,所以为了避免错误,我们需要添加条件成员访问。我们很高兴再次检查我们的测试!是时候在重构之前再运行一次颤振测试了。


══╡REFACTOR:正确调用按下时的回调

最后的重构是为了完善最终的外观。

当我们处于 Red-Green-Refactor 的节奏中时,我们并不关心像素、字体、填充和类似的东西。在完成所有 TDD 之前,无需运行应用程序来检查 UI。

你还有一项更重要的任务:

运行神奇的命令颤振测试,在绿区享受你的时光!


结论

对于许多开发人员来说,使用 TDD 方法构建应用程序一开始肯定会感到奇怪和不自然。你可能知道并理解 Red-Green-Refactor 的节奏,但不知何故仍然很难习惯它。但是随着时间的推移(我希望这篇文章),你会对你的代码更有信心,当重构应用程序的时候到来时,你会喜欢你的测试!

直到下一篇文章!谢谢!

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章