Flutter行为驱动开发插件gherkin_unit_test的使用
Flutter行为驱动开发插件gherkin_unit_test的使用
🧪 Gherkin Unit Test
此包基于称为Gherkin的行为驱动开发(BDD)语言。这种语言使我们能够以直观和易读的方式设计和执行测试。对于开发经验较少的人来说,这些测试也易于理解,因为语法非常接近英语。
大多数测试看起来像这样:
Feature: This feature shows an example
Scenario: It shows a good example
Given we start without an example
When an example gets created
Then the example should explode
我们框架中的类有:
UnitTest
UnitFeature
UnitScenario
UnitExample
UnitStep
(抽象)Given
When
Then
And
But
从上到下,每个类可以包含多个下一级别的类(一对多)。一个UnitTest
可以包含多个UnitFeature
,后者又可以包含多个UnitScenario
,以此类推。
🛠 Implementation 实现
创建测试类
开始创建继承自UnitTest
类的测试类。然后创建一个不带参数的构造函数,但要调用带有description
和空的features
列表的父类构造函数。
class DummyUnitTest extends UnitTest {
DummyUnitTest()
: super(
description: 'All unit tests regarding dummies',
features: [],
);
}
Features 功能
在features
列表中定义第一个UnitFeature
。给它一个名称和一个空的scenarios
列表。
UnitFeature
也是定义我们要测试的systemUnderTest
的地方。这个参数接受一个回调,在其中你可以执行任何逻辑来初始化systemUnderTest
。这个回调会在你指定的任何setUp
方法之后执行。
在这个例子中,我们将使用DummyService()
作为我们的systemUnderTest
。DummyService
将dummyMock
作为参数,我们将它保存到UnitMocks
对象中,以便稍后根据需要操作它。
class DummyUnitTest extends UnitTest {
DummyUnitTest()
: super(
description: 'All unit tests regarding dummies',
features: [
UnitFeature<DummyService>(
description: 'Saving of dummies',
setUpMocks: (mocks) {
mocks.write(DummyMock());
},
systemUnderTest: (mocks) {
return DummyService(dummyDependency: mocks.read(DummyMock));
},
scenarios: [],
),
],
);
}
Scenarios 场景
现在考虑一下你的测试中可能发生哪些场景。对于这个例子,我们将使用“成功的保存”和“失败的保存”作为可能的场景。
使用UnitScenario
类创建这两个场景,并将它们放在空列表中。我们还传递一个描述和这次一个空的steps
列表。
class DummyUnitTest extends UnitTest {
DummyUnitTest()
: super(
description: 'All unit tests regarding dummies',
features: [
UnitFeature<DummyService>(
description: 'Saving of dummies',
setUpMocks: (mocks) {
mocks.write(DummyMock());
},
systemUnderTest: (mocks) {
return DummyService(dummyDependency: mocks.read(DummyMock));
},
scenarios: [
UnitScenario(
description: 'Saving a good dummy should succeed',
steps: [],
),
UnitScenario(
description: 'Saving a bad dummy should fail',
steps: [],
),
],
),
],
);
}
Steps 步骤
现在是关键时刻。为每个场景定义步骤。我们可以访问Given
、When
、Then
、And
和But
。虽然所有这些步骤在后台基本上做同样的事情,但正确使用它们可以帮助你计划、设计和执行测试,以一种直观和正确的BDD方式。
每个步骤需要一个描述和一个回调。回调提供了以下参数:
SUT systemUnderTest
: 我们之前在UnitScenario
中指定的类,代表我们要测试的单元。Log log
: 允许在测试中微妙地记录步骤信息的类。UnitBox box
: 可用于在整个UnitScenario
的一系列步骤中写入和读取值的映射。任何通过box.write(key, value)
写入的值都可以在后续步骤中检索,直到被移除或所有步骤执行完毕。使用box.read(key)
读取值时会自动将其转换为你指定的类型。UnitMocks mocks
: 在整个UnitTest
、UnitFeature
和/或UnitScenario
中持久存在的盒子,可以用来存储你需要的模拟对象,以便稍后检索并根据需要存根方法。UnitExample? example
: 可选的“场景大纲”示例,可以在UnitScenario
中指定。
使用UnitBox
的例子:
[
Given(
'This is an example for the UnitBox',
(systemUnderTest, log, box, mocks, [example]) {
box.write('isExample', true);
},
),
When(
'we write some values',
(systemUnderTest, log, box, mocks, [example]) {
box.write('exampleValue', 1);
box.write('mood', 'happy');
},
),
Then(
'all the values should be accessible up until the last step.',
(systemUnderTest, log, box, mocks, [example]) {
final bool isExample = box.read('isExample');
final int exampleValue = box.read('exampleValue');
final String mood = box.read('mood');
expect(isExample, true);
expect(exampleValue, 1);
expect(mood, 'happy');
},
),
]
结合所有这些信息,我们可以最终完成并设置成功场景:
class DummyUnitTest extends UnitTest {
DummyUnitTest()
: super(
description: 'All unit tests regarding dummies',
features: [
UnitFeature<DummyService>(
description: 'Saving of dummies',
setUpMocks: (mocks) {
mocks.write(DummyMock());
},
systemUnderTest: (mocks) {
return DummyService(dummyDependency: mocks.read(DummyMock));
},
scenarios: [
UnitScenario(
description: 'Saving a good dummy should succeed',
steps: [
Given(
'The dummy service is initialised',
(systemUnderTest, log, box, mocks, [_]) {
mocks.read(DummyMock).stubWhatever();
// TODO(you): Initialise service
},
),
When(
'We call the dummy service with dummy info',
(systemUnderTest, log, box, mocks, [example]) {
// TODO(you): Call dummy service with dummy info
},
),
Then(
'It should succeed',
(systemUnderTest, log, box, mocks, [example]) {
// TODO(you): Verify success
},
),
],
),
UnitScenario(
description: 'Saving a bad dummy should fail',
steps: [],
),
],
),
],
);
}
Bonus UnitSteps 额外的UnitSteps
因为我们并不是每个人都想以相同的方式编写测试,所以我们也创建了这些组合步骤类,允许创建相同的单元测试,但步骤更少。
GivenWhenThen
: 当你不想创建和使用单独的Given
、When
和Then
步骤功能时,这允许你在一步中编写整个测试。WhenThen
: 当你不想创建和使用单独的When
和Then
步骤功能时,这允许你将两个步骤合并为一个。Should
: 当你觉得使用步骤不是你的风格时,这个步骤用一句话定义整个测试。
⚡️ Almost there! 几乎完成!
虽然这可能完全符合我们的测试需求,但我们还有一些功能可以让测试更加强大。
建筑相关的方法
每个类都有访问这些方法的功能,并且它们将以类似的方式运行:
setUpEach
- 将在每个UnitScenario
开始时运行。tearDownEach
- 将在每个UnitScenario
结束时运行。setUpOnce
- 将在所选类开始时运行一次。tearDownOnce
- 将在所选类结束时运行一次。
使用这些方法的例子:
class DummyUnitTest extends UnitTest {
DummyUnitTest()
: super(
description: 'All unit tests regarding dummies',
setUpOnce: (mocks, systemUnderTest) async {
await AppSetup.initialise(); // Runs once at the start of this test.
},
setUpEach: (mocks, systemUnderTest) async {
systemUnderTest.reset();
},
tearDownOnce: (mocks, systemUnderTest) async {
await AppSetup.dispose(); // Runs once at the end of this test.
},
features: [
UnitFeature<DummyService>(
description: 'Saving of dummies',
setUpEach: (mocks, systemUnderTest) {
// TODO(you): Do something
},
setUpMocks: (mocks) {
mocks.write(DummyMock());
},
systemUnderTest: (mocks) {
return DummyService(dummyDependency: mocks.read(DummyMock));
},
scenarios: [
UnitScenario(
description: 'Saving a good dummy should succeed',
tearDownEach: (mocks, systemUnderTest) {
// TODO(you): Do something
},
examples: [
const UnitExample(values: [1]),
const UnitExample(values: [5]),
const UnitExample(values: [10]),
],
steps: [
Given(
'I access the example value',
(systemUnderTest, log, box, mocks, [example]) {
final int exampleValue = example!.firstValue();
},
)
],
),
UnitScenario(
description: 'Saving a bad dummy should fail',
steps: [],
),
],
),
],
);
}
✅ Success! 成功!
现在要运行这些测试,你只需要将DummyUnitTests
添加到主测试函数中,点击运行并祈祷成功。
void main() {
DummyUnitTests().test();
}
示例代码
下面是一个完整的示例代码,展示了如何使用gherkin_unit_test
进行测试:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gherkin_unit_test/unit_test.dart';
import 'gherkin_unit_test_view_model.dart';
void main() {
runApp(const MyApp());
}
/// Just here to showcase the package.
///
/// Example project /test has all type of tests that fully tests the example project.
class IncrementModelCounterScenario
extends UnitScenario<GherkinUnitTestViewModel, UnitExample> {
IncrementModelCounterScenario()
: super(
description: 'Increment the modelCounter',
systemUnderTest: (_) => GherkinUnitTestViewModelMock(),
examples: [
const UnitExample(values: [1]),
const UnitExample(values: [5]),
const UnitExample(values: [10]),
],
steps: [
Given(
'The counter is at 0',
(systemUnderTest, log, box, mocks, [example]) {
systemUnderTest.reset();
expect(systemUnderTest.modelCounter, 0);
},
),
When(
'I increment the counter',
(systemUnderTest, log, box, mocks, [example]) {
final int nrOfIncrements = example.firstValue();
log.value(nrOfIncrements, 'Number of increments');
for (int increment = 0; increment < nrOfIncrements; increment++) {
systemUnderTest.incrementModelCounter();
}
box.write('nrOfIncrements', nrOfIncrements);
},
),
Then(
'We expect the modelCounter to have the value of the increments',
(systemUnderTest, log, box, mocks, [example]) {
final int nrOfIncrements = box.read('nrOfIncrements');
expect(systemUnderTest.modelCounter, nrOfIncrements);
log.success();
},
),
],
);
}
class GherkinUnitTestViewModelMock extends GherkinUnitTestViewModel {
@override
void rebuild() {}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Gherkin Unit Test',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: const GherkinUnitTestView(),
);
}
}
希望这些内容能帮助你更好地理解和使用gherkin_unit_test
插件!如果有任何问题或需要进一步的帮助,请随时提问。
更多关于Flutter行为驱动开发插件gherkin_unit_test的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter行为驱动开发插件gherkin_unit_test的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中使用gherkin_unit_test
插件进行行为驱动开发(BDD)的示例代码。gherkin_unit_test
插件允许你使用Gherkin语法编写测试用例,这对于BDD来说是非常有用的。
1. 添加依赖
首先,你需要在pubspec.yaml
文件中添加gherkin_unit_test
依赖:
dependencies:
flutter:
sdk: flutter
gherkin_unit_test: ^x.y.z # 请替换为最新版本号
然后运行flutter pub get
来安装依赖。
2. 编写Gherkin特性文件
在test/features
目录下创建一个新的.feature
文件,例如example.feature
:
Feature: 简单的数学运算
Scenario: 加法运算
Given 我有一个计算器
When 我输入 2 + 2
Then 结果应该是 4
Scenario: 减法运算
Given 我有一个计算器
When 我输入 5 - 2
Then 结果应该是 3
3. 编写步骤定义
接下来,在test
目录下创建一个新的Dart文件,例如step_definitions.dart
,并在其中定义与Gherkin步骤对应的测试逻辑:
import 'package:flutter_test/flutter_test.dart';
import 'package:gherkin_unit_test/gherkin_unit_test.dart';
// 简单的计算器类
class Calculator {
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
}
void main() {
var feature = loadFeature('test/features/example.feature');
feature.scenario('加法运算', () {
var calculator = Calculator();
var result;
given('我有一个计算器', () {});
when('我输入 2 + 2', () {
result = calculator.add(2, 2);
});
then('结果应该是 4', () {
expect(result, 4);
});
});
feature.scenario('减法运算', () {
var calculator = Calculator();
var result;
given('我有一个计算器', () {});
when('我输入 5 - 2', () {
result = calculator.subtract(5, 2);
});
then('结果应该是 3', () {
expect(result, 3);
});
});
}
4. 运行测试
你可以使用Flutter的测试运行器来运行这些测试。在命令行中运行:
flutter test test/step_definitions.dart
注意事项
- 路径:确保
.feature
文件和Dart文件的路径正确。 - 依赖版本:在实际使用中,请确保
gherkin_unit_test
的版本与Flutter项目兼容。 - 调试:如果测试失败,检查Gherkin步骤与Dart代码之间的映射是否正确。
通过以上步骤,你就可以在Flutter项目中使用gherkin_unit_test
插件进行行为驱动开发了。希望这个示例能帮助你快速上手。