tdml
v0.0.4
Published
测试描述标记语言
Downloads
2
Readme
TD(Test Description)ML, 测试描述标记语言,是一套用于高效简洁描述测试代码的规范语言。
- 为什么要使用TDML
单元测试是开发中重要的一部分工作之一,但是常常,业务时间紧张,除了要加用例代码,还要维护旧的用例,每个人用例写法多少也有不一致,同伴的用例代码也往往会含有特定逻辑,而且代码量大修改起来也难定位,前前后后也耗不少时间。不得不说,用例虽不难,但也会让我们投入不少精力。
由于用例大部分都是验证单元模块IO验证验证,核心代码比较固定和机械化,那么可以根据源码进行ast分析,解析出各个模块的io类型,并重新构造,直接生成用例代码,我们只需要指令式管理IO样本数据即可。为此,这里定义了一套简洁的描述语法规则TDML(测试描述标记语法,Test Description ML)。TDML的基础理念是:同样的测试用例的代码只写需要工具一次,其他的交给TDML
而至于底层使用mocha还是jest生成用例,可通过tdml.config.js配置。开发者只需要在注释中写指令语法就可以了。
- TDML引入的优势
所有语法必须写在函数前的块注释(/**/)中。然后自动生成对应的完整测试用例代码。
优势:
1,不用手写用例代码,不用维护
2,从注释直观开发ut测试IO,面向指令,所有用例和模块IO一目了然。边界IO也更加直观
3,统一用例代码,使用核心语法,保持一致性 (其实完全不用关心)
4,约束源码实现,约束一个函数只做一件IO事情,手动编写的用例过于灵活,后面其它人维护成本较高
5,大大降低TDD成本
6,注释文档、逻辑、用例同步更新,避免同步的成本
7,统一IO数据样本文件管理,样本数据更易维护
8,提高效率的同时对源码的组织结构形成反向约束 (需要按照规范的方式写,才能自动生成用例)
9,覆盖率低?不存在的
劣势: 增加注释量。
- 安装使用
npm i tdml -g
tdml c [path] # 例如 tdml c ./src/code.js
也可以安装在项目中书写tdml.config.js进行配置,并安装插件自动触发。
- TDML常用语法
为了不对源码进行入侵,也不额外维护文件,TDML使用注释语法规范。必须在块代码注释中声明,否则不生效,但也不产生任何影响。
1、判断相等
fn(...Params) => (returnValue),例如
/* name function B
* this is test
* B(1, 3) => (3)
*/
export function B (a: number, b: number): number {
return a * b;
}
自动编译后
test("B module", () => {
expect(B(1, 3)).toBe(3);
});
2、判断对象深度相等
fn(...Params) ==> (returnValue),例如
/* name B
* this is test
* B(1, 3) ==> (3)
*/
export function B (a: number, b: number): number {
return a * b;
}
自动编译后
test("B module", () => {
expect(B(1, 3)).toEqual(3);
});
3、判断对象不相等
fn(...Params) !=> (returnValue),例如
/* name B
* this is test
* B(1, 3) !=> (4)
*/
export function B (a: number, b: number): number {
return a * b;
}
自动编译后
test("B module", () => {
expect(B(1, 3)).not.toBe(4);
});
4、判断对象深度不相等
fn(...Params) !==> (returnValue),例如
/* name B
* this is test
* B(1, 3) !==> (4)
*/
export function B (a: number, b: number): number {
return a * b;
}
自动编译后
test("B module", () => {
expect(B(1, 3)).not.toEqual(4);
});
5,路径常量数据文件
使用冒号分割,冒号前为读取mock数据的属性key,后面为相对路径,带不带引号都可以,重复路径最终会自动合并
(:path, data.dataKey: path, ...params) => (data.dataKey: path)
/**
* name B
* A(a: './data.js', 3) => (3)
* A(b:./data.js, d:"./data1.js") => (1)
* A(a:./data.js, 1) => (a.b:./data1.js)
*/
export function B (a: number, b: number): number {
return a * b;
}
其中data.js和data1.js文件内容为(支持export和module.exports导出)
// data.js
module.exports = {
a: 1,
b: 1
};
// data1.js
export default {
c: 1,
d: 1
}
自动编译后
const data1 = {
a: 1,
b: 1
};
const data2 = {
c: 1,
d: 1
}
test("B module", () => {
expect(B(data1.a, 3)).toBe(3);
export(B(data1.b, data2.d)).toBe(1);
export(B(data1.a, 1)).toBe(data2.d);
});
6, 属性判断
fn(...Params).property => (lengthValue),例如
/* name B
* this is test
* B(1, 3).length => (1)
* B(1, 3)['length'] => (1)
* B(1, 3)['length']['xxx'] !=> (1)
*/
export function B (a: number, b: number): number {
return a * b;
}
自动编译后
test("B module", () => {
expect(B(1, 3).length).toBe(1);
expect(B(1, 3)['length']).toBe(1);
expect(B(1, 3)['length']['xxx']).not.toBe(1);
});
7,异步链式判断
fn(...params) -> (module1) ==> (returnValue)
异步执行fn,然后判断module1的值是否和returnValue相符。
/**
* 拉取按天时间段报表,内部操作为拉取一个接口,然后把接口数据dispatch到store上
*
* getOverviewChart({}, {}) -> (getState().apiData) ==> ({chart: {a:1}})
*/
export const getOverviewChart = (params = {}, options = {}) => (dispatch, getState) => {}
编译后,这里需要配置tdml.config.js来决定是否使用dispatch
dispatch(getOverviewChart({}, {})).then(() => {
expect(getState().apiData).toEqual({
chart: {
a: 1
}
});
done();
}, () => {});
8,链式触发调用和断言
fn(...params) -> number1(module1:path) -> ... -> number2(module2:path) => (returnValue)
fn调用时调用module1次数为number1,调用module2次数为number1,然后返回断言值为return
稍微完整的examples
/**
* add(6, 3) !=> (4)
* add(2, 3) ==> (6)
* add(2, "3") !=> ("13")
* add("abc", "df") => ("abcdf")
* add(path.b: './data/data', "df") => ("abcdf")
* add(:./data/data)['length']['xx'] => (234)
* add(b.c:'./data/data1').length => (123)
* add(b.c:./data/data1).length => (b.c: ./data/data3.js)
*/
export function add (a: number, b: number): number {
return a + b;
}
编译后
const data3 = {
"b": {
"c": 4
}
};
const data2 = {
"data": [1, "kesd", 3, "oooo", {
"a": 1
}, {
"a": 1,
"b": 2,
"c": {
"a": 1,
"b": 2,
"c": [{
"a": 1,
"b": 2
}]
}
}],
"path": "mmmmmmmmmmmmm"
};
const data1 = {
"data": [1, "kesd", 3, "oooo", {
"a": 1
}, {
"a": 1,
"b": 2,
"c": {
"a": 1,
"b": 2,
"c": [{
"a": 1,
"b": 2
}]
}
}],
"path": "mmmmmmmmmmmmm"
};
test("add module", () => {
expect(add(6, 3)).not.toBe(4);
expect(add(2, 3)).toEqual(6);
expect(add(2, "3")).not.toBe("13");
expect(add("abc", "df")).toBe("abcdf");
expect(add(data1.path.b, "df")).toBe("abcdf");
expect(add(data1)['length']['xx']).toBe(234);
expect(add(data2.b.c).length).toBe(123);
expect(add(data2.b.c).length).toBe(data3.b.c);
});
TODO:
1, 文件依赖自动解析
2, react集成配置解析 tdml.config.js
3,redux 集成配置解析 tdml.config.js
4,自动插件化 tdml.config.js
5,数据转换类模块用例分析(测试验证函数IO是否符合预期) 已完成
6,请求数据并dispatch数据类型模块分析(Mock接口数据,验证dispatch后对应节点数据是否符合预期)
7,ui事件触发类型函数分析(事件模拟,判断是否调用对应的其它模块以及调用的次数)
8,ui渲染类节点特性判断(渲染,然后节点查询对应的节点)
FUTURE TODO:
//