HarmonyOS Next 单元测试框架API总结

自动化测试框架代码部件仓arkXtest,包含单元测试框架(JsUnit)和Ui测试框架(UiTest)。单元测试框架(JsUnit)提供单元测试用例执行能力,提供用例编写基础接口,生成对应报告,用于测试系统或应用接口。Ui测试框架(UiTest)通过简洁易用的API提供查找和操作界面控件能力,支持用户开发基于界面操作的自动化测试脚本。

本文介绍HarmonyOS Next单元测试中用到的API。单元测试功能特性包含下面五点:

  1. 基本流程:支持编写及异步执行基础用例。
  2. 断言库:判断用例实际期望值与预期值是否相符。
  3. Mock能力:支持函数级mock能力,对定义的函数进行mock后修改函数的行为,使其返回指定的值或者执行某种动作。
  4. 数据驱动:提供数据驱动能力,支持复用同一个测试脚本,使用不同输入数据驱动执行。
  5. 专项能力:支持测试套与用例筛选、随机执行、压力测试、超时设置、遇错即停模式,跳过,支持测试套嵌套等。

基本流程

测试用例采用了测试领域通用方法,describe代表一个测试套, it代表一条用例。

API功能说明
describe定义一个测试套,支持两个参数:测试套名称和测试套函数。其中测试套函数不能是异步函数
beforeAll在测试套内定义一个预置条件,在所有测试用例开始前执行且仅执行一次,支持一个参数:预置动作函数。
beforeEach在测试套内定义一个单元预置条件,在每条测试用例开始前执行,执行次数与it定义的测试用例数一致,支持一个参数:预置动作函数。
afterEach在测试套内定义一个单元清理条件,在每条测试用例结束后执行,执行次数与it定义的测试用例数一致,支持一个参数:清理动作函数。
afterAll在测试套内定义一个清理条件,在所有测试用例结束后执行且仅执行一次,支持一个参数:清理动作函数。
beforeItSpecified@since1.0.15在测试套内定义一个单元预置条件,仅在指定测试用例开始前执行,支持两个参数:单个用例名称或用例名称数组、预置动作函数。
afterItSpecified@since1.0.15在测试套内定义一个单元清理条件,仅在指定测试用例结束后执行,支持两个参数:单个用例名称或用例名称数组、清理动作函数
it定义一条测试用例,支持三个参数:用例名称,过滤参数和用例函数。
expect支持bool类型判断等多种断言方法。
xdescribe@since1.0.17定义一个跳过的测试套,支持两个参数:测试套名称和测试套函数。
xit@since1.0.17定义一条跳过的测试用例,支持三个参数:用例名称,过滤参数和用例函数。

beforeItSpecified, afterItSpecified 示例代码:

import { describe, it, expect, beforeItSpecified, afterItSpecified } from '@ohos/hypium';
export default function beforeItSpecifiedTest() {
  describe('beforeItSpecifiedTest', () => {
    beforeItSpecified(['String_assertContain_success'], () => {
      const num:number = 1;
      expect(num).assertEqual(1);
    })
    afterItSpecified(['String_assertContain_success'], async (done: Function) => {
      const str:string = 'abc';
      setTimeout(()=>{
        try {
          expect(str).assertContain('b');
        } catch (error) {
          console.error(`error message ${JSON.stringify(error)}`);
        }
        done();
      }, 1000)
    })
    it('String_assertContain_success', 0, () => {
      let a: string = 'abc';
      let b: string = 'b';
      expect(a).assertContain(b);
      expect(a).assertEqual(a);
    })
  })
}

断言库

功能列表如下:

