HarmonyOS Next 單元測試框架API總結
自動化測試框架代碼部件倉arkXtest,包含單元測試框架(JsUnit)和Ui測試框架(UiTest)。單元測試框架(JsUnit)提供單元測試用例執行能力,提供用例編寫基礎接口,生成對應報告,用於測試系統或應用接口。Ui測試框架(UiTest)通過簡潔易用的API提供查找和操作界面控件能力,支持用户開發基於界面操作的自動化測試腳本。
本文介紹HarmonyOS Next單元測試中用到的API。單元測試功能特性包含下面五點:
- 基本流程:支持編寫及異步執行基礎用例。
- 斷言庫:判斷用例實際期望值與預期值是否相符。
- Mock能力:支持函數級mock能力,對定義的函數進行mock後修改函數的行為,使其返回指定的值或者執行某種動作。
- 數據驅動:提供數據驅動能力,支持複用同一個測試腳本,使用不同輸入數據驅動執行。
- 專項能力:支持測試套與用例篩選、隨機執行、壓力測試、超時設置、遇錯即停模式,跳過,支持測試套嵌套等。
基本流程
測試用例採用了測試領域通用方法,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和能力。