HarmonyOS ArkTS 用户首选项的开辟及测试

打印 上一主题 下一主题

主题 962|帖子 962|积分 2886

本节以一个“账本”为例,使用首选项的相干接口实现了对账单的增、删、改、查操纵,并使用自动化测试框架arkxtest来对应用举行自动化测试。
为了演示该功能,创建一个名为“ArkTSPreferences”的应用。应用源码可以在文末《跟老卫学HarmonyOS开辟》链接找到。
1. 操纵Preferences

首先要获取一个Preferences来操纵首选项。
在src/main/ets目次下创建名为“common”目次,用于存放常用的工具类。在该common目次创建工具类PreferencesUtil,代码如下:
  1. // 导入preferences模块
  2. import { preferences } from '@kit.ArkData';
  3. import { BusinessError } from '@kit.BasicServicesKit';
  4. import { common } from '@kit.AbilityKit';
  5. let context = getContext(this) as common.UIAbilityContext;
  6. let options: preferences.Options = { name: 'myStore' };
  7. export default class PreferencesUtil {
  8.   private dataPreferences: preferences.Preferences | null = null;
  9.   // 调用getPreferences方法读取指定首选项持久化文件,
  10.   // 将数据加载到Preferences实例,用于数据操作
  11.   async getPreferencesFromStorage() {
  12.     await preferences.getPreferences(context, options).then((data) => {
  13.       this.dataPreferences = data;
  14.       console.info(`Succeeded in getting preferences`);
  15.     }).catch((err: BusinessError) => {
  16.       console.error(`Failed to get preferences, Cause:` + err);
  17.     });
  18.   }
  19. }
  20. 复制
复制代码
为了对数据举行生存、查询、删除查操纵,我们要封装对应接口。首选项接口提供的生存、查询、删除方法均有callback和Promise两种异步回调方式,本例子使用了Promise异步回调。代码如下:
  1. // 将用户输入的数据,保存到缓存的Preference实例中
  2. async putPreference(key: string, data: string) {
  3.   if (this.dataPreferences === null) {
  4.     await this.getPreferencesFromStorage();
  5.   } else {
  6.     await this.dataPreferences.put(key, data).then(() => {
  7.       console.info(`Succeeded in putting value`);
  8.     }).catch((err: BusinessError) => {
  9.       console.error(`Failed to get preferences, Cause:` + err);
  10.     });
  11.     // 将Preference实例存储到首选项持久化文件中
  12.     await this.dataPreferences.flush();
  13.   }
  14. }
  15. // 使用Preferences的get方法读取数据
  16. async getPreference(key: string) {
  17.   let result: string= '';
  18.   if (this.dataPreferences === null) {
  19.     await this.getPreferencesFromStorage();
  20.   } else {
  21.     await this.dataPreferences.get(key, '').then((data) => {
  22.       result = data.toString();
  23.       console.info(`Succeeded in getting value`);
  24.     }).catch((err: BusinessError) => {
  25.       console.error(`Failed to get preferences, Cause:` + err);
  26.     });
  27.   }
  28.   return result;
  29. }
  30. // 从内存中移除指定文件对应的Preferences单实例。
  31. // 移除Preferences单实例时,应用不允许再使用该实例进行数据操作,否则会出现数据一致性问题。
  32. async deletePreferences() {
  33.   preferences.deletePreferences(context, options, (err: BusinessError) => {
  34.     if (err) {
  35.       console.error(`Failed to delete preferences. Code:${err.code}, message:${err.message}`);
  36.       return;
  37.     }
  38.     this.dataPreferences = null;
  39.     console.info('Succeeded in deleting preferences.');
  40.   })
  41. }
  42. 复制
复制代码
2. 账目信息的表现

在src/main/ets目次下创建名为“database”目次,并在该database目次下创建类AccountData,代码如下:
  1. export default interface AccountData {
  2.   id: number;
  3.   accountType: number;
  4.   typeText: string;
  5.   amount: number;
  6. }
  7. 复制
复制代码
AccountData各属性寄义如下:


  • id:主键。
  • accountType:账目类型。0表现支出;1表现收入。
  • typeText:账目的具体类别。
  • amount:账目金额。
3. 计划界面

