哈喽,我是老刘
这两天发现一个Flutter 3.24版本的单元测试的一个小bug,提醒大家注意一下。
老刘自己写代码十多年了,写Flutter也6年多了,没想到前两天在一个小小的BottomNavigationBar 组件上翻了车。
给大家分享一下事件的经过。
问题经过
这件事的起因是最近想做一个自己用的小App,Flutter实现。
大家知道我一直是TDD的践行者,所以就先写了首页的测试代码。
首页底部设计有两个tab按钮,我的这个测试是点击按钮切换页面内容。
实现方法是BottomNavigationBar 配合页面内容的TabView。
结果在执行tester.tap方法的地方测试代码就崩了。
就只有一句提示
大家注意
No tests match "xxx..."
这种提示,如果经排查发现,测试代码中加入某个操作就出现,那么就可以怀疑一下是测试框架底层崩溃导致的。
我当时第一反应是不是我的BottomNavigationBar 的用法有问题?
因为之前是不用这个组件的,我们有自己封装的UI组件库,这次是图省事想简单看一下效果。
但是反复排查也没发现问题,而且真机运行也没问题。
这时候我还是怀疑我自己哪个地方没用对,于是把官方的组件测试代码拿过来试了一下。
dart
testWidgets('BottomNavigationBar callback test', (WidgetTester tester) async {
late int mutatedIndex;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
label: 'AC',
),
BottomNavigationBarItem(
icon: Icon(Icons.access_alarm),
label: 'Alarm',
),
],
onTap: (int index) {
mutatedIndex = index;
},
),
),
),
);
await tester.tap(find.text('Alarm'));
expect(mutatedIndex, 1);
});
结果也崩了。
这时候已经是凌晨1点多了,脑子有点不清醒。
本来打算去睡觉明天再说,可是突然灵光一闪,是不是我手欠选择了Flutter 3.24.5版本的问题?
于是换成3.10,结果没问题了,测试正常通过。
然后我就清醒了,感觉问题不是出在BottomNavigationBar上,有可能所有点击事件都有问题。
于是我又写了一个简单的测试代码:
dart
testWidgets('Button tap test', (WidgetTester tester) async {
int ex = 0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: TextButton(child: const Text('Alarm'), onPressed: () {
ex = 1;
}),
),
),
),
);
await tester.tap(find.text('Alarm'));
expect(ex, 1);
});
果不其然,也崩了。
所以应该是Flutter 3.24版本下,test环境,点击动作有问题。
这时候凌晨两点多,已经困的不行了,睡觉,明天再说。
问题确定
第二天起来理了理思路,在想应该先排除一下我的本地环境问题,于是在我的笔记本上又尝试了一次。
笔记本也复现了这个问题,而且幸运的是笔记本上海多了一个错误提示:
[ERROR:flutter/impeller/runtime_stage/runtime_stage.cc(28)] Reached unreachable code.
这个提示怎么看起来是impeler的问题呢?
上网搜了一下,果然有人报了这个bug
[ERROR:flutter/impeller/runtime_stage/runtime_stage.cc(28)] Reached unreachable code. · Issue #147551 · flutter/flutter · GitHub
而且他是在mac上,Flutter版本是3.22
我本地测试了一下Flutter 3.19,是没问题的,所以问题大概率出在3.20之后。
问题的具体细节和原因没有花时间细看。
这里一方面给大家一个提示,碰到这个问题大概知道是咋回事。
另一方面也是借着这个定位过程总结一下经验。
总结经验
1、当运行的框架,特别是框架的底层代码比如c++代码崩溃,就很容易出现一些奇怪的现象误导开发者。
比如这次的提示 【No tests match "xxx..."】
2、如果怀疑是test框架的问题,可以通过命令行运行并加入一些类似"-v"的参数查看运行更详细的过程。
这样可以帮你快速判断问题来源。
话说自己的课程里还经常提醒这一点,结果用到的时候就没想起来。
我是直到第二天用笔记本运行看到异常提示信息才想起来,这个属实有点不应该。
3、关于TDD中的UI测试,老刘一贯的观点是只测试UI中涉及业务逻辑的部分,不测试UI布局和交互的细节。
就好像这次的测试例:'用户点击个人中心按钮,页面内容切换为个人中心'
它测试的其实是如何实现一个UI效果,这部分内容是否应该通过测试覆盖,应该覆盖到什么程度其实是值得商榷的。
比如如果我通过DefaultTabController + TabView + TabBar来实现这个效果,那其实测试的就是DefaultTabController的内部逻辑了。
好了,以上这些基于这次这个问题的思考才是我这篇文章想要表达的核心内容。
如果大家有更多的想法或者不同意见都欢迎交流。
点击免费领老刘整理的《Flutter开发手册》,覆盖90%应用开发场景。
可以作为Flutter学习的知识地图。
覆盖90%开发场景的《Flutter开发手册》