Flutter Test 基礎

aTaller發表於2020-05-21

級別:★☆☆☆☆
標籤:「Flutter Test」「Flutter 單元測試」「Flutter Widget Test」
作者: ITWYW
審校: aTaller團隊

前言 筆者最近看了一些關於 Flutter Test 的內容,會整理分享約3篇文章。本文是第一篇,筆者會在本文中介紹Flutter Test 的基本概念,整合並使用 Flutter Test的方式,Flutter 單元測試的基礎知識,Flutter Widget Test 的基礎知識等。

零、Flutter Test 基礎概念

Flutter Test基礎概念的內容摘抄自 測試 Flutter App 介紹

1. 單元測試

單元測試:測試單一功能、方法或類。

例如,被測單元的外部依賴性通常被模擬出來,如package:mockito。 單元測試通常不會讀取/寫入磁碟、渲染到螢幕,也不會從執行測試的程式外部接收使用者操作。

單元測試的目標是在各種條件下驗證邏輯單元的正確性。

2. Widget 測試

widget 測試:(在其它UI框架稱為 元件測試) 測試的單個widget。

測試widget涉及多個類,並且需要提供適當的widget生命週期上下文的測試環境。 例如,它應該能夠接收和響應使用者操作和事件,執行佈局並例項化子widget。widget測試因此比單元測試更全面。 然而,就像一個單元測試一樣,一個widget測試的環境被一個比完整的UI系統簡單得多的實現所取代。

小部件測試的目標是驗證小部件的UI如預期的那樣的外觀和互動。

3. 整合測試

整合測試: 測試一個完整的應用程式或應用程式的很大一部分。

通常,整合測試可以在真實裝置或OS模擬器上執行,例如iOS Simulator或Android Emulator。 被測試的應用程式通常與測試驅動程式程式碼隔離,以避免結果偏差。

整合測試的目標是驗證應用程式作為一個整體正確執行,它所組成的所有widget如預期的那樣相互整合。 您還可以使用整合測試來驗證應用的效能。

下邊筆者會以 Flutter 單元測試為例,分享下 整合並使用 Flutter Test的方式。

一、整合並使用 Flutter Test

1. Flutter 單元測試整合方式

在 pubspec.yaml 檔案中新增如下配置,並儲存,在終端執行 flutter pub get

dev_dependencies:
  flutter_test:
    sdk: flutter
複製程式碼

上述配置,flutter_test 是建立 專案之後,預設包含的配置,flutter_test 依賴是用於 單元測試、Widget Test 的依賴。

2. 執行單元測試、Widget 測試的方式

執行單元測試可直接在終端執行如下命令:

2.1 flutter test 命令
flutter test
flutter test 檔案目錄../檔名.dart
複製程式碼
2.2 flutter test 使用示例

使用如下的命令,執行單元測試或 Widget 測試。flutter test 預設測試的檔案為伴隨專案建立一起生成的 widget_test.dart 檔案。flutter test + 待測試檔案的目錄/待測試檔案 用於測試指定檔案。

wangyongwangdeiMac:flutter_tutorial wangyongwang$ flutter test test/widget_test.dart
00:03 +15: All tests passed!                                                         
wangyongwangdeiMac:flutter_tutorial wangyongwang$ flutter test
00:02 +15: All tests passed!

wangyongwangdeiMac:test_demo wangyongwang$ flutter test
00:08 +1: All tests passed! 
複製程式碼

3. 單元測試、Widget 測試基礎 API

3.1 test API 基本使用
// 引入 flutter_test.dart
import 'package:flutter_test/flutter_test.dart';

// test
test('測試描述資訊', (){
    // 待測試程式碼
  });


// 如果多個待測試內容直接有關聯,可以考慮使用 group API 把多個測試整合在一起。
  group('groupTest', () {
      test('test1', () {
          // 待測試程式碼
      });
      test('test2', () {
         // test2 和 test1 之間存在某些關聯 
      });
  });

// Widget Test 應用場景:在 Widget樹中查詢子 Widget 是否存在、讀取文字、驗證 Widget 屬性的值是否正確。
testWidgets('測試描述', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: App.MyApp(),
      ),
    );
    final appBar = find.byKey(Key(App.HomeAppBarKeyString));
    expect(appBar, findsOneWidget);
  });

複製程式碼
3.2 test API 示例

使用test API,在test API 的匿名函式回撥中執行待測試程式碼。

test('my first unit test', () {
    var answer = 68;

    // 單元測試通過
    expect(answer, 68);
    // 單元測試失敗
    // expect(answer, 77);
  });
複製程式碼

上述使用expect,用於斷言第一個引數和第二個引數要匹配。匹配的情況下,測試就會通過,不匹配的情況下,測試就會失敗。

我們可以簡單看下 expect 這個API


/// Assert that `actual` matches `matcher`.
///
/// See [test_package.expect] for details. This is a variant of that function
/// that additionally verifies that there are no asynchronous APIs
/// that have not yet resolved.
///
/// See also:
///
///  * [expectLater] for use with asynchronous matchers.
void expect(
  dynamic actual,
  dynamic matcher, {
  String reason,
  dynamic skip, // true or a String
}) {
  TestAsyncUtils.guardSync();
  test_package.expect(actual, matcher, reason: reason, skip: skip);
}
複製程式碼

目前筆者只使用到了前2個引數。