为了简化程序,突出核心逻辑,我们的界面计划的非常简单,只是一个Text组件和四个Button组件。四个Button组件用于触发增、删、改、查操纵,而Text组件用于展示每次操纵后的效果。修改Index代码如下:
  1. // 导入PreferencesUtil
  2. import PreferencesUtil from '../common/PreferencesUtil';
  3. // 导入AccountData
  4. import AccountData from '../database/AccountData';
  5. const PREFERENCES_KEY = 'fruit';
  6. @Entry
  7. @Component
  8. struct Index {
  9.   @State message: string = 'Hello World'
  10.   private preferencesUtil = new PreferencesUtil();
  11.   async aboutToAppear() {
  12.     // 初始化首选项
  13.     await this.preferencesUtil.getPreferencesFromStorage();
  14.     // 获取结果
  15.     this.preferencesUtil.getPreference(PREFERENCES_KEY).then(resultData => {
  16.       this.message = resultData;
  17.     });
  18.   }
  19.   build() {
  20.     Row() {
  21.       Column() {
  22.         Text(this.message)
  23.           .id('text_result')
  24.           .fontSize(50)
  25.           .fontWeight(FontWeight.Bold)
  26.         // 增加
  27.         Button(('增加'), { type: ButtonType.Capsule })
  28.           .width(140)
  29.           .fontSize(40)
  30.           .fontWeight(FontWeight.Medium)
  31.           .margin({ top: 20, bottom: 20 })
  32.           .onClick(() => {
  33.             // 保存数据
  34.             let newAccount: AccountData = { id: 1, accountType: 0, typeText: '苹果', amount: 0 };
  35.             this.preferencesUtil.putPreference(PREFERENCES_KEY, JSON.stringify(newAccount));
  36.           })
  37.         // 查询
  38.         Button(('查询'), { type: ButtonType.Capsule })
  39.           .width(140)
  40.           .fontSize(40)
  41.           .fontWeight(FontWeight.Medium)
  42.           .margin({ top: 20, bottom: 20 })
  43.           .onClick(() => {
  44.             // 获取结果
  45.             this.preferencesUtil.getPreference(PREFERENCES_KEY).then(resultData => {
  46.               this.message = resultData;
  47.             });
  48.           })
  49.         // 修改
  50.         Button(('修改'), { type: ButtonType.Capsule })
  51.           .width(140)
  52.           .fontSize(40)
  53.           .fontWeight(FontWeight.Medium)
  54.           .margin({ top: 20, bottom: 20 })
  55.           .onClick(() => {
  56.             // 修改数据
  57.             let newAccount: AccountData = { id: 1, accountType: 1, typeText: '栗子', amount: 1 };
  58.             this.preferencesUtil.putPreference(PREFERENCES_KEY, JSON.stringify(newAccount));
  59.           })
  60.         // 删除
  61.         Button(('删除'), { type: ButtonType.Capsule })
  62.           .width(140)
  63.           .fontSize(40)
  64.           .fontWeight(FontWeight.Medium)
  65.           .margin({ top: 20, bottom: 20 })
  66.           .onClick(() => {
  67.             this.preferencesUtil.deletePreferences();
  68.           })
  69.       }
  70.       .width('100%')
  71.     }
  72.     .height('100%')
  73.   }
  74. }
  75. 复制
复制代码
上述代码,在aboutToAppear生命周期阶段,初始化了Preferences。点击“新增”会将预设好的数据“{ id: 1, accountType: 0, typeText: '苹果', amount: 0 }”写入到Preferences。点击“修改”会将预设好的“{ id: 1, accountType: 1, typeText: '栗子', amount: 1 }”的数据更新到Preferences。点击“删除”则会从内存中移除指定文件对应的Preferences单实例。
4. 运行

运行应用表现的界面效果如下图所示。


当用户点击“增加”后再点击“查询”时,界面如下图所示,证明数据已经成功写入Preferences。

当用户点击“修改”后再点击“查询”时,界面如下图所示,证明数据已经被修改并更新回Preferences。


当用户点击“删除”后再点击“查询”时,界面如下图所示,证明数据已经从Preferences删除。


5. 编写UI测试脚本