API功能说明
assertClose检验actualvalue和expectvalue(0)的接近程度是否是expectValue(1)。
assertContain检验actualvalue中是否包含expectvalue。
assertEqual检验actualvalue是否等于expectvalue[0]。
assertFail抛出一个错误。
assertFalse检验actualvalue是否是false。
assertTrue检验actualvalue是否是true。
assertInstanceOf检验actualvalue是否是expectvalue类型,支持基础类型。
assertLarger检验actualvalue是否大于expectvalue。
assertLess检验actualvalue是否小于expectvalue。
assertNull检验actualvalue是否是null。
assertThrowError检验actualvalue抛出Error内容是否是expectValue。
assertUndefined检验actualvalue是否是undefined。
assertNaN@since1.0.4 检验actualvalue是否是一个NAN
assertNegUnlimited@since1.0.4 检验actualvalue是否等于Number.NEGATIVE_INFINITY
assertPosUnlimited@since1.0.4 检验actualvalue是否等于Number.POSITIVE_INFINITY
assertDeepEquals@since1.0.4 检验actualvalue和expectvalue是否完全相等
assertPromiseIsPending@since1.0.4 判断promise是否处于Pending状态。
assertPromiseIsRejected@since1.0.4 判断promise是否处于Rejected状态。
assertPromiseIsRejectedWith@since1.0.4 判断promise是否处于Rejected状态,并且比较执行的结果值。
assertPromiseIsRejectedWithError@since1.0.4 判断promise是否处于Rejected状态并有异常,同时比较异常的类型和message值。
assertPromiseIsResolved@since1.0.4 判断promise是否处于Resolved状态。
assertPromiseIsResolvedWith@since1.0.4 判断promise是否处于Resolved状态,并且比较执行的结果值。
not@since1.0.4 断言取反,支持上面所有的断言功能
message@since1.0.17自定义断言异常信息

expect断言示例代码:

import { describe, it, expect } from '@ohos/hypium';

export default function expectTest() {
  describe('expectTest', () => {
    it('assertBeClose_success', 0, () => {
      let a: number = 100;
      let b: number = 0.1;
      expect(a).assertClose(99, b);
    })
    it('assertInstanceOf_success', 0, () => {
      let a: string = 'strTest';
      expect(a).assertInstanceOf('String');
    })
    it('assertNaN_success', 0, () => {
      expect(Number.NaN).assertNaN(); // true
    })
    it('assertNegUnlimited_success', 0, () => {
      expect(Number.NEGATIVE_INFINITY).assertNegUnlimited(); // true
    })
    it('assertPosUnlimited_success', 0, () => {
      expect(Number.POSITIVE_INFINITY).assertPosUnlimited(); // true
    })
    it('not_number_true', 0, () => {
      expect(1).not().assertLargerOrEqual(2);
    })
    it('not_number_true_1', 0, () => {
      expect(3).not().assertLessOrEqual(2);
    })
    it('not_NaN_true', 0, () => {
      expect(3).not().assertNaN();
    })
    it('not_contain_true', 0, () => {
      let a: string = "abc";
      let b: string = "cdf";
      expect(a).not().assertContain(b);
    })
    it('not_large_true', 0, () => {
      expect(3).not().assertLarger(4);
    })
    it('not_less_true', 0, () => {
      expect(3).not().assertLess(2);
    })
    it('not_undefined_true', 0, () => {
      expect(3).not().assertUndefined();
    })
    it('deepEquals_null_true', 0, () => {
      // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
      expect(null).assertDeepEquals(null);
    })
    it('deepEquals_array_not_have_true', 0, () => {
      // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
      const a: Array<number> = [];
      const b: Array<number> = [];
      expect(a).assertDeepEquals(b);
    })
    it('deepEquals_map_equal_length_success', 0, () => {
      // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
      const a: Map<number, number> = new Map();
      const b: Map<number, number> = new Map();
      a.set(1, 100);
      a.set(2, 200);
      b.set(1, 100);
      b.set(2, 200);
      expect(a).assertDeepEquals(b);
    })
    it("deepEquals_obj_success_1", 0, () => {
      const a: SampleTest = {x: 1};
      const b: SampleTest = {x: 1};
      expect(a).assertDeepEquals(b);
    })
    it("deepEquals_regExp_success_0", 0, () => {
      const a: RegExp = new RegExp("/test/");
      const b: RegExp = new RegExp("/test/");
      expect(a).assertDeepEquals(b);
    })
    it('test_isPending_pass_1', 0, () => {
      let p: Promise<void> = new Promise<void>(() => {});
      expect(p).assertPromiseIsPending();
    })
    it('test_isRejected_pass_1', 0, () => {
      let info: PromiseInfo = {res: "no"};
      let p: Promise<PromiseInfo> = Promise.reject(info);
      expect(p).assertPromiseIsRejected();
    })
    it('test_isRejectedWith_pass_1', 0, () => {
      let info: PromiseInfo = {res: "reject value"};
      let p: Promise<PromiseInfo> = Promise.reject(info);
      expect(p).assertPromiseIsRejectedWith(info);
    })
    it('test_isRejectedWithError_pass_1', 0, () => {
      let p1: Promise<TypeError> = Promise.reject(new TypeError('number'));
      expect(p1).assertPromiseIsRejectedWithError(TypeError);
    })
    it('test_isResolved_pass_1', 0, () => {
      let info: PromiseInfo = {res: "result value"};
      let p: Promise<PromiseInfo> = Promise.resolve(info);
      expect(p).assertPromiseIsResolved();
    })
    it('test_isResolvedTo_pass_1', 0, () => {
      let info: PromiseInfo = {res: "result value"};
      let p: Promise<PromiseInfo> = Promise.resolve(info);
      expect(p).assertPromiseIsResolvedWith(info);
    })
    it("test_message", 0, () => {
      expect(1).message('1 is not equal 2!').assertEqual(2); // fail
    })
  })
}

