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 的功能,亲测有效,如果大家有什么疑问的话,欢迎留言联系我。整理不易,转载请标明出处,谢谢!

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