1、在上文中采用了数据、case、测试代码分离的方式设计,但是在实际操作中还有点问题,并且还有很大的优化空间,首先就是参数引用的问题,在上文中,没有对提取的参数作别名处理,所以提取到了重名的参数会覆盖掉之前的,现修正如下
config/case/task_case.js
const pathList = require('../../config/global/path')
const data = require('../data/task')
/**
* @description isQuote:true,为true时,说明引用了提取的字段,引用字段要和提取字段名称一致,且引用字段名称前面需要加$符号
* @description isExtract:1 提取reqest,2提取response,3 提取request和response
* @description as:别名
*/
exports.task = [
{
isExtract: 2,
name: 'keyword',
config: {
url: pathList.keywordPathList.save,
method: 'post',
headers: {},
data: data.keyword
},
assert: [{code: '0000'}, {msg: '执行成功'}],
res_extract: ['data'],
as: {
data: 'id'
}
},
{
isQuote: true,
isExtract: 3,
name: 'save',
config: {
url: pathList.taskList.save,
method: 'post',
headers: {},
data: data.save
},
assert: [{code: '0000'}, {msg: '一切ok'}],
res_extract: ['data'],
req_extract: ['spiderName']
},
{
isQuote: true,
name: 'edit',
config: {
url: pathList.taskList.save + '/' + '{data}',
method: 'put',
headers: {},
data: data.edit
},
assert: [{code: '0000'}, {msg: '一切ok'}, {data: '修改任务完成'}]
},
{
isQuote: true,
name: 'get',
config: {
url: pathList.taskList.save,
method: 'get',
headers: {},
params: {
spiderName: '{spiderName}',
page: 1,
pageSize: 10
},
},
assert: [{msg: '一切ok'}, {totalCount: 1}]
},
{
isQuote: true,
name: 'reset',
config: {
url: pathList.taskList.reset + '/' + '{data}',
method: 'post',
headers: {},
},
assert: [{code: '0000'}, {msg: '一切ok'}]
},
{
isQuote: true,
name: 'run',
config: {
url: pathList.taskList.run + '/' + '{data}',
method: 'get',
headers: {},
params: {
taskName: '{spiderName}'
}
},
assert: [{code: '0000'}, {msg: '一切ok'}]
}
]
看case中有个as字段,是为了给提取重名的字段时,避免被替换掉
2、完成这个功能,我重新对方法进行了修改
utils/regexp.js
const type_judge = require('../utils/type_judge');
const assert = require('assert')
/**
* @description 根据正则表达式提取字符串中的数据
* @param {string} str 要提取的字符串,case中需要提取的值
* @param {object|string} obj 待提取的对象或者字符串,response或者request
* @return {array} 被提取的值会以数组形式返回
*/
function extract(str, obj) {
let string_pattern = `(?<="${str}":").+?(?=")`;
let number_pattern = `(?<="${str}":)\\d+`;
let arr;
if (type_judge(obj) === 'String') {
arr = obj.match(new RegExp(string_pattern, 'gm')) || null
if (arr === null) {
arr = obj.match(new RegExp(number_pattern, 'gm')) || null
}
} else if (type_judge(obj) === 'Object') {
arr = JSON.stringify(obj).match(new RegExp(string_pattern, 'gm')) || null;
if (arr === null) {
arr = JSON.stringify(obj).match(new RegExp(number_pattern, 'gm')) || null;
}
} else {
assert.fail('type of parms(obj) is not correct')
}
return arr
}
/**
* @description 根据正则表达式保存提取的数据
* @param {array}array case中的需要提取值的数组字段,如
* @param {object}obj response或者request
* @param {*}args 可选参数,用来传递别名
* @return {object}返回所有从对象中提取的数据
*/
function saveExtract(array, obj, ...args) {
let field = {}
if (array !== [] && array !== undefined) {
for (let i in array) {
let arr = extract(array[i], obj);
if (arr !== null) {
for (let j in arr) {
if (args.length !== 0 && args[0] !== undefined) {
for (let n in args[0]) {
if (n === array[i]) {
field[args[0][n]] = arr[j]
}
}
} else
field[array[i]] = arr[j]
}
} else
break
}
}
return field
}
/**
*
* @param {object}obja case中需要提取的数组
* @param {object}objb request,即response.config.data
* @param {object}objc response
* @param {*}args 可选参数,传递case中的别名
* @return {object}根据isExtract的值,提取对应的对象的值
*/
function excute(obja, objb, objc, ...args) {
let temp = {};
if (obja['isExtract'] && args.length !== 0 && args[0] !== undefined) {
switch (obja['isExtract']) {
case 1:
temp = saveExtract(obja['req_extract'], objb, args[0]);
break;
case 2:
temp = saveExtract(obja['res_extract'], objc, args[0]);
break;
case 3:
temp = Object.assign(saveExtract(obja['req_extract'], objb, args[0]), saveExtract(obja['res_extract'], objc, args[0]));
break;
default:
break
}
} else if (obja['isExtract'] && (args.length === 0 || args[0] === undefined)) {
switch (obja['isExtract']) {
case 1:
temp = saveExtract(obja['req_extract'], objb);
break;
case 2:
temp = saveExtract(obja['res_extract'], objc);
break;
case 3:
temp = Object.assign(saveExtract(obja['req_extract'], objb), saveExtract(obja['res_extract'], objc));
break;
default:
break
}
}
return temp
}
module.exports = excute
utils/replace_config.js
/**
* @description 正则匹配并替换引用
* @param {object}obj
* @param {object}field
* @return {object}返回替换后的对象||原对象
*/
function rep(obj, field) {
if (obj['isQuote']) {
let temp = JSON.stringify(obj)
for (let i in Object.keys(field)) {
let reg = new RegExp(`\{(${Object.keys(field)[i]})\}`, 'gm')
temp = temp.replace(reg, field[Object.keys(field)[i]])
}
let result = JSON.parse(temp)
// result['config']['url'] = encodeURI(result['config']['url'])
return result
} else {
return obj
}
}
module.exports = rep
3、如上就是新修正的方法,但是这种并不是最终的方式,还可以进一步优化,使用动态加载的策略来设计,这边只简单了写个示例
utils/read_dir.js
const fs = require('fs');
function readdirSync(path){
return fs.readdirSync(path)
}
module.exports = readdirSync
case/test/test_case.js
module.exports = [
{name: 'test_1'},
{name: 'test_2'},
{name: 'test_3'},
]
case/test/test1_case.js
module.exports = [
{name: 'test1_1'},
{name: 'test1_2'},
{name: 'test1_3'},
]
nodejs导出使用exports导出时,是导出一个对象,使用module.exports导出时,会根据赋值的类型进行导出,切记
api/list/dynamic_test.js
const read = require('../../utils/read_dir')
const path = require('path')
const assert = require('assert')
let list = read(path.resolve('src/config/test'))
for(let i in list){
let config = require(`../../config/test/${list[i]}`)
describe(`${list[i]}`,function (){
for (let j in config)
it(`${config[j].name}`,function (){
assert.ok('success')
})
})
}
这样设计的话,那么无论是修改接口,还是新增功能,用户只用直接在case目录下新建一个case,然后按照case格式去添加参数即可,不需要在动任何的逻辑代码,这种设计的方式,个人认为会更合理