Flutter android/iOS自動化集成測試並上傳到Firebase testlab的處理方案

前言:

        作爲移動應用的開發者,會編寫自動化測試代碼將大大解放我們的雙手。移動應用的測試工作量通常很大,爲了驗證真實用戶的使用體驗,往往需要跨越多個Android和iOS平臺的物理設備手動完成測試。隨着產品功能不斷迭代累積,測試工作量和複雜度也隨之大幅增長,手動測試變得越來越困難,而自動化測試將重複的、機械的人工操作變爲自動化的驗證步驟,極大地節省人力、時間和硬件資源,從而提高了測試效率,保證了應用程序的穩定性和功能的完整性。接下來我簡單總結一下如何在Flutter通過integration test實現自動化集成測試並上傳到Firebase testlab的處理方案,有需要的話可以參考。

實現的步驟:

1. 在 pubspec.yaml 文件添加插件

dev_dependencies:
  integration_test:
    sdk: flutter
  flutter_test:
    sdk: flutter

2.在項目的根目錄創建integration_test和test_driver文件夾(與lib同一層目錄,否則自動測試跑不起來)

integration_test目錄:用於編寫集成測試的測試用例代碼
test目錄:用於編寫單元測試和組件測試的測試用例代碼
test_driver目錄:用於文本集成的調用驅動程序integration driver,使其能夠使用 flutter drive 命令運行新測試

integration_test/
  testlab_login.dart
  testlab_register.dart
lib/
  ...
test/
  login_test.dart
  register_test.dart
test_driver/
  integration_test.dart

3.爲測試驅動程序腳本創建一個入口點integration_test.dart

import 'package:integration_test/integration_test_driver.dart';

Future<void> main() => integrationDriver();

4. 常用的自動化測試構造方法

(1) 常用的 expct 預期驗證:
findsOneWidget  驗證 widget 只在屏幕中出現一次
findsNothing  驗證沒有可被查找的 widgets
findsWidgets  驗證一個或多個 widgets 被找到
findsNWidgets  驗證特定數量的 widgets 被找到

static Future<void> testMethod01(
      WidgetTester tester) async {
    final Finder item1 = find.text('Hello');
    final Finder item2 = find.text('Hi');
    final Finder item3 = find.text('Other');
    
    expect(item1, findsOneWidget);//findsOneWidget 驗證 widget 只在屏幕中出現一次
    expect(item2, findsWidgets);//findsWidgets 驗證一個或多個 widgets 被找到
    expect(item3, findsNothing);//findsNothing 驗證沒有可被查找的 widgets
    await tester.longPress(item1);//輸入預期的內容
    await tester.longPress(item2);
    await tester.longPress(item3);
    await tester.pumpAndSettle();
  }

(2) 常用的finder構造函數:
find.text  通過特定文本 String 來查找 widget
find.byKey 通過 Key 來查找 widget
find.byType 通過已經指定的 Type 類型來查找 widget

 static Future<void> testMethod02(
      WidgetTester tester, String email) async {
    //通過特定文本 String 來查找 widget
    await tester.longPress(find.text("Next"));
    //通過 Key 來查找 widget
    await tester.longPress(find.byKey(const Key("k_key")));//在頁面的組件設置key
    //通過 TextFormField 類型的來查找 widget
    await tester.enterText(find.byType(TextFormField), email);
    await tester.pumpAndSettle();
  }

(3) WidgetTester 還提供文本輸入、點擊、拖動的相關方法:

tester.enterText();  //文本輸入
tester.tap();  //點擊
tester.longPress(); //長點擊
tester.drag(); //拖動
tester.pumpAndSettle();  //重新構建widget

static Future<void> testMethod03(
      WidgetTester tester, String email) async {
    //通過輸入的方式來查找 TextFormField 的類型的 widget
    await tester.enterText(find.byType(TextFormField), email);
    //通過點擊的方式查找特定文本 String 的 widget
    await tester.tap(find.text("Next"));
    //通過長點擊的方式查找特定文本 String 的 widget
    await tester.longPress(find.text("Next"));
    await tester.pumpAndSettle();
}

5.封裝自動化測試的用例代碼 (以登錄爲例)

class TestLabCommon {
  static Future<void> testLanding(WidgetTester tester) async {
     //通過長點擊的方式來查找顯示特定文本 String 的 widget
    await tester.longPress(find.text("Login"));
    //爲了保證模擬用戶交互實現後,widget 樹能重建
    await tester.pumpAndSettle();
  }

  static Future<void> testLoginEmail(
      WidgetTester tester, String email) async {
    //通過輸入的方式來查找 TextFormField的類型的 widget
    await tester.enterText(find.byType(TextFormField), email);
    await tester.tap(find.text("Next"));
    await tester.pumpAndSettle();
  }