UI测试基于单元测试,UI测试脚本在单元测试脚本上增加了对UiTest接口,具体请参考API文档。
如下的示例代码是在上面的单元测试脚本底子上增量编写,实现的是在启动的应用页面上举行点击操纵,然后检测当前页面变化是否为预期变化。
在“ohosTest/ets/test/”目次下,是专门用于存放具体测试代码的。在该目次下,已经存在了一个测试用例样板代码Ability.test.ets文件,基于该文件举行编写UI测试脚本。修改后,代码如下:
  1. import { describe, it, expect } from '@ohos/hypium';
  2. import { abilityDelegatorRegistry, Driver, ON } from '@kit.TestKit';
  3. import { UIAbility, Want } from '@kit.AbilityKit';
  4. import AccountData from '../../../main/ets/database/AccountData';
  5. const delegator: abilityDelegatorRegistry.AbilityDelegator = abilityDelegatorRegistry.getAbilityDelegator()
  6. const bundleName = abilityDelegatorRegistry.getArguments().bundleName;
  7. function sleep(time: number) {
  8.   return new Promise<void>((resolve: Function) => setTimeout(resolve, time));
  9. }
  10. export default function abilityTest() {
  11.   describe('ActsAbilityTest', () => {
  12.     // 编写UI测试脚本
  13.     it('testUi',0, async (done: Function) => {
  14.       console.info("uitest: testUi begin");
  15.       // 启动待测试的 ability
  16.       const want: Want = {
  17.         bundleName: bundleName,
  18.         abilityName: 'EntryAbility'
  19.       }
  20.       await delegator.startAbility(want);
  21.       await sleep(1000);
  22.       // 检查顶层显示的 ability
  23.       await delegator.getCurrentTopAbility().then((Ability: UIAbility)=>{
  24.         console.info("get top ability");
  25.         expect(Ability.context.abilityInfo.name).assertEqual('EntryAbility');
  26.       })
  27.       // UI 测试代码
  28.       // 初始化driver
  29.       let driver = Driver.create();
  30.       await driver.delayMs(1000);
  31.       // 查找'增加'按钮
  32.       let buttonAdd = await driver.findComponent(ON.text('增加'));
  33.       // 点击按钮
  34.       await buttonAdd.click();
  35.       await driver.delayMs(1000);
  36.       // 查找'查询'按钮
  37.       let buttonQuery = await driver.findComponent(ON.text('查询'));
  38.       // 点击按钮
  39.       await buttonQuery.click();
  40.       await driver.delayMs(1000);
  41.       // 查找 id 为'text_result'的 Text 组件
  42.       let text = await driver.findComponent(ON.id('text_result'));
  43.       // 检查文本内容
  44.       await text.getText().then(result => {
  45.         let newAccount: AccountData = { id: 1, accountType: 0, typeText: '苹果', amount: 0 };
  46.         expect(result).assertEqual(JSON.stringify(newAccount))
  47.       });
  48.       done();
  49.     })
  50.   })
  51. }
  52. 复制
复制代码
上述代码主要做了以下几件事:


  • 查找增加按钮,并举行点击;
  • 查找查询按钮,并举行点击;
  • 查找Text组件,断言该Text组件文本内容是否与渴望的值一致。
6. 运行UI测试脚本

首先,启动模拟器或者真机。在模拟器或者真机上安装应用。
其次,点击如下图13-1所示的测试用例的左侧三角按钮,以运行测试脚本。


如果断言成功,则说明测试通过,可以看到如下绿色打勾的标识。


如果断言失败,则说明测试没有通过,可以看到如下赤色告警标识,并会提示断言失败的缘故原由。


7. 参考



  • 《跟老卫学HarmonyOS开辟》 开源免费教程,GitHub - waylau/harmonyos-tutorial: HarmonyOS Tutorial. 《跟老卫学HarmonyOS开辟》
  • 《鸿蒙HarmonyOS手机应用开辟实战》(清华大学出版社)
  • 《鸿蒙HarmonyOS应用开辟从入门到醒目战》(北京大学出版社)
  • “鸿蒙体系实战短视频App 从0到1把握HarmonyOS”(鸿蒙体系实战短视频App 从0到1把握HarmonyOS_实战课程_慕课网)
  • 《鸿蒙HarmonyOS应用开辟入门》(清华大学出版社)
  • “2024鸿蒙零底子快速实战-仿抖音App开辟(ArkTS版)”(2024 鸿蒙零底子快速实战-仿抖音App开辟( ArkTS版 )_实战课程_慕课网)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

涛声依旧在

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表