值得注意的是 expectLater 這個方法,expectLater 使用場景為:非同步網路請求情況的斷言。

下邊筆者舉一個非同步網路請求的測試的例子。

import 'package:dio/dio.dart';

class QiNetwork extends Object {
  static Future<Response> request({String urlString}) async {
    Response response = await Dio().get(urlString);
    return response;
  }
}
複製程式碼
import 'package:flutter_tutorial/testSourceCode/qi_network.dart';

Future testNetwork() async {
  Response response;
  test('測試網路請求', () async {
    String testURLString = 'https://api.github.com/orgs/flutterchina/repos';
    await QiNetwork.request(urlString: testURLString).then((value) {
      response = value;
      expectLater(response.statusCode, 200);
      List responseList = response.data;
      Map responseFirstListMap = responseList.first;
      print(responseFirstListMap['name']);
      expect(responseFirstListMap['name'], 'dio');
    });
  });
}

void main() async {
  await testNetwork();
 }
複製程式碼

測試通過輸出資訊如下

wangyongwangdeiMac:flutter_tutorial wangyongwang$ flutter test
00:01 +0: ... 測試網路請求                                                                      00:02 +0: ... 測試網路請求                                                                      00:02 +1: ... 測試網路請求                                                                      00:03 +1: ... 測試網路請求                                                                      00:03 +1: /Users/wangyongwang/Documents/GitHub/aTaller/FlutterLearningRecord/flutter_tutorial/test/widget_test.dart: 測試網路請求
dio
00:03 +2: ... 測試網路請求                                                                      00:03 +2: All tests passed! 
複製程式碼
3.3 其他測試方式

除了使用 flutter test 還可以點選 test API 上方的 Run ,z可以直接對指定的某一個test API 進行測試。

Flutter Test Run

3.4 group API 示例

下邊筆者把關於字串相關的操作的 test 放在了同一個 group 方法中。這裡筆者的目的是根據字串相關的這種關聯,把相關 test 放大了同一個 group。


  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 的示例,程式碼如下:

class QiCounter {
  int countValue = 0;
  int increment() {
    return ++countValue;
  }

  int decrease() {
    return --countValue;
  }
}
複製程式碼
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_tutorial/testSourceCode/qi_counter.dart';

void testCounter() {
  test('testCounter', () {
    final counter = QiCounter();
    expect(counter.countValue, 0);
    expect(counter.increment(), 1);
    expect(counter.increment(), 2);
    expect(counter.decrease(), 1);
  });
}



void testGroupCounter() {
  group("Counter Group Test", () {
    test('Counter初始化', () {
      final counter = QiCounter();
      expect(counter.countValue, 0);
    });

    test('Counter Increment', () {
      final counter = QiCounter();
      expect(counter.increment(), 1);
      expect(counter.countValue, 1);
    });

    test('Counter Increment', () {
      final counter = QiCounter();
      expect(counter.increment(), 1);
      expect(counter.decrease(), 0);
      expect(counter.countValue, 0);
      expect(counter.decrease(), -1);
      expect(counter.countValue, -1);
    });
  });

  test('testCounter', () {
    final counter = QiCounter();
    expect(counter.countValue, 0);
    expect(counter.increment(), 1);
    expect(counter.increment(), 2);
    expect(counter.decrease(), 1);
  });
}

void main() async {
  testCounter();
   testGroupCounter();
}
複製程式碼

上述程式碼的測試結果為:

wangyongwangdeiMac:flutter_tutorial wangyongwang$ flutter test
00:02 +1: ... Counter Group Test Counter初始化                                           00:03 +6: All tests passed!   
複製程式碼
3.5 更多 test 示例

更多 test 的示例,筆者參照著做了一些常識,大家有需要去 FlutterLearningRecord widget_test.dart 檢視。

3.6 Widget Test API

Widget Test 使用的API 為 testWidgets,同樣,第一個引數為描述測試資訊,第二個引數為一個非同步匿名函式。這個匿名函式帶有一個WidgetTester 型別的引數tester,我們可以使用 tester.pumpWidget 指定要對哪個頁面進行測試,可以使用find.byKey 獲取 Widget 的 finder,之後可以使用 expect 斷言會找到一個 Widget(findsOneWidget),或者斷言找不到相應的 Widget(findsNothing)。

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_tutorial/main.dart' as App;

void main() {
  testWidgets("My First Widget ", (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: App.MyApp(),
      ),
    );
    final appBar = find.byKey(Key(App.HomeAppBarKeyString));
    expect(appBar, findsOneWidget);

    final appBarNone = find.byKey(Key('noneKey'));
    expect(appBarNone, findsNothing);
    // expect(appBarNone, findsOneWidget);
  });
}
複製程式碼

main.dart 中的程式碼比較多,筆者就不貼出來了,有需要的話,大家去 FlutterLearningRecord widget_test.dart 檢視即可。

二、 Demo

FlutterLearningRecord

三、參考學習網址


筆者微信:可加並拉入《aTaller技術交流群》。

筆者微信

關注我們的途徑有:
aTaller(簡書)
aTaller(掘金)
aTaller(微信公眾號)

推薦文章:
Flutte 開發小技巧
Flutter 常用 Widget 介紹
Flutter 圖片載入
Flutter 混合棧複用原理
Flutter Platform Channel 使用與原始碼分析
Flutter Platform View 使用及原理簡析
奇舞週刊

相關文章