Flutter(二十二)測(cè)試

一. 單元測(cè)試

單元測(cè)試是針對(duì)一個(gè)函數(shù)或者類進(jìn)行測(cè)試

1.1. 添加測(cè)試依賴

test 或者 flutter_test加入依賴文件勃痴,默認(rèn)創(chuàng)建的Flutter程序已經(jīng)有了依賴:

  • Test 包提供了編寫測(cè)試所需要的核心功能
dev_dependencies:
  flutter_test:
    sdk: flutter

1.2. 創(chuàng)建需要測(cè)試的類

單元測(cè)試通常是測(cè)試一個(gè)函數(shù)或者類陈惰,這個(gè)函數(shù)或者類被稱之為是一個(gè)單元诅愚。

在這里揍拆,我們按照官方示例厂财,創(chuàng)建一個(gè)簡(jiǎn)單的Counter類來(lái)演示:

class Counter {
  int value = 0;

  void increment() => value++;
  void decrement() => value--;
}

1.3. 創(chuàng)建測(cè)試文件

我們?cè)趖est目錄下(注意:不是lib目錄下)新症,創(chuàng)建一個(gè)測(cè)試文件:counter_test.dart

  • 通常測(cè)試代碼都會(huì)放在該目錄下筑凫,并且測(cè)試文件不會(huì)打包到最終的應(yīng)用程序中嫌变;
  • 測(cè)試文件通常以 _test.dart 命名吨艇,這是 test runner 尋找測(cè)試文件的慣例;
import 'package:flutter_test/flutter_test.dart';
import 'package:test_demo/counter.dart';

void main() {
  test("Counter Class test", () {
    // 1.創(chuàng)建Counter并且執(zhí)行操作
    final counter = Counter();
    counter.increment();
    // 2.通過(guò)expect來(lái)監(jiān)測(cè)結(jié)果正確與否
    expect(counter.value, 1);
  });
}

1.4. 整合多個(gè)測(cè)試

如果對(duì)同一個(gè)類或函數(shù)有多個(gè)測(cè)試腾啥,我們希望它們關(guān)聯(lián)在一起進(jìn)行測(cè)試东涡,可以使用group

import 'dart:math';

import 'package:flutter_test/flutter_test.dart';
import 'package:test_demo/counter.dart';

void main() {
  group("Counter Test", () {
    test("Counter Default Value", () {
      expect(Counter().value, 0);
    });

    test("Counter Increment test", () {
      final counter = Counter();
      counter.increment();
      expect(counter.value, 1);
    });

    test("Counter Decrement test", () {
      final counter = Counter();
      counter.decrement();
      expect(counter.value, -1);
    });
  });
}

1.5. 執(zhí)行測(cè)試結(jié)果

用 IntelliJ 或 VSCode 執(zhí)行測(cè)試

IntelliJ 和 VSCode 的 Flutter 插件支持執(zhí)行測(cè)試。用這種方式執(zhí)行測(cè)試是最好的倘待,因?yàn)樗梢蕴峁┳羁斓姆答侀]環(huán)疮跑,而且還支持?jǐn)帱c(diǎn)調(diào)試。

  • IntelliJ
  1. 打開文件 counter_test.dart
  2. 選擇菜單 Run
  3. 點(diǎn)擊選項(xiàng) Run 'tests in counter_test.dart'
  4. 或者凸舵,也可以使用系統(tǒng)快捷鍵!
  • VSCode
  1. 打開文件 counter_test.dart
  2. 選擇菜單 Debug
  3. 點(diǎn)擊選項(xiàng) Start Debugging
  4. 或者祖娘,也可以使用系統(tǒng)快捷鍵!

在終端執(zhí)行測(cè)試

我們也可以打開終端,在工程根目錄輸入以下命令來(lái)執(zhí)行測(cè)試:

flutter test test/counter_test.dart

二. Widget測(cè)試

Widget測(cè)試主要是針對(duì)某一個(gè)封裝的Widget進(jìn)行單獨(dú)測(cè)試

1.1. 添加測(cè)試依賴

Widget測(cè)試需要先給 pubspec.yaml 文件的 dev_dependencies 段添加 flutter_test 依賴啊奄。

  • 在單元測(cè)試中我們已經(jīng)說(shuō)過(guò)渐苏,默認(rèn)創(chuàng)建的Flutter項(xiàng)目已經(jīng)添加了