interface SampleTest {
  x: number;
}

interface PromiseInfo {
  res: string;
}

Mock能力

单元测试框架Mock能力从npm包1.0.1版本开始支持,需修改源码工程中package.info中配置依赖npm包版本号后使用。

接口列表如下:

API功能说明
mockFunc(obj: object, f:function())mock某个类的对象obj的函数f,那么需要传两个参数:obj和f,支持使用异步函数(说明:对mock而言原函数实现是同步或异步没太多区别,因为mock并不关注原函数的实现)。
when(mockedfunc:function)对传入后方法做检查,检查是否被mock并标记过,返回的是一个方法声明。
afterReturn(x:value)设定预期返回一个自定义的值value,比如某个字符串或者一个promise。
afterReturnNothing()设定预期没有返回值,即 undefined。
afterAction(x:action)设定预期返回一个函数执行的操作。
afterThrow(x:msg)设定预期抛出异常,并指定异常msg。
clear(obj: object)用例执行完毕后,进行数据mocker对象的还原处理(还原之后对象恢复被mock之前的功能)。
any设定用户传任何类型参数(undefined和null除外),执行的结果都是预期的值,使用ArgumentMatchers.any方式调用。
anyString设定用户传任何字符串参数,执行的结果都是预期的值,使用ArgumentMatchers.anyString方式调用。
anyBoolean设定用户传任何boolean类型参数,执行的结果都是预期的值,使用ArgumentMatchers.anyBoolean方式调用。
anyFunction设定用户传任何function类型参数,执行的结果都是预期的值,使用ArgumentMatchers.anyFunction方式调用。
anyNumber设定用户传任何数字类型参数,执行的结果都是预期的值,使用ArgumentMatchers.anyNumber方式调用。
anyObj设定用户传任何对象类型参数,执行的结果都是预期的值,使用ArgumentMatchers.anyObj方式调用。
matchRegexs(Regex)设定用户传任何正则表达式类型参数Regex,执行的结果都是预期的值,使用ArgumentMatchers.matchRegexs(Regex)方式调用。
verify(methodName, argsArray)验证methodName(函数名字符串)所对应的函数和其参数列表argsArray的执行行为是否符合预期,返回一个VerificationMode:一个提供验证模式的类,它有times(count)、once()、atLeast(x)、atMost(x)、never()等函数可供选择。
times(count)验证行为调用过count次。
once()验证行为调用过一次。
atLeast(count)验证行为至少调用过count次。
atMost(count)验证行为至多调用过count次。
never验证行为从未发生过。
ignoreMock(obj, method)使用ignoreMock可以还原obj对象中被mock后的函数,对被mock后的函数有效。
clearAll()用例执行完毕后,进行数据和内存清理,不会还原obj对象中被mock后的函数。