  static Future<void> testLoginPassword(WidgetTester tester,String password) async {
    //通過輸入的方式來查找 TextFormField 的類型的 widget
    await tester.enterText(find.byType(TextFormField), password);
    await tester.longPress(find.text("Next"));
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1));
    await tester.pump(const Duration(seconds: 1)); //3秒
  }

  static Future<void> testLogin(WidgetTester tester, String email, String password) async {
    await testLandingLogin(tester); 
    await testLoginNewUserEmail(tester, email); 
    await testLoginNewUserPassword(tester, password); 
  }
}

6.調用自動化測試的用例代碼

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  String groupDescription = '冒煙測試';

  group(groupDescription, () {
    String testcaseDescription = '登錄流程自動化測試';
    testWidgets(testcaseDescription, (tester) async {
      app.main();//調用app的入口函數
      await tester.pumpAndSettle();
      await TestLabCommon.testLogin(
          tester, email, password);
    });
  });
}

7. 在 Firebase testlab 進行測試

 Android 篇:

(1) 在 build.gradle 添加插件

android { 

    defaultConfig { 
    ... 
    minSdkVersion 21 
    targetSdkVersion 33
    versionCode flutterVersionCode.toInteger() 
    versionName flutterVersionName 
    //添加
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    } 
} 

dependencies { 
    //添加
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test:runner:1.3.0'
    androidTestImplementation 'androidx.test:rules:1.3.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

(2) 創建一個新文件 android/app/src/androidTest/java/your/package/yourapp/ MainActivityTest.java

package uk.co.credito.staging;

import androidx.test.rule.ActivityTestRule;

import org.junit.Rule;
import org.junit.runner.RunWith;

import dev.flutter.plugins.integration_test.*;

@RunWith(FlutterTestRunner.class)
public class MainActivityTest {
    @Rule
    public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class, true, false);
}

(3) 本地運行自動化測試腳本代碼

flutter test integration_test/testlab_login.dart

(4) 運行自動化測試腳本代碼

flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/testlab_login.dart

(5) 打包生成測試版本的 apk

pushd android
flutter build apk
./gradlew app:assembleAndroidTest
./gradlew app:assembleDebug -Ptarget=integration_test/testlab_login.dart
popd

(6) 上傳 apk 到 Firebase testlab

> App apk 位置:
   /build/app/outputs/apk/debug/app-debug.apk
> Test app 位置:
    /build/app/outputs/apk/androidTest/app-debug-androidTest.apk

(7) 在 Firebase testlab 創建自定義的 Android 設備

(8) 選擇 integration test 插樁測試,上傳 apk 到 Firebase testlab

(9)選擇自定義的 Android 設備並運行測試

(10) Firebase testlab 插樁測試的詳情,可查看 Android 日誌和視頻

iOS篇:

(1) 在ios/Podfile文件添加

target 'Runner' do
#  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))

  target 'RunnerTests' do
      inherit! :search_paths
  end
end

(2) 運行自動化測試腳本代碼

flutter build ios --config-only integration_test/testlab_login.dart

(3) 添加RunnerTests.m的測試文件

@import XCTest;
@import integration_test;

INTEGRATION_TEST_IOS_RUNNER(RunnerTests)

(4) 在Xcode運行自動化測試代碼

 1. run flutter build ios --config-only integration_test/testlab_login.dart
 2. open ios/Runner.xcworkspace
 3. run 'RunnerTests' in Xcode

(5)在 Flutter 的根目錄運行自動化腳本代碼並打包生成測試版本的應用程序

../build/ios_integ/Build/Products/ios_tests.zip
 output=""../build/ios_integ""
 product=""build/ios_integ/Build/Products""
 dev_target=""16.2""
 flutter build ios integration_test/testlab_login.dart --simulator
 pushd ios
 xcodebuild -workspace Runner.xcworkspace -scheme Runner -config Flutter/Release.xcconfig -derivedDataPath $output -sdk iphoneos build-for-testing
 popd
 pushd $product
 zip -r ""ios_tests.zip"" ""Release-iphoneos"" ""Runner_iphoneos$dev_target-arm64.xctestrun""
 popd

(6)在 Firebase testlab 創建自定義的 iOS 設備

(7)選擇 XCTest測試,上傳 ios_tests.zip 到 Firebase testlab

(8)Firebase testlab 插樁測試的詳情,可查看 iOS 日誌和視頻

總結:Flutter 通過 Integration test 實現 Android/iOS 自動化集成測試並上傳到 Firebase testlab 的功能,親測有效,如果大家有什麼疑問的話,歡迎留言聯繫我。整理不易,轉載請標明出處,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章