目录
1 框架概述
2 测试框架引用方式
3 Java测试用例编写说明
3.1 用例编写基础语法
3.2 使用AbilityDelegator测试应用组件
3.3 API压力测试用例
3.4 数据驱动测试用例
4 JS测试用例编写说明
4.1 JS测试框架工程结构
4.2 用例编写命名建议
4.3 用例编写基础语法
4.4 用例编写属性标注
4.5 同步异步
4.6 数据驱动测试用例
5 测试用例执行方式
1 框架概述
单元测试框架包括Java单元测试框架和JavaScript单元测试框架,它们是HarmonyOS中分别基于Java语言和JavaScript语言编写的应用的测试框架,提供基础的单元测试能力。
Java单元测试框架基于JUnit开源框架和Ability
框架提供的AbilityDelegator实现,并提供多种扩展测试能力,包括:
- 支持单元测试和应用组件(Ability)测试
- 支持编写数据驱动测试用例
- 支持API压力测试/组合测试
- 支持编写应用UI测试
- 支持分布式场景的多设备应用UI测试
JavaScript单元测试框架借鉴OSGI的插件机制,所有的功能都作为服务,注册到核心框架中,框架调用这些服务,从而把所有用例执行起来;另外测试框架还提供event注册,提供在任务、测试套、用例执行前或者执行完之后运行指定的功能。
目前JavaScript单元测试框架已集成在DevEco开发工具的SDK中,可以对JavaScript APP进行单元测试, 基本能力包括:
- 对页面逻辑层的 API 进行测试
- 对 JS framework 进行测试
JaveScript单元测试框架利用HarmonyOS应用框架Entry-Feature机制,将测试代码与测试框架打包成Feature Hap包,与被测试应用运行在同一个进程中,从而支持对应用接口的逻辑测试。您只需要在 Deveco Studio 中将测试代码编译打包成 hap ,一键安装到设备上启动测试框架,运行测试用例即可。
2 测试框架引用方式
编写运行JavaScript测试用例,需要依赖测试框架sdk包,因此需要在app项目编译脚本中配置。
在app项目根目录的build.gradle文件中,按照如下方式添加maven仓:
- allprojects {
- repositories {
- // ensure this maven repository is added
- maven {
- url 'https://developer.huawei.com/repo/'
- }
- }
- }
在模块的build.gradle中,按照如下方式配置测试框架依赖:
- dependencies {
- ohosTestImplementation 'com.huawei.ohos.testkit:runner:2.0.0.100'
- }
3 Java测试用例编写说明
3.1 用例编写基础语法
Java单元测试框架的实现是基于JUnit4开源框架。JUnit为业界通用的Java单元测试框架,它通过Java注解机制提供了一套用例语法规范,用户使用注解来标注测试过程各阶段的生命周期函数。常用的注解包括:
- @BeforeClass: 标注测试类级别的setup方法(函数修饰符为public static void, 无参数),可选
- @AfterClass: 标注测试类级别的teardown方法(函数修饰符为public static void, 无参数),可选
- @Before: 标注测试测试用例的setup方法(函数修饰符为public void, 无参数),可选
- @After: 标注测试测试用例的teardown方法(函数修饰符为publicvoid, 无参数),可选
- @Test: 标注测试用例方法体(函数修饰符为public static void, 无参数),必选
样例代码:
- import org.junit.*;
- public class DemoTest {
- @BeforeClass
- public static void classSetup(){
- //测试类级别的准备动作,测试类执行前执行
- }
- @Before
- public void setup(){
- //测试用例的准备动作,测试用例执行前执行
- }
- @Test
- public void test(){
- //测试用例方法体
- }
- @After
- public void teardown(){
- //测试用例清理动作,测试用例执行后执行
- }
- @AfterClass
- public static void classTeardown(){
- //测试类级别清理动作,测试类执行后执行
- }
- }
3.2 使用AbilityDelegator测试应用组件
AbilityDelegator为元能力子系统提供的测试支持能力,用于控制Ability的生命周期,获取Ability对象状态,注入点击事件等。使用AbilityDelegator可以提升HarmonyOS Java单元测试的开发效率和稳定性。
获取测试APP进程AbilityDelegator单例的方法: AbilityDelegatorRegistry.getAbilityDelegator()
AbilityDelegator
提供的常用测试辅助方法如下表:
表1 AbilityDelegator提供的测试辅助方法
接口名 |
描述 |
getAppContext |
获取HarmonyOS应用Context |
runOnUIThreadSync |
在UI线程执行任务 |
startAbilitySync |
启动Ability并返回Ability对象(只支持启动本地Page类型Ability) |
stopAbility |
销毁Ability对象 |
getCurrentTopAbility |
获取本地Top Ability对象 |
getCurrentAbilitySlice |
获取本地当前AbilitySlice对象 |
trigger[Click/Key/Touch]Event |
模拟点击/按键/触摸事件 |
代码实例:
- import ohos.aafwk.ability.Ability;
- import ohos.aafwk.ability.delegation.AbilityDelegatorRegistry;
- import ohos.aafwk.ability.delegation.IAbilityDelegator;
- import ohos.aafwk.content.Intent;
- import ohos.bundle.ElementName;
- import org.junit.*;
- import java.util.Optional;
- import static org.junit.Assert.assertTrue;
- public class AbilityTest {
- private Ability topAbility = null;
- private static IAbilityDelegator delegator = null;
- @BeforeClass
- public static void classSetup() {
- // 获取delegator实例
- delegator = AbilityDelegatorRegistry.getAbilityDelegator();
- }
- @Test
- public void testMainAbility() {
- Intent intent = new Intent();
- ElementName name = new ElementName("", "com.huawei.testaa", MainAbility.class.getName());
- intent.setElement(name);
- // 使用delegator启动ability
- delegator.startAbilitySync(intent);
- // 使用delegator获取当前ability对象
- topAbility = delegator.getCurrentTopAbility();
- assertNotNull("Start ability failed", topAbility);
- assertEquals("Not the expected ability", MainAbility.class, topAbility.getClass());
- }
- @After
- public void teardown() {
- if (topAbility != null) {
- topAbility.terminateAbility();
- }
- }
- }
3.3 API压力测试用例
3.3.1 单用例重复执行
在测试用例上添加@Benchmark注解, 可以控制用例方法体单线程重复执行,直到达到执行的指定的重复次数;如果重复执行过程中出现异常,将停止执行,并生成用例fail结果; 如果重复执行过程中未产生异常,生成用例pass结果。
用例重复执行次数,通过@Benchmark注解的repeats属性指定,默认值为7。
- package ohos.testkit.paramtest;
- import ohos.testkit.annotation.Benchmark;
- import ohos.testkit.runner.OhosJUnitClassRunner;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- @RunWith(OhosJUnitClassRunner.class)
- public class SpecialTest {
- @Test
- @Benchmark(repeats = 7)
- public void benchmarkTest() {
- Math.abs(1.2);
- }
- }
用例编写要点:
- 测试类必须添加注解@RunWith(OhosJUnitClassRunner.class)
- 测试类必须添加注解@Benchmark(repeats = num), 通过repeats属性指定方法体需要重复执行的次数
- 方法体内可以有assert断言,也可以没有。如果没有assert断言,执行框架会自动捕获方法体的执行异常,并据此生成fail结果
3.3.2 单用例并发执行
在测试用例上添加@Concurrent注解, 可以控制用例方法体多线程并发执行;如果任何一个线程中捕获到异常,将生成用例fail结果; 如果所有线程正常执行完成,生成用例pass结果。
用例执行并发数,通过@Concurrent注解的parallels属性指定,默认值为3。
- package ohos.testkit.paramtest;
- import ohos.testkit.annotation.Concurrent;
- import ohos.testkit.runner.OhosJUnitClassRunner;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- @RunWith(OhosJUnitClassRunner.class)
- public class SpecialTest {
- @Test
- @Concurrent(parallels = 3)
- public void concurrentTest() {
- Math.abs(1.2);
- }
- }
用例编写要点:
- 测试类必须添加注解@RunWith(OhosJUnitClassRunner.class)
- 测试类必须添加注解@Concurrent(parallels= num), 通过parallels属性指定方法体执行的并发数目
- 方法体内可以有assert断言,也可以没有。如果没有assert断言,执行框架会自动捕获方法体的执行异常,并据此生成fail结果
3.3.3 多用例组合(测试套)执行
ohos.testkit提供SuiteExecutor公共API,用于执行自定义的测试套。
在用例设计层面,一个suite为一组普通用例的集合,用于验证组合场景(例如将一组接口测试的用例组合为测试套执行,可以验证接口间的相互影响,接口对调用时序的兼容性等)。
在用例执行层面,suite内部的用例集可以串行执行,或者并行执行;可以设置执行轮数用于多轮重复执行;可以使用SuiteExecutor提供的randomSelect机制实现用例随机组合,即从suite全量用例中随机抽取用例组合执行。
API使用说明
1. SuiteExecutor.runSuite
- /**
- * Run a test suite.
- *
- * @param tests 测试用例集,一个或者多个测试用例的名称.
- * @param policy 用例集执行策略,可取值SERIAL(串行)和PARALLEL(并行).
- * @param rounds 用例集重复执行的轮数.
- */
- public static void runSuite(String[] tests, RunPolicy policy, int rounds, int randomSelect);
说明:
- 用例集内的用例名称格式规定为:"包名.类名#方法名", 例如: ohos.acts.aafwk.ability.test.AbilityDelegatorOperationTest#testMonitorOperation_0001
- policy取值为SERIAL时,用例执行的顺序与用例集定义的顺序一致
- suite执行过程中,如果捕获到异常,将停止执行(本轮未执行完的用例,以及未执行的轮次将直接跳过),依据该异常产生suite执行结果。
注意:
- suite测试集内,用例不可重复; 且需要保证所有用例必须存在,否则本条用例执行时将生成fail结果。
- 用例的用例名称可以为测试类名例如: com.huawei.fts.test.MathTest, 在这种情况下,SuiteExecutor将执行该类内的所有测试方法。
- suite执行过程中,涉及的测试类所定义的@BeforeClass或@AfterClass,将分别在测试中属于该类的所有用例开始执行之前和全部执行之后执行一次; 测试类所定义的@Before或@After,将分别在测试集中属于该类的每条用例开始执行之前和执行结束之后执行;
2. SuiteExecutor.runSuiteWithRandomSelect
- /**
- * Run a test suite.
- *
- * @param tests 测试用例集,一个或者多个测试用例的名称.
- * @param policy 用例集执行策略,可取值SERIAL(串行)和PARALLEL(并行).
- * @param rounds 用例集重复执行的轮数.
- * @param randomSelect 每轮执行中,随机挑选的用例数目.
- */
- public static void runSuiteWithRandomSelect(String[] tests, RunPolicy policy, int rounds, int randomSelect);
说明:
- randomSelect取值有效(大于零,小于等于用例全集大小)时,每轮执行的用例为测试框架从用例全集中随机挑选出来的。每一轮次的用例集相互独立,无必然联系。
代码实例:
- package ohos.testkit.paramtest;
- import org.junit.Test;
- import ohos.testkit.suitetest.runner.RunPolicy;
- import ohos.testkit.suitetest.runner.SuiteExecutor;
- import static org.junit.Assert.assertEquals;
- public class MathTest {
- private static int sCount = 0;
- @Test
- public void testMathMax() throws InterruptedException {
- int result = Math.max(1, 2);
- assertEquals(2, result);
- }
- @Test
- public void testMathMin() throws InterruptedException {
- int result = Math.min(1, 2);
- assertEquals(1, result);
- }
- @Test
- public void suiteTest() {
- SuiteExecutor.runSuite(new String[]{
- "decc.testkit.paramtest.MathTest#testMathMax",
- "decc.testkit.paramtest.MathTest#testMathMin"},
- RunPolicy.SERIAL, 8);
- }
- }
3.4 数据驱动测试用例
数据驱动测试是指测试用例的测试逻辑与数据分离,编写一条测试用例,通过传入不同的测试数据,达到不同场景的测试覆盖效果。
测试数据通过@Parameters注解,以字符串数组的形式提供, 每个字符串对应一组测试数据;
每组测试数据包含如果入参数据,它们之间通过字符','或者';'进行分割, 分割字符前后的空格将被忽略;
每组测试参数内的入参个数必须与测试方法的参数个数相等,而且按照排列先后顺序一一对应,否则测试用例执行时将产生数据解析异常,或者由于数据错误而导致错误的测试结果;
如下的示例代码中,通过@Parameters注解提供了两组测试数据,每组数据包含3个数据值(与测试方法参数相对应)。测试用例执行时,测试框架将自动解析和转换测试数据并注入测试方法:
- @RunWith(OhosJUnitClassRunner.class)
- public class ParamsInAnnotationTest {
- @Test
- @Parameters({
- "1,2,3",
- "6,7,13"
- })
- public void addTest(int num0, int num1, int expectedSum) {
- assertEquals("unexpected sum", expectedSum, num0, num0);
- }
- }
说明:
- 测试类必须添加注解@RunWith(OhosJUnitClassRunner.class)
- @Parameters注解在每个测试方法上,确定测试方法的参数化数据, 测试方法的参数的个数与Parameters中数据的个数一致。
- 测试框架能够自动转换基本类型数据(char, byte, short, int, long, float, double, boolean)以及String类型和枚举Enum类型,此外还能够自动转换元素为上述10种类型的数组数据(char[], ...String[], Enum[]). 用例编写人员只需按照参数编码规则提供数据即可
- 测试框架内置了List数据转换器,能够自动将指定的测试数据转换为指定类型的List对象; 要使用List转换器时,需要在测试方法的List类型参数前面添加注解@Param(element = Xxx.class), 其中Xxx.class为期望转换的List元素类型. 示例代码:
- @Test
- @Parameters({
- "'1.9,2.3,0', 1, 2.3",
- "'34.58,99,0.7', 2, 0.7"
- })
- public void listParamTest(@Param(element = float.class) List numbers, int index, float expValue) {
- assertEquals(expValue, numbers.get(index));
- }
- 测试框架提供了自定义数据转换的接口,允许用例编写人员通过自定义的转换规则将@Parameters中输入的字符串参数转换为期望的数据类型。
使用方法为:
- 继承ohos.testkit.paramtest.parameterize.Converter类,实现T convert(Object param)方法, 在该方法中解析字符串并生成目标类型数据;
- 在需要转换的方法参数前加上注解@Param;
- 在@Param注解中,设置converter属性为step1中实现的Converter实现类。
- @Test
- @Parameters({
- "01.12.2012, 2012, 12, 1",
- "03.11.2013, 2013, 11, 3",
- "04.10.2014, 2014, 10, 4"
- })
- public void testCalendar(@Param(converter = DateConverter.class) Date date,
- int expYear, int expMonth, int expDay) {
- Calendar calendar = createCalendarWithDate(date);
- assertCalendarDate(calendar, expYear, expMonth, expDay);
- }
- public static class DateConverter extends Converter {
- @Override
- public Date convert(Context targetContext, Object param) throws Exception {
- try {
- return new SimpleDateFormat("dd.MM.yyyy").parse(param.toString());
- } catch (ParseException e) {
- throw new Exception(e);
- }
- }
- }
说明:自定义转换器DateConverter用于将@Parameters中提供的参数字符串"01.12.2012", "03.11.2013", "04.10.2014"转换为Date对象,并在测试执行时传递给测试方法testCalendar的第一个参数
4 JS测试用例编写说明
4.1 JS测试框架工程结构
在Deveco Studio开发工具新建的JavaScript项目里,ohosTest目录负责存放测试代码及相关文件。测试模块所在路径如下图所示,测试相关文件及其功能如下表所示。其中default目录是新建js项目时自动生成的,用户无需修改。test目录下存放各个测试文件,用户可以在此目录下自定义测试用例。
图1 用例路径一览图
表2 测试相关文件说明表
文件名称 |
功能 |
ohosTest |
存放测试相关文件 |
ExampleJsunit.test.js |
测试文件,后缀为xxxx.test.js |
List.test.js |
测试用例加载文件,执行指定测试文件,新增的测试用例文件需要在这里新增引用 |
4.2 用例编写命名建议
测试工程目录及文件夹统一采用小写英文风格命名,不允许出现中文,参考“ohosTest”、“default”。
测试文件以“xx.test.js”,前缀的“XX”名称为英文字母、数字、下划线的组合,以字母开头,遵从大驼峰命名法,例如“ExampleJsunit.test.js”。测试文件在同一测试工程中应保持唯一,避免重复。
注意
不能使用逗号、横线、空格以及\ / : * ? “”< > | ()&等特殊字符。
测试套名与测试用例名同上命令规范,保持名称的唯一性。
4.3 用例编写基础语法
测试用例遵循 ES6 标准,describe 代表一个测试套, it 代表一条用例,describe 支持多层嵌套。
- describe:定义一个测试套,支持两个参数: 测试套名称和测试套函数; describe 支持嵌套, 每个 describe 内均可以定义 beforeAll 、beforeEach 、afterEach 和 afterAll。
- beforeAll:在测试套内定义一个预置条件,在所有测试用例开始前执行且仅执行一次,支持一个参数:预置动作函数。
- beforeEach:在测试套内定义一个单元预置条件,在每条测试用例开始前执行,执行次数与 it 定义的测试用例数一致,支持一个参数:预置动作函数。
- afterEach:在测试套内定义一个单元清理条件,在每条测试用例结束后执行,执行次数与 it 定义的测试用例数一致,支持一个参数:清理动作函数。
- afterAll:在测试套内定义一个清理条件,在所有测试用例结束后执行且仅执行一次,支持一个参数:清理动作函数。
- it:定义一条测试用例,支持三个参数:用例名称,过滤参数和用例函数。
- expect:支持 bool 类型判断等多种断言方法。
- describe('testSuiteName', function () {
- beforeAll(function () {
- console.info('beforeAll called')
- })
- beforeEach(function () {
- console.info('beforeEach called')
- })
- afterEach(function () {
- console.info('afterEach called')
- })
- afterAll(function () {
- console.info('afterAll called')
- })
- it('testSpecName', 0, function () {
- console.info('testSpec')
- })
- })
4.4 用例编写属性标注
测试用例属性标注参见以下 demo。
- it('app_info_test_001', SMALL|LEVEL0|FUNCTION, function () {
- var info = app.getInfo()
- expect(info.versionCode).assertEqual('1')
- console.info('testCase001')
- })
- it('app_info_test_002', MEDIUM|LEVEL2|PERFORMANCE, function () {
- console.info('testCase002')
- })
表3 用例编写属性标注
测试套/测试用例 |
说明 |
“appInfoTest”测试套 |
测试套的预置条件 beforeAll、清理条件 afterAll |
“app_info_test_001”测试用例 |
测试用例的预置条件 beforeEach、清理条件 afterEach。 用例属性标记为—SMALL-小型测试、LEVEL0-0 级测试、FUNCTION-方法类测试。 |
“app_info_test_002”测试用例 |
测试用例的预置条件 beforeEach、清理条件 afterEach。 用例属性标记为—MEDIUM-中型测试、LEVEL1-1 级测试、PERFORMANCE-性能类测试。 |
4.5 同步异步
同步用例写法如下:
- it('app_info_test_001', DEFAULT, function () {
- var info = app.getInfo()
- expect(info.versionCode).assertEqual('1')
- console.info('testCase001')
- })
异步用例写法如下:
1 promise 用法
- describe('testSuitePromise', function () {
- it('testCasePromise', DEFAULT, async function (done) {
- await testPromise().then((value) => {
- console.info(value)
- expect(value).assertEqual(0)
- done()
- }).catch((err) => {
- console.info(err.code)
- expect.assertFail()
- done()
- })
- console.info('end....');
- })
- })
- function testPromise() {
- return new Promise(function(resolve, reject) {
- setTimeout(function () {
- console.info('调用 setTimeout...')
- resolve(0)
- }, 1000)
- })
- }
2 callback 用法
- describe('testSuiteCallback', function () {
- it('testCaseCallback', 0, async function (done) {
- file.writeText({
- uri: 'internal://app/test.txt',
- text: 'abc',
- encoding: 'UTF-8',
- success: () => {
- console.info("成功")
- done()
- },
- fail:function(data, code) {
- console.info('失败code:' + code)
- done()
- }
- })
- })
- })
4.6 数据驱动测试用例
在接口测试中通常会有一些参数种类很多,需要执行多次的接口,如果每多一个参数就多编写一条用例会导致用例冗余,维护也困难,针对这种场景,框架提供的参数配置的方式执行用例,可以根据配置参数来驱动测试用例的执行次数和每一次传入的参数。H-JSUnit测试框架提供数据驱动功能:
- 参数传递 : 为指定测试套、测试用例传递参数
- 压力测试 : 为指定测试套、测试用例的执行次数
数据驱动功能的使用依赖于data.json配置文件,该文件需用户手动在(entry/src/ohosTest/js/test)目录下新建data.json文件,文件内容见下文json配置
json配置
- {
- "suites":[
- {
- "describe":"testSuite01",
- "stress": 4,
- "params": {
- "suiteParams1": "suiteParams001",
- "suiteParams2": "suiteParams002"
- },
- "items":[{
- "it":"testSpec01",
- "stress": 1,
- "params":[{
- "name":"tom",
- "value": 5
- },{
- "name":"jerry",
- "value": 4
- }]
- },{
- "it":"testSpec02",
- "stress": 2,
- "params":[{
- "name":"Array"
- },{
- "name":"Array01"
- }]
- }]
- }
- ]
- }
以上配置表示"testSuite01"测试套被执行4次,
- "describe":"testSuite01",
- "stress": 4,
该测试套可获取的参数为—参数名为"suiteParams1",参数值为"suiteParams001"和参数名为"suiteParams2",参数值为"suiteParams002"。
- "suiteParams1": "suiteParams001",
- "suiteParams2": "suiteParams002"
该测试套下的"testSpec01"测试用例被执行1次,
- "it":"testSpec01",
- "stress": 1,
该测试用例可获取到的参数为—参数名为"tom",参数值为"5"和参数名为"jerry",参数值为"4"。
- "params":[{
- "name":"tom",
- "value": 5
- },{
- "name":"jerry",
- "value": 4
- }]
具体文件配置参数说明:
表4 基本功能表
配置项名称 |
功能 |
"suite" |
测试套配置 |
"items" |
测试用例配置 |
"describe" |
测试套名称 |
"it" |
测试用例名称 |
"params" |
测试套 / 测试用例可传入使用的参数 |
"stress" |
测试套 / 测试用例的指定执行次数 |
配置json文件后,在index.js文件中添加相关依赖与服务(entry/src/ohosTest/pages/index/index.js)
导入数据驱动依赖
- import { DataDriver } from '@ohos/hypium'
导入数据驱动要的配置参数
- import data from './test/data.json'
实例化数据驱动,并添加对应的服务
- const dataDriver = new DataDriver({data:data})
- core.addService('dataDriver',dataDriver)
添加相关测试用例
- require('./test/testDataDriver.test.js')
在编写同步测试用例时,在回调方法中加入data变量,且作为第一个参数。在用例方法中便可使用在data.json中配置的参数。
注意
数据驱动参数必须为data变量,且必须放在一个参数位置。
5 测试用例执行方式
- 连接测试设备
通过hdc将测试设备正常连接到主机上。您可以通过如下命令行来查看hdc当前连接的所有设备:
- hdc list targets
- 执行测试用例
在DevEco Studio中,在DemoTest类名上单击右键,然后在弹出菜单上点击Run DemoTest按钮,启动app测试用例执行。
图2 测试用例的执行方式
- 查看测试结果
测试用例执行完成后,测试报告列表将出现在Deveco Studio左下角的run面板上。如果存在失败测试用例,您可以通过点击测试用例名称来查看具体的失败信息。