dev_dependencies:
  flutter_test:
    sdk: flutter

1.2. 創(chuàng)建測(cè)試Widget

import 'package:flutter/material.dart';

class HYKeywords extends StatelessWidget {
  final List<String> keywords;
  HYKeywords(this.keywords);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: keywords.map((key) {
          return ListTile(
            leading: Icon(Icons.people),
            title: Text(key),
          );
        }).toList(),
      ),
    );
  }
}

1.3. 編寫測(cè)試代碼

創(chuàng)建對(duì)應(yīng)的測(cè)試文件掀潮,編寫對(duì)應(yīng)的測(cè)試代碼:

  • testWidgets:flutter_test中用于測(cè)試Widget的函數(shù);

  • tester.pumpWidget:pumpWidget 方法會(huì)建立并渲染我們提供的 widget整以;

  • find:find() 方法來(lái)創(chuàng)建我們的 Finders胧辽;

  • findsNothing:驗(yàn)證沒有可被查找的 widgets。

  • findsWidgets:驗(yàn)證一個(gè)或多個(gè) widgets 被找到公黑。

  • findsNWidgets:驗(yàn)證特定數(shù)量的 widgets 被找到邑商。

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:test_demo/keywords.dart';

void main() {
  testWidgets("KeywordWidget Test", (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(title: "demo", home: HYKeywords(["abc", "cba", "nba"]),));

    final abcText = find.text("abc");
    final cbaText = find.text("cba");
    final icons = find.byIcon(Icons.people);

    expect(abcText, findsOneWidget);
    expect(cbaText, findsOneWidget);
    expect(icons, findsNWidgets(2));
  });
}

官方文檔中還有更多關(guān)于Widget的測(cè)試:

三. 集成測(cè)試

單元測(cè)試和Widget測(cè)試都是在測(cè)試獨(dú)立的類或函數(shù)或Widget,它們并不能測(cè)試單獨(dú)的模塊形成的整體或者獲取真實(shí)設(shè)備或模擬器上應(yīng)用運(yùn)行的狀態(tài)凡蚜;

這些測(cè)試任務(wù)可以交給 集成測(cè)試 來(lái)完成人断;

集成測(cè)試需要有兩個(gè)大的步驟

  • 發(fā)布一個(gè)可測(cè)試應(yīng)用程序到真實(shí)設(shè)備或者模擬器上;
  • 利用獨(dú)立的測(cè)試套件去驅(qū)動(dòng)應(yīng)用程序朝蜘,檢查儀器是否完好可用恶迈;

3.1. 創(chuàng)建可測(cè)試應(yīng)用程序

我們需要?jiǎng)?chuàng)建一個(gè)可以運(yùn)行在模擬器或者真實(shí)設(shè)備的應(yīng)用程序。

這里我直接使用了官方的示例程序谱醇,但是不同的是我這里給兩個(gè)Widget添加了兩個(gè)Key

  • 顯示數(shù)字的Text Widget:ValueKey("counter")
  • 點(diǎn)擊按鈕的FloatingActionButton Widget:key: ValueKey("increment")
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              key: ValueKey("counter"),
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        key: ValueKey("increment"),
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

3.2. 添加flutter_driver依賴

我們需要用到 flutter_driver 包來(lái)編寫 集成測(cè)試暇仲,所以我們需要把 flutter_driver 依賴添加到應(yīng)用pubspec.yaml 文件的 dev_dependencies 位置:

dev_dependencies:
  flutter_driver:
    sdk: flutter
  flutter_test:
    sdk: flutter
  test: any

3.3. 創(chuàng)建測(cè)試文件

和單元測(cè)試以及Widget測(cè)試不同的是,集成測(cè)試的程序和待測(cè)試的應(yīng)用并不在同一個(gè)進(jìn)程內(nèi)副渴,所以我們通常會(huì)創(chuàng)建兩個(gè)文件:

  • 文件一:用于啟動(dòng)帶測(cè)試的應(yīng)用程序
  • 文件二:編寫測(cè)試的代碼

我們可以將這兩個(gè)文件放到一個(gè)文件中:test_driver

lib/
    main.dart
  test_driver/
    app.dart
    app_test.dart

3.4. 編寫安裝應(yīng)用代碼

安裝應(yīng)用程序代碼在app.dart中奈附,分層兩步完成:

  • 讓 flutter driver 的擴(kuò)展可用
  • 運(yùn)行應(yīng)用程序