用户可以通过以下方式进行引入mock模块进行测试用例编写:

//afterReturn 的使用
import { describe, expect, it, MockKit, when } from '@ohos/hypium';

class ClassName {
  constructor() {
  }

  method_1(arg: string) {
    return '888888';
  }

  method_2(arg: string) {
    return '999999';
  }
}
export default function afterReturnTest() {
  describe('afterReturnTest', () => {
    it('afterReturnTest', 0, () => {
      console.info("it1 begin");
      // 1.创建一个mock能力的对象MockKit
      let mocker: MockKit = new MockKit();
      // 2.定类ClassName,里面两个函数,然后创建一个对象claser
      let claser: ClassName = new ClassName();
      // 3.进行mock操作,比如需要对ClassName类的method_1函数进行mock
      let mockfunc: Function = mocker.mockFunc(claser, claser.method_1);
      // 4.期望claser.method_1函数被mock后, 以'test'为入参时调用函数返回结果'1'
      when(mockfunc)('test').afterReturn('1');
      // 5.对mock后的函数进行断言,看是否符合预期
      // 执行成功案例,参数为'test'
      expect(claser.method_1('test')).assertEqual('1'); // 执行通过
    })
  })
}
//afterReturnNothing 的使用
import { describe, expect, it, MockKit, when } from '@ohos/hypium';

class ClassName {
  constructor() {
  }

  method_1(arg: string) {
    return '888888';
  }

  method_2(arg: string) {
    return '999999';
  }
}
export default function  afterReturnNothingTest() {
  describe('afterReturnNothingTest', () => {
    it('testMockfunc', 0, () => {
      console.info("it1 begin");
      // 1.创建一个mock能力的对象MockKit
      let mocker: MockKit = new MockKit();
      // 2.定类ClassName,里面两个函数,然后创建一个对象claser
      let claser: ClassName = new ClassName();
      // 3.进行mock操作,比如需要对ClassName类的method_1函数进行mock
      let mockfunc: Function = mocker.mockFunc(claser, claser.method_1);
      // 4.期望claser.method_1函数被mock后, 以'test'为入参时调用函数返回结果undefined
      when(mockfunc)('test').afterReturnNothing();
      // 5.对mock后的函数进行断言,看是否符合预期,注意选择跟第4步中对应的断言方法
      // 执行成功案例,参数为'test',这时候执行原对象claser.method_1的方法,会发生变化
      // 这时候执行的claser.method_1不会再返回'888888',而是设定的afterReturnNothing()生效// 不返回任何值;
      expect(claser.method_1('test')).assertUndefined(); // 执行通过
    })
  })
}

数据驱动

数据驱动可以根据配置参数来驱动测试用例的执行次数和每一次传入的参数,使用时依赖data.json配置文件,文件内容如下:

{
    "suites": [{
        "describe": ["actsAbilityTest"],
        "stress": 2,
        "params": {
            "suiteParams1": "suiteParams001",
            "suiteParams2": "suiteParams002"
        },
        "items": [{
            "it": "testDataDriverAsync",
            "stress": 2,
            "params": [{
                "name": "tom",
                "value": 5
            }, {
                "name": "jerry",
                "value": 4
            }]
        }, {
            "it": "testDataDriver",
            "stress": 3
        }]
    }]
}

配置参数说明:

配置项名称功能必填
"suite"测试套配置 。
"items"测试用例配置 。
"describe"测试套名称 。
"it"测试用例名称 。
"params"测试套 / 测试用例 可传入使用的参数 。
"stress"测试套 / 测试用例 指定执行次数 。

专项能力

能力需通过在cmd窗口中输入aa test命令执行触发,并通过设置执行参数触发不同功能。另外,测试应用模型与编译方式不同,对应的aa test命令也不同。

  • 筛选能力

    • 按测试用例属性筛选
    • 按测试套/测试用例名称筛选

总结

本文介绍了HarmonyOS Next 单元测试套件相关API和能力。


凡事提前
1.3k 声望352 粉丝

互联网从业者,做过Android、UI自动化、UI自动化框架、测试开发,目前在研究HarmonyOS 自动化测试相关技术。