Flutter单元测试插件given_when_then_unit_test的使用

发布于 1周前 作者 wuwangju 来自 Flutter

Flutter单元测试插件given_when_then_unit_test的使用

given_when_then_unit_test 是一个用于创建更具可读性测试的Flutter包。它可以帮助你以更自然的语言编写单元测试,使测试代码更加优雅且易于理解。

特性

提高测试代码的可读性

通过使用 given_when_then 插件,你可以将测试逻辑用接近英语句子的方式表达出来,从而提高代码的可读性。例如:

// Without `given_when_then`
group('calculator', () {
  // ...
  group('add 1', () => calc.add(1), then: () {
    test('result should be 1', () {
      // ...
    });

    group('[and] subtract 1', () => calc.subtract(1), body: () {
      test('res should be 0', () {
        // ...
      });
    });
  });
});

// 🔥 With `given_when_then` as a common English sentence
given('calculator', () {
  // ...
  when('add 1', () => calc.add(1), then: () {
    then('result should be 1', () {
      // ...
    });

    when('[and] subtract 1', () => calc.subtract(1), body: () {
      then('res should be 0', () {
        // ...
      });
    });
  });
});

结合 shouldly 包可以进一步增强代码的可读性:

given('calculator', () {
  late Calculator calc;

  before(() {
    calc = Calculator();
  });

  when('add 1', () {
    before(() => calc.add(1));

    then('result should be 1', () {
      calc.res.should.be(1);
    });

    and('subtract 1', () {
      before(() => calc.subtract(1));
      then('res should be 0', () {
        calc.res.should.beZero();
      });
    });
  });
});

自动生成BDD风格的测试消息

使用 given_when_then 可以自动生成更具描述性的测试消息,而无需额外的工作:

✓ Given empty calculator When add 1 Then result should be 1
✓ Given empty calculator When add 1 and subtract 1 Then res should be 0

使用方法

简单示例

不使用 given_when_then

group('empty calculator', body: () {
  late Calculator calc;

  setUp(() {
    calc = Calculator();
  });

  group('add 1', () {
    setUp(() {
      calc.add(1);
    });

    test('result should be 1', () {
      calc.res.should.be(1);
    });

    group('[and] subtract 1', () {
      setUp(() {
        calc.subtract(1);
      });

      test('res should be 0', () {
        calc.res.should.beZero();
      });
    });
  });
});

使用 given_when_then

given('empty calculator', () {
  late Calculator calc;

  before(() {
    calc = Calculator();
  });

  when('add 1', () => calc.add(1), then: () {
    then('result should be 1', () {
      calc.res.should.be(1);
    });

    and('subtract 1', () => calc.subtract(1), body: () {
      then('res should be 0', () {
        calc.res.should.beZero();
      });
    });
  });
});

高级示例:使用模拟对象(Mocking)

given('Post Controller', body: () {
  late PostController postController;
  late IPostRepository mockPostRepository;
  late IToastr mockToastr;

  before(() {
    mockPostRepository = MockPostRepository();
    mockToastr = MockToastr();
    postController = PostController(
      repo: mockPostRepository,
      toastr: mockToastr,
    );
  });

  whenn('save new valid post', () {
    bool? saveResult;

    before(() async {
      when(() => mockPostRepository.addNew('new post'))
          .thenAnswer((_) => Future.value(true));

      saveResult = await postController.addNew('new post');
    });

    then('should return true', () async {
      saveResult.should.beTrue();
    });

    then('toastr shows success', () async {
      verify(() => mockToastr.success('ok')).called(1);
    });
  });

  whenn('save new invalid post', () {
    bool? saveResult;
    before(() async {
      when(() => mockPostRepository.addNew('new invalid post'))
          .thenAnswer((_) => Future.value(false));

      saveResult = await postController.addNew('new invalid post');
    });

    then('should return false', () async {
      saveResult.should.beFalse();
    });

    then('toastr shows error', () async {
      verify(() => mockToastr.error('invalid post')).called(1);
    });
  });
});

测试用例

有多种方式来使用测试用例:

版本1

testCases([
  const TestCase([1, 1, 2]),
  const TestCase([5, 3, 8])
], (testCase) {
  final x = testCase.args[0] as int;
  final y = testCase.args[1] as int;

  given('two numbers $x and $y', () {
    when('summarizing them', () {
      then('the result should be equal to ${testCase.args.last}', () {
        (x + y).should.be(testCase.args[2] as int);
      });
    });
  });
});

版本2 - 使用泛型

