这篇文章是根据官方文档摘录的一些日常开发中常用内容, 方便日后查阅.

关于 Dart 中的单元测试

下面简略描述一下 Dart 中的单元测试应该如何进行, 更多内容详见官方文档.

  • 使用 test 函数写单元测试.

  • 使用 expect 函数进行断言.

    dart
    import "package:test/test.dart";
    
    void main() {
      test("String.split() splits the string on the delimiter", () {
        var string = "foo,bar,baz";
        expect(string.split(","), equals(["foo", "bar", "baz"]));
      });
    
      test("String.trim() removes surrounding whitespace", () {
        var string = "  foo ";
        expect(string.trim(), equals("foo"));
      });
    }
  • 使用 group 包含多个单元测试:

    dart
    import "package:test/test.dart";
    
    void main() {
      group("String", () {
        test(".split() splits the string on the delimiter", () {
          var string = "foo,bar,baz";
          expect(string.split(","), equals(["foo", "bar", "baz"]));
        });
    
        test(".trim() removes surrounding whitespace", () {
          var string = "  foo ";
          expect(string.trim(), equals("foo"));
        });
      });
    
      group("int", () {
        test(".remainder() returns the remainder of division", () {
          expect(11.remainder(3), equals(2));
        });
    
        test(".toRadixString() returns a hex string", () {
          expect(11.toRadixString(16), equals("b"));
        });
      });
    }
  • 使用 matcher 包中的若干方法可以进行复杂的断言验证:

    dart
    import "package:test/test.dart";
    
    void main() {
      test(".split() splits the string on the delimiter", () {
        expect("foo,bar,baz", allOf([
          contains("foo"),
          isNot(startsWith("bar")),
          endsWith("baz")
        ]));
      });
    }
  • 使用 setUptearDown 来在每个单元测试执行前后插入需要执行的代码:

    dart
    import "package:test/test.dart";
    
    void main() {
      HttpServer server;
      Uri url;
      setUp(() async {
        server = await HttpServer.bind('localhost', 0);
        url = Uri.parse("http://${server.address.host}:${server.port}");
      });
    
      tearDown(() async {
        await server.close(force: true);
        server = null;
        url = null;
      });
    
      // ...
    }

    另外如果测试失败的话, tearDown 仍然会执行, 从而保证比如资源能正确释放等.

  • 指定在某些平台上进行单元测试, 则使用注解:

    dart
    @TestOn("ios && android")
  • 普通异步测试, 这里有个好的地方是不用手动去指定等待时间这些杂鱼, 而是可以使用 async 和 await 来等待:

    dart
    import "dart:async";
    
    import "package:test/test.dart";
    
    void main() {
      test("new Future.value() returns the value", () async {
        var value = await new Future.value(10);
        expect(value, equals(10));
      });
    }
  • 使用 completion() 的异步测试: 可以用这个 matcher 包中的方法来测试 Future:

    dart
    import "dart:async";
    
    import "package:test/test.dart";
    
    void main() {
      test("new Future.value() returns the value", () {
        expect(new Future.value(10), completion(equals(10)));
      });
    }
  • 使用 throwsA() 或类似的方法: 可以用它们来验证 Future 中抛出的特定异常:

    dart
    import "dart:async";
    
    import "package:test/test.dart";
    
    void main() {
      test("new Future.error() throws the error", () {
        expect(new Future.error("oh no"), throwsA(equals("oh no")));
        expect(new Future.error(new StateError("bad state")), throwsStateError);
      });
    }

要执行单元测试, 使用如下命令:

bash
# 运行某个文件中的单元测试
flutter test test/unit_test.dart
# 运行所有测试: 这个命令无法自动找到在 test 子文件夹中的单元测试, 故如果只执行单元测试, 可以使用下一个命令.
flutter test

# 更常用的是如下的命令: 执行 unit_test 文件夹下的所有单元测试.
flutter test --machine test/unit_tests/**

当然更简单的办法是直接使用 IDE 提供的测试执行按钮, 点一下就执行了.