Flutter蛋糕定制插件cake_flutter的使用
Flutter蛋糕定制插件cake_flutter的使用
Cake 测试运行器用于 Flutter
专为 Flutter 构建的最小单元测试工具!如果只需要 Dart 的基础包,请获取基础 Cake 包。
在 VS Code 中运行此工具?安装 Cake VS-Code 扩展 以在 IDE 中运行测试、代码片段等。
警告:Beta 版
- 此软件包目前处于 Beta 阶段。文档稀缺,功能可能会有变动,存在大量错误。请自行承担风险。
入门指南
安装
- 全局安装 Cake 测试运行器。
dart pub global activate cake
flutter pub add dev:cake_flutter
- 在您的 开发依赖项 中包含
flutter_test
包。
dev_dependencies:
flutter_test:
sdk: flutter
运行
- Cake 会搜索在运行目录中具有 .cake.dart 后缀的文件。
通过命令行使用 Cake 运行
在要运行测试的目录中执行以下命令:
dart run cake
您还可以添加标志来运行特定测试或查看特定测试的输出。
通过 Flutter 测试命令行运行
Cake-Flutter 是基于现有的 Flutter 测试器构建的,因此可以使用 flutter test
命令运行 Cake 测试。虽然如此,如果您坚持使用 .cake.dart
命名约定,则必须单独调用每个文件。
在要运行测试的目录中执行以下命令:
flutter test path/to/your/test.cake.dart
使用 VS Code
- 在测试浏览器中或直接在文件中运行或调试测试。(有关更多详细信息,请参阅 市场页面。)
编写单元测试
让我们从一个快速示例开始。这是 Cake 相当于 模板 Flutter 项目测试的等效物。
import 'package:cake_flutter/cake_flutter.dart';
import 'package:flutter/material.dart';
void main() {
FlutterTestRunner(
'Counter increments smoke test',
[
Test(
'Counter should start at zero',
action: (test) => test.index(),
assertions: (test) => [
Expect.equals(actual: test.search.text('0').length, expected: 1),
Expect.equals(actual: test.search.text('1').length, expected: 0),
],
),
Test(
'Counter should increment when + is tapped',
action: (test) async {
test.index();
await test.search.icon(Icons.add).first.tap();
await test.forward();
test.index();
},
assertions: (test) => [
Expect.equals(actual: test.search.text('0').length, expected: 0),
Expect.equals(actual: test.search.text('1').length, expected: 1),
],
),
],
setup: (test) async {
await test.setApp(const MyApp());
},
);
}
更多示例,请参阅 /examples/test
文件夹。
组织
Cake 依靠层次结构来组织测试。所有测试必须被包裹在一个 TestRunner 中。然后,您可以直接列出 Tests 或进一步将其嵌套到组中。建议每个文件只有一个 TestRunner。
阶段
Cake 鼓励开发者编写清晰、简单的原子测试,通过定义每个测试的独立阶段来实现。您可能熟悉 “Arrange-Act-Assert” 或 “Given-When-Then” 的测试编写风格。在这里,您可以将其类比为 “Setup-Action-Assertions”(如有必要,包括额外的 teardown)。
所有阶段都可以编写为异步的,如果需要的话。
设置与清理
设置和清理是从父级继承到子级测试和组的。因此,您可以在根 TestRunner 中编写初始设置逻辑,然后在特定组或测试中运行任何特定的设置,以便专门安排正在测试的功能。所有设置在任何操作之前运行。所有清理在所有断言完成后运行,并且无论测试过程中是否出现任何问题都会运行。
操作
操作阶段旨在突出显示测试结果的动作。这可能是点击按钮、发送 API 调用或运行函数。请记住,在一次测试中只能运行一个动作。如果您发现操作函数很大或包含大量代码,这通常是一个信号,表明它需要分解为多个测试或放入 setup 函数中。
虽然通常需要,但操作阶段不是必需的。经常验证初始状态的单元测试可能会跳过操作阶段。
操作可以返回一个值,该值可以稍后用于断言。
断言
断言在操作阶段之后运行,并返回一个 Expect 列表。这些断言将预期输出与实际结果进行匹配。断言的数量没有限制。默认情况下,Cake 在第一个失败后忽略其余断言。这可以在 选项 中关闭。
上下文
上下文是如何从阶段传递信息的方式,是一个从父级到子级继承的对象。这作为每个阶段的参数传递。
Flutter 上下文
与基础 Dart Cake 不同,Flutter Cake 有自己的 Flutter 上下文,类似于 flutter_test 包的包装器。
WidgetTester
您可以通过 Flutter 上下文的 tester
属性访问 flutter_test WidgetTester。理想情况下,您不需要这样做,但如果要将测试迁移到 Cake 或作为变通方法,它是可用的。
搜索
像测试运行器中的 find
一样,Cake 测试器也有自己的类似搜索功能,可以爬取并索引您的测试的小部件树。
有效的搜索标准:
- 按键搜索
- 按图标搜索
- 按文本搜索
- 按小部件类型搜索
索引
搜索需要一些处理来索引,所以您必须首先手动调用 test.index()。一旦索引,您可以调用 test.search 来搜索小部件。您可以添加索引选项以仅索引某些小部件以提高搜索性能,或者启用调试选项以打印到控制台。记住在提交代码前关闭任何调试标志。
快照测试
Cake 支持快照测试。快照是…
断言匹配
通用
-
equals
-
isEqual *
-
isNotEqual
-
isNull
-
isNotNull
-
isType **
-
isTrue
-
isFalse
-
equals 和 isEqual 可以互换使用。
-
isEqual 和 isType 需要定义泛型,否则它们总是通过,因为它们认为类型是
dynamic
。
Flutter 特定
-
isWidgetType *
-
searchHasNone
-
searchHasOne
-
searchHasSome
-
searchHasN
-
findMatch
-
findsOneWidget
-
findsNothing
-
findsWidgets
-
findsNWidgets
-
findsAtLeastNWidgets
-
snapshotMatches
-
snapshotsMatch
-
matchesGolden
-
snapshotIsEqual
-
snapshotIsNotEqual
-
isWidgetType 需要定义泛型,否则它们总是通过,因为它们认为类型是
Widget
。
使用示例
- 对整个设置的小部件进行简单的快照。
Test(
'Counter should start at zero',
action: (test) async {
await test.snapshot(fileName: 'initial_state');
},
assertions: (test) => [
SnapshotExpect.snapshotMatches(test.snapshots.first),
],
)
- 对符合标准的多个小部件进行快照。
Test(
'Can take a snapshot of multiple widgets',
action: (test) async {
await test.search.textIncludes('').snapshot(
fileName: 'multiple_widgets',
// 因为我们只对文本小部件进行快照而不是其父级支架,所以我们需要指定文本方向。
snapshotWidgetSetup:
const SetupSettings(textDirection: TextDirection.ltr),
);
},
assertions: (test) => [
// 注意,调用集合的快照将只返回一个快照。这里只是为了展示检查快照的不同方式。
...SnapshotExpect.snapshotsMatch(test.snapshots),
],
),
快照选项
快照选项允许控制快照的渲染方式、如何处理不匹配以及应保存的位置。这些可以为每个快照单独设置,也可以为运行器、组或测试设置。
- includeSetupWidgets
- 类型: bool?
- 默认: false
- 描述:
- 确定是否包含快照中的设置小部件。
- 如果为 false,快照将仅包括根小部件。
- 使用:
test.snapshot(includeSetupWidgets: true);
- createIfMissing
- 类型: bool?
- 默认: true
- 描述:
- 创建快照,如果它不存在。
- 在第一次测试运行期间初始化快照很有用。
- 使用:
test.snapshot(createIfMissing: false);
- createCopyIfMismatch
- 类型: bool?
- 默认: true
- 描述:
- 如果快照不匹配,则创建不匹配快照的副本。
- 有助于通过保留当前状态进行比较来调试更改。
- 使用:
test.snapshot(createCopyIfMismatch: true);
- mismatchDirectory 和 mismatchFileName
- 类型: String?
- 默认: 在快照的目录和文件名后附加 _mismatch。
- 描述:
- 指定保存不匹配快照的位置。
- 有助于在专用目录中组织不匹配的快照。
- 使用:
test.snapshot(
mismatchDirectory: 'test/snapshots/mismatches',
mismatchFileName: 'my_widget_mismatch',
);
- overwriteGolden
- 类型: bool?
- 默认: false
- 描述:
- 如果存在则覆盖当前黄金文件。
- 有意在更改后更新快照很有用。
- 使用:
test.snapshot(overwriteGolden: true);
- directory 和 fileName
- 类型: String?
- 默认:
- directory: “test/snapshots”
- fileName: 从测试名称派生。
- 描述:
- 指定快照文件的位置和名称。
- 使用:
test.snapshot(
directory: 'custom/snapshots',
fileName: 'my_widget',
);
- fontFamily
- 类型: String?
- 默认: 将使用 Flutter 的默认测试字体。
- 描述:
- 指定快照中使用的字体家族。
- 如果使用自定义字体,请先加载它并将族名传递到这里。
- 使用:
test.snapshot(fontFamily: 'Roboto');
- warnIfInvalid
- 类型: bool?
- 默认: 未明确记录。
- 描述:
- 如果快照无效或不可比较,则显示警告。
- 使用:
test.snapshot(warnIfInvalid: true);
- snapshotWidget
- 类型: Widget?
- 描述:
- 捕获特定小部件的快照而不是整个测试环境。
- 使用:
test.snapshot(snapshotWidget: MyCustomWidget());
- snapshotWidgetSetup
- 类型: SetupSettings?
- 描述:
- 提供额外的配置给 snapshotWidget,例如文本方向。
- 使用:
test.snapshot(
snapshotWidget: MyCustomWidget(),
snapshotWidgetSetup: const SetupSettings(textDirection: TextDirection.ltr),
);
示例全使用:
test.snapshot(
includeSetupWidgets: false,
createIfMissing: true,
createCopyIfMismatch: true,
mismatchDirectory: 'test/snapshots/mismatches',
mismatchFileName: 'my_widget_mismatch',
overwriteGolden: false,
directory: 'test/snapshots',
fileName: 'my_widget',
fontFamily: 'Roboto',
warnIfInvalid: true,
snapshotWidget: MyCustomWidget(),
snapshotWidgetSetup: const SetupSettings(textDirection: TextDirection.ltr),
);
关于快照字体
根据版本,Flutter 将使用 “Ahem”(3.7 之前的版本)或 “FlutterTest”(3.7 或更高版本)。如果您注意到快照中的字体呈现为块状,则这是默认设置。Flutter 这样做是为了确保个别字体更改不会妨碍快照测试。
然而,这并不总是期望的效果。要覆盖它,请在加载小部件之前加载所需的字体。这是一个非常简单的示例,您可以根据需要修改加载方式。
import 'package:cake_flutter/cake_flutter.dart';
void main() {
FlutterTestRunner(
'Counter Snapshots',
[
Test(
'Counter should start at zero',
action: (test) async {
await test.snapshot();
},
assertions: (test) => [
...SnapshotExpect.snapshotMatch(test.snapshots),
],
),
],
setup: (test) async {
// 为测试设置自定义字体
final fontData = rootBundle.load('assets/fonts/Roboto-Regular.ttf');
final fontLoader = FontLoader('Roboto')..addFont(fontData);
await fontLoader.load();
await test.setApp(const MyApp());
},
options: const FlutterTestOptions(
snapshotOptions: SnapshotOptions(
// 设置此选项以便在此测试运行器中使用的每个快照。
// 这也可以在调用 .snapshot() 时单独设置。
fontFamily: 'Roboto',
),
),
);
}
更多关于Flutter蛋糕定制插件cake_flutter的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter蛋糕定制插件cake_flutter的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter项目中使用cake_flutter
插件的示例代码。假设cake_flutter
插件提供了一些用于蛋糕定制的功能,比如选择蛋糕尺寸、口味、装饰等。请注意,实际插件的功能和API可能会有所不同,以下代码仅为示例,具体实现需参考插件的官方文档。
首先,确保你已经在pubspec.yaml
文件中添加了cake_flutter
插件的依赖:
dependencies:
flutter:
sdk: flutter
cake_flutter: ^latest_version # 替换为插件的最新版本号
然后,运行flutter pub get
来安装依赖。
接下来,在你的Flutter项目中,你可以按照以下方式使用cake_flutter
插件:
import 'package:flutter/material.dart';
import 'package:cake_flutter/cake_flutter.dart'; // 假设这是插件的导入路径
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Cake Customization App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CakeCustomizationScreen(),
);
}
}
class CakeCustomizationScreen extends StatefulWidget {
@override
_CakeCustomizationScreenState createState() => _CakeCustomizationScreenState();
}
class _CakeCustomizationScreenState extends State<CakeCustomizationScreen> {
CakeCustomizationModel _cakeModel = CakeCustomizationModel(); // 假设这是插件提供的模型类
void _onSizeChanged(CakeSize size) {
setState(() {
_cakeModel.size = size;
});
}
void _onFlavorChanged(CakeFlavor flavor) {
setState(() {
_cakeModel.flavor = flavor;
});
}
void _onDecorationChanged(List<CakeDecoration> decorations) {
setState(() {
_cakeModel.decorations = decorations;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Customize Your Cake'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Cake Size:', style: TextStyle(fontSize: 18)),
SizedBox(height: 8),
DropdownButton<CakeSize>(
value: _cakeModel.size,
hint: Text('Select Size'),
onChanged: _onSizeChanged,
items: CakeSize.values.map<DropdownMenuItem<CakeSize>>((CakeSize value) {
return DropdownMenuItem<CakeSize>(
value: value,
child: Text(value.toString()),
);
}).toList(),
),
SizedBox(height: 24),
Text('Cake Flavor:', style: TextStyle(fontSize: 18)),
SizedBox(height: 8),
DropdownButton<CakeFlavor>(
value: _cakeModel.flavor,
hint: Text('Select Flavor'),
onChanged: _onFlavorChanged,
items: CakeFlavor.values.map<DropdownMenuItem<CakeFlavor>>((CakeFlavor value) {
return DropdownMenuItem<CakeFlavor>(
value: value,
child: Text(value.toString()),
);
}).toList(),
),
SizedBox(height: 24),
Text('Cake Decorations:', style: TextStyle(fontSize: 18)),
SizedBox(height: 8),
Wrap(
children: _cakeModel.decorations.map<Widget>((CakeDecoration decoration) {
return Chip(
label: Text(decoration.toString()),
onDeleted: () {
setState(() {
_cakeModel.decorations.remove(decoration);
});
},
);
}).toList(),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
// 提交蛋糕定制信息,这里可以调用插件提供的API进行下一步操作
// 例如:CakeCustomizationService.submitCustomization(_cakeModel);
print('Cake Model: $_cakeModel');
},
child: Text('Submit'),
),
],
),
),
);
}
}
// 假设这些是插件定义的枚举类,实际使用时需要参考插件的文档
enum CakeSize { small, medium, large }
enum CakeFlavor { chocolate, vanilla, strawberry }
class CakeDecoration {
final String name;
CakeDecoration(this.name);
@override
String toString() => name;
}
// 假设这是插件提供的模型类,用于存储蛋糕定制信息
class CakeCustomizationModel {
CakeSize size = CakeSize.small;
CakeFlavor flavor = CakeFlavor.chocolate;
List<CakeDecoration> decorations = [];
@override
String toString() {
return 'CakeCustomizationModel{size: $size, flavor: $flavor, decorations: $decorations}';
}
}
在这个示例中,我们创建了一个简单的蛋糕定制界面,用户可以选择蛋糕的尺寸、口味和装饰。这些信息被存储在一个CakeCustomizationModel
对象中。请注意,由于cake_flutter
插件的具体API和功能未知,上述代码中的枚举类、模型类和插件调用都是假设的。实际使用时,你需要参考插件的官方文档来调整代码。