testCases2<String, String>([
  const TestCase2('Flutter', 'F'),
  const TestCase2('Awesome', 'A'),
], (args) {
  test('Word ${args.arg1} start with ${args.arg2}', () {
    args.arg1.should.startWith(args.arg2);
  });
});

格式化测试报告

你可以通过设置变量 GivenWhenThenOptions.pads 来格式化测试报告,使其在一行或每行打印每个步骤:

GivenWhenThenOptions.pads = 4;

结果如下:

Given the account balance is $100 
    And the card is valid 
    And the machine contains enough money 
When the Account Holder requests $20 
Then the Cashpoint should dispense
    And the account balance should be $80
    And the card should be returned

已知问题

  • mocktailmockito 包冲突,因为它们也引入了 when 方法。可以通过隐藏 when 并使用 whenn 来解决此问题:
import 'package:mocktail/mocktail.dart' hide when;
import 'package:mocktail/mocktail.dart' as mktl show when;

示例代码

以下是一个完整的示例代码,展示了如何使用 given_when_then_unit_testshouldly 包进行单元测试:

// ignore_for_file: public_member_api_docs

import 'package:given_when_then_unit_test/given_when_then_unit_test.dart';
import 'package:shouldly/shouldly.dart';
import 'package:test/test.dart';

class Calculator {
  num _val = 0;
  num get res => _val;

  void add(num value) {
    _val = _val + value;
  }

  void subtract(int value) {
    _val = _val - value;
  }
}

void main() {
  group('calculator', () {
    late Calculator calc;
    setUp(() => calc = Calculator());

    group('add 1', () {
      setUp(() => calc.add(1));
      test('result should be 1', () {
        expect(calc.res, 1);
      });

      group('[and] substract 1', () {
        setUp(() => calc.subtract(1));
        test('res should be 0', () {
          expect(calc.res, isZero);
        });
      });
    });
  });

  // You can rewrite tests above with Given When Then + Shouldly as a common English sentence
  given('calculator', () {
    late Calculator calc;

    before(() {
      calc = Calculator();
    });

    when('add 1', () {
      before(() => calc.add(1));
      then('result should be 1', () {
        calc.res.should.be(1);
      });

      and('subtract 1', () {
        before(() => calc.subtract(1));

        then('res should be 0', () {
          calc.res.should.beZero();
        });
      });
    });
  });
}

希望这些内容能帮助你更好地理解和使用 given_when_then_unit_test 插件!如果你有任何问题或需要进一步的帮助,请随时提问。


更多关于Flutter单元测试插件given_when_then_unit_test的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter单元测试插件given_when_then_unit_test的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用given_when_then_unit_test插件来进行单元测试的示例代码。given_when_then_unit_test插件并不是一个官方或广泛认可的插件,但基于你提到的命名约定,我们可以使用Flutter的test包和一些常见的测试模式来模拟这个流程。

在Flutter中,我们通常使用test包来进行单元测试,结合given_when_then的结构可以让测试代码更加清晰和易于理解。以下是一个简单的示例,展示了如何使用test包来编写符合given_when_then结构的单元测试。

1. 添加依赖

首先,确保你的pubspec.yaml文件中包含了test依赖(Flutter项目默认已经包含)。

dev_dependencies:
  test: ^1.19.0  # 请检查最新版本

2. 编写被测函数

假设我们有一个简单的计数器函数increment,它接受一个整数并返回增加1的结果。

// counter.dart
int increment(int value) {
  return value + 1;
}

3. 编写单元测试

接下来,我们编写单元测试,使用given_when_then的结构来组织代码。

// counter_test.dart
import 'package:test/test.dart';
import 'counter.dart';

void main() {
  test('increment function works as expected', () {
    // given
    int initialValue = 5;
    int expectedValue = 6;

    // when
    int result = increment(initialValue);

    // then
    expect(result, equals(expectedValue));
  });
}

4. 运行测试

你可以使用命令行工具来运行测试:

flutter test test/counter_test.dart

或者,如果你使用的是IDE(如VS Code),你可以直接点击测试文件旁边的运行按钮来执行测试。

解释

  • given:设置测试的前提条件或输入值。在这个例子中,我们设置了initialValue为5。
  • when:执行被测函数。在这个例子中,我们调用了increment函数。
  • then:验证结果是否符合预期。在这个例子中,我们使用expect函数来检查increment函数的返回值是否为6。

通过这种方式,你的单元测试代码将更加清晰和结构化,便于理解和维护。虽然given_when_then_unit_test不是一个具体的Flutter插件,但你可以通过上述方式在Flutter项目中实现类似的测试结构。

回到顶部