背景
近期我投入了一个利用Node.js实现的自动化项目,以应对各类自动化任务。或许你可以将我的描述想象成E2E(端到端)自动化测试工作。换言之,我需要通过自动化测试来验证不同模块的功能。鉴于要测试的模块众多,如果每次都进行全量回归测试,时间将会相当长。因此,我们的需求是实现一种配置,能够自由调整每次自动化测试执行的模块,以实现功能的高度灵活性与高效性。为了让你更好地理解,我将以一个场景来具体说明我的想法。
功能模块描述
假设我们的功能模块有这些:登录、2FA验证、首页功能测试以及发布任务功能测试等等。我们需要确保这些功能模块能够随时灵活增加,而不必全部进行回归测试。
项目设计
- 任务注册与编号映射:为了有效管理各个任务,我们引入了任务 Map 查找表。每个任务都会被分配一个唯一的任务编号,作为这个查找表的关键。这样的设计使得在任务调度时,我们能够快速地根据任务编号找到对应的任务。
TS
/**
* 任务字典表
*/
export enum EnumTask{
/**
* 登录任务
*/
LoginTask = 10000,
/**
* 2FA任务
*/
TwoFATask = 10001,
/**
* 首页任务
*/
HomeTask = 10002,
... 不展开更多
}
/**
* 任务工厂类,用于根据任务编号生成任务实例
*/
export default class TaskFactory implements ITaskFactory {
taskMap: { [key in EnumTask]: new() => ITask }
constructor () {
this.taskMap = Object.create(null)
}
public async getTaskByCode (code: EnumTask): Promise<ITask | undefined> {
const TaskClass = this.taskMap[code]
if (TaskClass) {
return new TaskClass()
}
return undefined
}
public async setTask (code: EnumTask) => ITask): Promise<boolean> {
this.taskMap[code] = task
return true
}
}
- 任务调度和执行:我们需要建立一个高效的任务调度机制,它能够根据预先设定的任务指令,动态地实例化特定的任务类,并执行相应的操作。这个调度器会根据任务编号从任务 Map 中查找相应的任务类,然后触发其 runTask 方法,以完成任务的执行。
TS
// 具体的任务调度不展开讲,这里只用一个任务队列来演示
import TaskFactory from '@src/tasks/factory'
import TwoFATask from '@src/tasks/2fa'
import LoginTask from '@src/tasks/login'
import HomeTask from '@src/tasks/home'
const taskFactory = new TaskFactory()
/**
* 注册任务
*/
taskFactor.setTask(EnumTask.HomeTask, HomeTask)
taskFactor.setTask(EnumTask.LoginTask, LoginTask)
taskFactor.setTask(EnumTask.TwoFATask, TwoFATask)
/**
* 运行任务
*/
public async run () {
// 添加其它功能去影响 commands 里的值
const commands = [EnumTask.HomeTask, EnumTask.LoginTask, EnumTask.TwoFATask]
for (let i = 0; i < commands.length; i++) {
const task = await taskFactory.getTaskByCode(commands[i])
await task.runTask()
}
}
- 抽象通用任务类"Task":我们将各个模块都抽象为一个通用的任务类"Task"。为了保证任务的一致性,我们引入了一个接口,规定每个任务类都必须实现名为"runTask"的方法。这个设计模式让任务的处理方式更为标准化,使得无论任务多么复杂,都能在执行时遵循同一套规范。下面是对这个设计的UML图解释:
我们首先定义了一个ITask接口,然后要求所有的类都需要遵循ITask接口的约束。每个类代表一个功能模块,其中所有要执行的方法都在runTask方法中等待任务调度中心的调用。代码的实现部分如下:
TS
export default class LoginTask implements ITask {
...
// 功能实现部分
public async runTask () {
try {
...
} catch (err:any) {
// 处理异常流
await this.errorHandler(err)
} finally {
// 任务执行完毕后的处理
await this.finallyHandler()
}
// 约定所有任务执行返回的数据结构,好处不展开讲
return this.taskRunOutcome.getData()
}
}
通过上述步骤,如果项目中需要添加新的任务,只需要根据ITask接口的约束,添加新的任务类并实现对应的方法。
结语
在这个项目中,我们将Node.js的自动化能力发挥到了极致。通过灵活的配置和高效的任务调度,我们实现了功能模块的自动化测试,提高了测试效率和开发质量。这种设计模式不仅使得整个流程更加标准化,也为未来的项目提供了可扩展性和维护性。