test_driver/app.dart 文件中增加以下代碼:

import 'package:flutter_driver/driver_extension.dart';
import 'package:test_demo/main.dart' as app;

void main() {
  // 開啟DriverExtension
  enableFlutterDriverExtension();

  // 手動(dòng)調(diào)用main函數(shù), 啟動(dòng)應(yīng)用程序
  app.main();
}

3.5. 編寫集成測(cè)試代碼

現(xiàn)在我們有了待測(cè)應(yīng)用,我們可以為它編寫測(cè)試文件了煮剧。這包含了四個(gè)步驟:

  • 創(chuàng)建 SerializableFinders 定位指定組件

  • setUpAll() 函數(shù)中運(yùn)行測(cè)試案例前斥滤,先與待測(cè)應(yīng)用建立連接

  • 測(cè)試重要場(chǎng)景

  • 完成測(cè)試后,在 teardownAll() 函數(shù)中與待測(cè)應(yīng)用斷開連接

test_driver/app_test.dart 文件中增加以下代碼:

import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';

void main() {
  group("Counter App Test", () {
    FlutterDriver driver;

    // 初始化操作
    setUpAll(() async {
      driver = await FlutterDriver.connect();
    });

    // 測(cè)試結(jié)束操作
    tearDownAll(() {
      if (driver != null) {
        driver.close();
      }
    });

    // 編寫測(cè)試代碼
    final counterTextFinder = find.byValueKey('counter');
    final buttonFinder = find.byValueKey('increment');

    test("starts at 0", () async {
      expect(await driver.getText(counterTextFinder), "0");
    });

    test("on tap click", () async {
      await driver.tap(buttonFinder);

      expect(await driver.getText(counterTextFinder), "1");
    });
  });
}

3.6. 運(yùn)行集成測(cè)試

首先勉盅,啟動(dòng)安卓模擬器或者 iOS 模擬器佑颇,或者直接把 iOS 或 Android 真機(jī)連接到你的電腦上。

接著草娜,在項(xiàng)目的根文件夾下運(yùn)行下面的命令:

flutter drive --target=test_driver/app.dart

這個(gè)指令的作用:

  1. 創(chuàng)建 --target 目標(biāo)應(yīng)用并且把它安裝在模擬器或真機(jī)中
  2. 啟動(dòng)應(yīng)用程序
  3. 運(yùn)行位于 test_driver/ 文件夾下的 app_test.dart 測(cè)試套件

運(yùn)行結(jié)果:我們會(huì)發(fā)現(xiàn)正常運(yùn)行挑胸,并且結(jié)果app中的FloatingActionButton自動(dòng)被點(diǎn)擊了一次。

參考:小碼哥Flutter

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宰闰,一起剝皮案震驚了整個(gè)濱河市嗜暴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌议蟆,老刑警劉巖闷沥,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異咐容,居然都是意外死亡舆逃,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)路狮,“玉大人虫啥,你說(shuō)我怎么就攤上這事⊙俜粒” “怎么了涂籽?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵馅精,是天一觀的道長(zhǎng)衡蚂。 經(jīng)常有香客問我,道長(zhǎng)螟加,這世上最難降的妖魔是什么直焙? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任景东,我火速辦了婚禮,結(jié)果婚禮上奔誓,老公的妹妹穿的比我還像新娘斤吐。我一直安慰自己,他們只是感情好厨喂,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布和措。 她就那樣靜靜地躺著,像睡著了一般蜕煌。 火紅的嫁衣襯著肌膚如雪派阱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天幌绍,我揣著相機(jī)與錄音,去河邊找鬼故响。 笑死傀广,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的彩届。 我是一名探鬼主播伪冰,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼樟蠕!你這毒婦竟也來(lái)了贮聂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤寨辩,失蹤者是張志新(化名)和其女友劉穎吓懈,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體靡狞,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡耻警,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甘穿。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腮恩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出温兼,到底是詐尸還是另有隱情秸滴,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布募判,位于F島的核電站荡含,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏兰伤。R本人自食惡果不足惜内颗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望敦腔。 院中可真熱鬧均澳,春花似錦、人聲如沸符衔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)判族。三九已至躺盛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間形帮,已是汗流浹背槽惫。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辩撑,地道東北人界斜。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像合冀,于是被迫代替她去往敵國(guó)和親各薇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容