鸿蒙6.0应用开发——自动化测试框架开发
鸿蒙6.0应用开发——自动化测试框架开发文章目录鸿蒙6.0应用开发——自动化测试框架开发概述场景案例场景描述实现原理开发步骤实现效果概述自动化测试框架是一套面向多设备、全场景的端侧测试体系基于DevEco Studio开发环境和hvigor构建系统整合了UI测试ohos.UiTest、单元测试ohos/hypium等能力通过标准化的工程结构、编码规范与执行流程支撑开发者实现高效高质量验证。该框架涵盖单元测试框架、UI测试框架和白盒性能测试框架。单元测试框架是自动化测试框架基础底座UI测试脚本和性能测试脚本需基于单元测试框架进行开发用于定义测试用例及验证执行结果。UI测试框架调用UiTest接口进行UI界面查找和模拟操作。白盒性能测试框架调用PerfTest接口采集和度量测试应用内指定逻辑执行时的基础性能数据。本文介绍了单元测试框架和UI测试框架的实现旨在帮助开发者了解和掌握自动化测试框架的开发流程与实现细节。关键步骤如下场景案例场景描述本节基于官网codelab《从简单页面开始》介绍自动化测试框架的开发流程与实现细节主要涵盖单元测试和UI测试两部分开发者可根据具体业务场景对应用实施自动化测试。实现原理单元测试使用单元测试框架通过Mock隔离被测代码与外部依赖在无需启动完整应用的前提下对应用逻辑如工具函数、业务服务等进行快速、隔离、可重复的验证。本文采用该框架的以下特性来实现单元测试特性使用说明使用场景基础流程能力通过基础流程能力如describe、it等接口定义测试套和测试用例。并对测试套和测试用例设置预置条件和清理条件。定义测试套和测试用例以及测试用例执行前需要预置条件和执行后需要清理条件的场景如设置定时器和清理定时器。断言能力使用如assertEqual等断言接口判断检验实际值是否等于预期值。检验函数功能是否正常。Mock能力使用Mock能力Mock自定义对象的函数。函数依赖外部资源或复杂逻辑如依赖网络请求返回值。数据驱动使用数据驱动能力对测试套或者测试用例执行若干次。多个测试用例或测试套有相同类型参数如进行压力测试。UI测试通过DevEco Testing的UIViewer获取屏幕坐标点信息并使用UI测试框架接口对指定坐标点或指定控件注入模拟的输入事件如点击、滑动等实现界面交互和验证的自动化。本文针对不同UI测试场景提供如下实现方案场景实现方案查找组件创建On对象通过id或type描述目标控件然后使用findComponent()根据目标控件的属性要求查找该控件。模拟输入通过inputText()模拟文本输入。模拟点击通过Component或Driver中的click属性模拟点击。模拟触摸屏手指滑动通过swipe()方法模拟对轮播图的滑动。等待页面加载使用waitForIdle()等待当前界面的所有控件空闲后再进行下一步操作。UI测试流程图如下开发步骤搭建DevEco Studio环境测试脚本基于DevEco Studio编写开发者需先下载DevEco Studio并完成环境准备。下载安装HypiumHypium是OpenHarmony上的测试框架提供测试用例的编写、执行及结果显示功能用于OpenHarmony系统应用接口和应用界面的测试。使用DevEco Studio打开测试项目并按以下方案进行配置。说明本示例使用的Hypium版本为ohos/hypium(V1.0.24)若开发者需使用最新版本请查看ohos/hypium。方案一通过ohpm命令下载ohos/hypium。ohpm installohos/hypium1.0.24--save-dev方案二在应用工程的oh-package.json5文件的devDependencies中配置版本号然后点击编辑器窗口上方的“Sync Now”同步工程即可使用对应版本的框架功能。新建测试脚本参考创建ArkTS测试用例导入所需的单元测试框架能力及其他测试脚本中依赖的接口编写单元测试脚本。启动被测试页面检查设备显示的页面是否为预期页面。流程图如下在自动化测试中常用基础流程能力的it定义测试用例其参数如下参数名类型必填说明testCaseNamestring是测试用例的名称用于标识该测试用例。attributeTestType | Size | Level是测试类型用于标记测试用例的类型。funcFunction是异步函数async包含测试用例的具体逻辑。使用it创建测试用例后通过AbilityDelegatorRegistry获取应用包名构造want启动对象、调用startAbility()启动应用。在应用加载完成后调用getCurrentTopAbility()获取设备上前台显示页面并使用expect()和assertEqual()断言当前页面是否为预期启动页面。const delegator: abilityDelegatorRegistry.AbilityDelegator abilityDelegatorRegistry.getAbilityDelegator(); export default function UITest() { describe(UITest, () { /** * Start the application to be tested. */ it(startApp, Level.LEVEL3, async (done: Function) { hilog.info(0x0000, testTag, %{public}s, UITest: TestUiExample begin); // Initialize the Driver object. const driver Driver.create(); const bundleName abilityDelegatorRegistry.getArguments().bundleName; // Specify the bundle name and ability name of the application to be tested. const want: Want { bundleName: bundleName, abilityName: EntryAbility } // Start the application to be tested. await delegator.startAbility(want); // Wait until the application starts. await driver.waitForIdle(4000, 5000); const ability: UIAbility await delegator.getCurrentTopAbility(); hilog.info(0x0000, testTag, %{public}s, get top ability); // Ensure that the top ability of the application is the specified ability. expect(ability.context.abilityInfo.name).assertEqual(EntryAbility); done(); }) // ... }) }编写单元测试用例基础流程能力使用基础流程能力beforeAll()定义预置条件afterAll()定义清理条件。预置条件在所有测试用例开始前执行一次清理条件在所有测试用例结束后执行一次。letsuccess-1;lettimeout0;beforeAll((){// Preset increment action before all test cases of the test suite start.success;// Set a timer before all test cases of the test suite start.timeoutsetTimeout((){hilog.info(0x0000,testTag,%{public}s,setTimeout);},1000);})beforeEach((){// Preset increment action before each test case of the test suite starts.success;})afterEach((){hilog.info(0x0000,testTag,%{public}s,success:${success});})afterAll((){hilog.info(0x0000,testTag,%{public}s,AfterAll executed);hilog.info(0x0000,testTag,%{public}s,success:${success});// Clear the timer After all test cases of the test suite end.clearTimeout(timeout);})断言能力通过assertUndefined()判断被检验的值是否为undefined并使用assertEqual()检验实际值是否符合预期值。it(inputAccountLength,0,(){letinputAccountLengthCommonConstants.INPUT_ACCOUNT_LENGTH;// Check if INPUT_ACCOUNT_LENGTH is not undefined.expect(inputAccountLength).not().assertUndefined();expect(inputAccountLength).assertEqual(11);})检验mainViewModel类中自定义函数返回值的长度及数据类型是否符合预期。it(getFirstGridData,0,(){constfirstGridDatamainViewModel.getFirstGridData();// Verify if the return value of getFirstGridData is eight.expect(firstGridData.length).assertEqual(8);// Verify if the type of firstGridData[0] is ItemData.expect(firstGridData[0]instanceofItemData).assertTrue();})Mock能力对mainViewModel类中的getSwiperImages()函数进行Mock并设置函数被Mock后的返回值。用例执行完毕后恢复被Mock对象的实例。it(getSwiperImages,0,(){constswiperImagesmainViewModel.getSwiperImages();expect(swiperImages).assertInstanceOf(Array);expect(swiperImages.length).assertEqual(4);// Mock the getSwiperImages function of the mainViewModel class.letmockernewMockKit();letgetSwiperImagesmocker.mockFunc(mainViewModel,mainViewModel.getSwiperImages);// The result [] is returned when the function is called with any arguments passed in.when(getSwiperImages)(ArgumentMatchers.any).afterReturn([]);expect(mainViewModel.getSwiperImages()).assertInstanceOf(Array);expect(mainViewModel.getSwiperImages().length).assertEqual(0);// Restore the mocked object instances.mocker.clear(mainViewModel);// Verify if the mocked object instances is restored.expect(mainViewModel.getSwiperImages().length).assertEqual(4);})数据驱动数据驱动需要使用Ability能力可参考自定义Ability和Resources。文件内容示例可在运行测试用例后在对应模块的build/{productName}/intermediates/src/ohosTest下查看。定义Ability后需要在module.json5文件中补充配置字段mainElement、pages和abilities。关于字段的具体说明请参考module.json5配置文件。{module:{name:entry_test,type:feature,description:$string:module_test_desc,mainElement:TestAbility,// Corresponds to the ability name in the abilities section below.deviceTypes:[phone],deliveryWithInstall:true,installationFree:false,pages:$profile:test_pages,// Corresponds to the test_pages.json file under resources base profile.abilities:[// Configuration of the ability to add.{name:TestAbility,srcEntry:./ets/testability/TestAbility.ets,description:$string:TestAbility_desc,icon:$media:icon,label:$string:TestAbility_label,exported:true,startWindowIcon:$media:icon,startWindowBackground:$color:start_window_background}]}}数据驱动能力依据测试数据配置驱动测试用例的执行次数及每次执行时的参数传递使用时依赖data.json配置文件。{suites:[{describe:[MainViewModelTest],stress:1,items:[{it:testDataDriverAsync,stress:2,params:[{name:tom,value:5},{name:jerry,value:4}]},{it:testDataDriver,stress:3}]}]}Stage模型在测试工程中的TestAbility目录下TestAbility.ets文件中导入data.json并在文件中的Hypium.hypiumTest()函数执行前设置参数数据。exportdefaultclassTestAbilityextendsUIAbility{abilityDelegator:abilityDelegatorRegistry.AbilityDelegator;constructor(){super();this.abilityDelegatorabilityDelegatorRegistry.getAbilityDelegator();}onCreate(want:Want,launchParam:AbilityConstant.LaunchParam){hilog.info(0x0000,testTag,%{public}s,TestAbility onCreate);hilog.info(0x0000,testTag,%{public}s,want param:JSON.stringify(want)??);hilog.info(0x0000,testTag,%{public}s,launchParam:JSON.stringify(launchParam)??);letabilityDelegatorArguments:abilityDelegatorRegistry.AbilityDelegatorArgs;abilityDelegatorArgumentsabilityDelegatorRegistry.getArguments();hilog.info(0x0000,testTag,%{public}s,start run testcase!!!);// Set the data before Hypium.hypiumTest() is executed.Hypium.setData(data);Hypium.hypiumTest(this.abilityDelegator,abilityDelegatorArguments,testsuite);}// ...}在data.json文件配置的测试套MainViewModelTest中定义测试用例测试用例名称应与配置文件中items下的it名称一致。interfaceParmObj{name:string,value:number}exportdefaultfunctionMainViewModelTest(){describe(MainViewModelTest,(){// ...it(testDataDriverAsync,0,async(done:Function,data:ParmObj){// Use data object to receive parameters passed from data.json.hilog.info(0x0000,testTag,%{public}s,name:${data.name});hilog.info(0x0000,testTag,%{public}s,value:${data.value});// The name passed in data.json is either tom or jerry.expect(data.nametom||data.namejerry).assertTrue();// Check if the actual value and the expected value 4 are within the allowable error range 1.expect(data.value).assertClose(4,1);done();});// ...})}编写UI测试用例在UI测试中开发者可以利用UiTest接口模拟点击、双击、长按、滑动等操作以验证应用程序中的UI行为。模拟文本输入通过On对象匹配目标控件然后使用inputText()模拟文本输入。it(accountInputText,TestType.FUNCTION,async(){letdriverDriver.create();// Match TextInput component by id.letonON.id(account);letaccountInputawaitdriver.findComponent(on);awaitaccountInput.inputText(123456);letaccountawaitaccountInput.getText();expect(account).assertEqual(123456);})模拟触摸屏手指操作使用click()模拟触摸屏手指操作以收起键盘然后通过findComponent()查找Button控件点击该按钮进行登录操作。it(loginButton,TestType.FUNCTION,async(){letdriverDriver.create();// Click the location of the confirm button in the input method to collapse the input method.awaitdriver.click(1196,2511);awaitdriver.waitForIdle(2000,3000);// Check if the button is displayed.letloginButtonawaitdriver.findComponent(ON.type(Button));awaitloginButton.click();// Wait the application for loading to the main page.awaitdriver.waitForIdle(4000,5000);})等待Swiper控件加载完成后使用swipe()模拟触摸屏手指滑动。it(swiper,TestType.FUNCTION,async(){letdriverDriver.create();// Wait the Swiper component for displaying in the current page.awaitdriver.waitForComponent(ON.type(Swiper),2000);// Check if the Swiper component exists.awaitdriver.assertComponentExist(ON.type(Swiper));awaitdriver.waitForIdle(1000,2000);// Swipe the carousel from right to left.awaitdriver.swipe(1100,700,100,700,3000);// Wait for the swipe operation to complete.awaitdriver.waitForIdle(1000,2000);awaitdriver.swipe(1100,700,100,700,3000);awaitdriver.waitForIdle(1000,2000);})页面加载等待使用swipe()切换页面后通过waitForIdle()和waitForComponent()等待Toggle控件出现来判断页面跳转是否完成。it(setting,TestType.FUNCTION,async(){letdriverDriver.create();awaitdriver.swipe(1100,1500,100,1500,3000);awaitdriver.waitForIdle(1000,2000);// Match the Toggle component in the ListItem component.letonON.type(Toggle).within(ON.type(ListItem));awaitdriver.waitForComponent(on,2000);awaitdriver.assertComponentExist(on);// ...})执行测试脚本连接目标测试设备如手机或模拟器后在DevEco Studio页面点击对应按钮或通过命令行执行测试脚本。详细可参考DevEco Studio执行测试脚本和命令行执行测试脚本。实现效果自动化测试实现效果如下图所示

相关新闻