From 18b167266d62708d0f7066fe335bf58d45f30f76 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Sun, 28 Nov 2021 15:03:37 +0800 Subject: [PATCH] Add test module --- miniprogram/app.ts | 3 +- miniprogram/logger/InternalLogLabel.ts | 8 +- miniprogram/logger/Logger.ts | 8 +- miniprogram/logger/StackInfo.ts | 43 +++++- miniprogram/logger/index.ts | 11 -- miniprogram/{ => utils}/Config.ts | 0 miniprogram/utils/TestCase.ts | 206 +++++++++++++++++++++++++ 7 files changed, 254 insertions(+), 25 deletions(-) delete mode 100644 miniprogram/logger/index.ts rename miniprogram/{ => utils}/Config.ts (100%) create mode 100644 miniprogram/utils/TestCase.ts diff --git a/miniprogram/app.ts b/miniprogram/app.ts index 06eab41..02194b4 100644 --- a/miniprogram/app.ts +++ b/miniprogram/app.ts @@ -1,4 +1,5 @@ -import { Logger, LevelLogLabel } from "./logger/index"; +import { Logger } from "./logger/Logger"; +import { LevelLogLabel } from "./logger/LevelLogLabel"; App({ diff --git a/miniprogram/logger/InternalLogLabel.ts b/miniprogram/logger/InternalLogLabel.ts index 82c690a..f17cbc8 100644 --- a/miniprogram/logger/InternalLogLabel.ts +++ b/miniprogram/logger/InternalLogLabel.ts @@ -11,7 +11,7 @@ class InternalLogLabel { * 堆栈路径样式 */ public static readonly normalStyle:LogStyle = new LogStyle() - .setColor("#CCCCCC").setBorder("4px", "1px solid #979797").setBlank("0 5px"); + .setColor("#979797").setBorder("4px", "1px solid #979797").setBlank("0 5px"); /** * 一个回车 @@ -27,7 +27,7 @@ class InternalLogLabel { // 获得调用堆栈 let stack = StackInfo.getFirstStack(); - return new LogLabel(stack?.fileName ?? "Unknown file name", + return new LogLabel(stack?.calcFileName() ?? "Unknown file name", InternalLogLabel.normalStyle, false, true, true); } @@ -39,7 +39,7 @@ class InternalLogLabel { // 获得调用堆栈 let stack = StackInfo.getFirstStack(); - return new LogLabel(stack?.url ?? "Unknown url", + return new LogLabel(stack?.calcPathName() ?? "Unknown url", InternalLogLabel.normalStyle, false, true, true); } @@ -51,7 +51,7 @@ class InternalLogLabel { // 获得调用堆栈 let stack = StackInfo.getFirstStack(); - return new LogLabel(stack?.url ?? "Unknown url", + return new LogLabel(stack?.calcPathName() ?? "Unknown url", InternalLogLabel.normalStyle, true, false, true); } diff --git a/miniprogram/logger/Logger.ts b/miniprogram/logger/Logger.ts index 2a5a482..a2d8506 100644 --- a/miniprogram/logger/Logger.ts +++ b/miniprogram/logger/Logger.ts @@ -1,4 +1,4 @@ -import {LOGGER_CONSOLE, LOGGER_FILTER} from "../Config"; +import { LOGGER_FILTER, LOGGER_CONSOLE } from "../utils/Config"; import { InternalLogLabel } from "./InternalLogLabel"; import { LogLabel } from "./LogLabel"; import { MultipleLogContent } from "./MultipleLogContent"; @@ -147,12 +147,12 @@ class Logger { public static logLine(content:T, ...labels:LogLabel[]):T { return Logger.logBase>( new MultipleLogContent>(content), labels, - [InternalLogLabel.urlLabel, InternalLogLabel.blankLabel] + [InternalLogLabel.fileNameLabel, InternalLogLabel.blankLabel] )[0]; } /** - * 函数 Logger.logMultiple 的别名 + * 函数 Logger.logLine 的别名 */ public static ll:typeof Logger.logLine = Logger.logLine; @@ -164,7 +164,7 @@ class Logger { public static logLineMultiple>(labels:LogLabel[], ...content:T):T { return Logger.logBase( new MultipleLogContent(...content), labels, - [InternalLogLabel.urlLabel, InternalLogLabel.blankLabel] + [InternalLogLabel.fileNameLabel, InternalLogLabel.blankLabel] ); } diff --git a/miniprogram/logger/StackInfo.ts b/miniprogram/logger/StackInfo.ts index 4b6d06d..1793b8a 100644 --- a/miniprogram/logger/StackInfo.ts +++ b/miniprogram/logger/StackInfo.ts @@ -19,13 +19,46 @@ class StackInfo { */ public url:string | undefined; - public setInfo(functionName:string, fileName:string, url:string):StackInfo { + /** + * 文件名和行号 + */ + public fileNameLine: string | undefined; + + /** + * 设置信息 + * @param functionName 函数名 + * @param fileName 文件名 + * @param url 文件路径 + */ + public setInfo(functionName:string, fileNameLine:string, url:string):StackInfo { this.functionName = functionName; - this.fileName = fileName; + this.fileNameLine = fileNameLine; this.url = url; return this; } + /** + * 计算文件名 + */ + public calcFileName():string | undefined { + + let replaceToTs = this.fileNameLine?.replace(".js", ".ts"); + let matched = replaceToTs?.match(/^(.+\.(js|ts)):\d+:\d+$/); + + return matched ? matched[1] : undefined; + } + + /** + * 计算路径名 + */ + public calcPathName():string | undefined { + + let replaceToTs = this.url?.replace(".js", ".ts"); + let matched = replaceToTs?.match(/^https?:\/\/(\d+\.){3}\d+:\d+\/(.+):\d+:\d+$/); + + return matched ? matched[2] : undefined; + } + /** * 获取函数调用栈列表 */ @@ -65,7 +98,7 @@ class StackInfo { /** * 排除的 */ - public static readonly excludeFile:RegExp = /^Logger\.js:\d+:\d+/; + public static readonly excludeFile:RegExp = /^.*(\\|\/)logger(\\|\/).+\.js:\d+:\d+/; /** * 获取第一个调用栈 @@ -76,9 +109,9 @@ class StackInfo { for(let i = 0; i < callStack.length; i++) { - if(!callStack[i].fileName) continue; + if(!callStack[i].url) continue; - if(!StackInfo.excludeFile.test(callStack[i].fileName ?? "")) { + if(!StackInfo.excludeFile.test(callStack[i].url ?? "")) { return callStack[i]; } } diff --git a/miniprogram/logger/index.ts b/miniprogram/logger/index.ts deleted file mode 100644 index 6c169a9..0000000 --- a/miniprogram/logger/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Logger from "./Logger"; - -export default Logger; -export { Logger }; - -export { InternalLogLabel } from "./InternalLogLabel"; -export { MultipleLogContent } from "./MultipleLogContent"; -export { LevelLogLabel } from "./LevelLogLabel"; -export { LogLabel } from "./LogLabel"; -export { LogStyle } from "./LogStyle"; -export { StackInfo } from "./StackInfo"; \ No newline at end of file diff --git a/miniprogram/Config.ts b/miniprogram/utils/Config.ts similarity index 100% rename from miniprogram/Config.ts rename to miniprogram/utils/Config.ts diff --git a/miniprogram/utils/TestCase.ts b/miniprogram/utils/TestCase.ts new file mode 100644 index 0000000..b1cc94d --- /dev/null +++ b/miniprogram/utils/TestCase.ts @@ -0,0 +1,206 @@ +// import { Logger } from "../logger/Logger"; +import { LogStyle } from "../logger/LogStyle"; +import { LogLabel } from "../logger/LogLabel"; + +/** + * 测试结果 + */ +class TestResult { + + /** + * 用例名称 + */ + public caseName:string; + + /** + * 测试结果 + */ + public result:boolean; + + /** + * 消息 + */ + public message:string; + + /** + * 附加消息 + */ + public attach:string; + + /** + * 初始化 + * @param caseName 用例名称 + */ + constructor(caseName:string) { + this.caseName = caseName; + this.result = false; + this.message = ""; + this.attach = ""; + } + + /** + * 设置结果 + */ + public setResult(result:boolean, message?:string, attach?:string) { + this.result = result; + this.message = message ?? (result ? "success!" : "failed!"); + this.attach = attach ?? this.attach; + + return this; + } +} + +/** + * 测试函数结构 + */ +type TestFunction = () => TestResult | Promise; + +/** + * 收集测试函数结构 + */ +class CaseCollect { + + /** + * 用例键名 + */ + public key:string; + + /** + * 用例测试函数 + */ + public caseFunction:TestFunction; + + /** + * 测试结果 + */ + result: Promise | undefined; + + /** + * @param key 测试用例键名 + * @param caseFunction 测试函数 + */ + public constructor(key:string, caseFunction:TestFunction) { + this.key = key; + this.caseFunction = caseFunction; + } + + /** + * 运行测试用例 + */ + public async runTestCase():Promise { + + let result = this.caseFunction(); + + if(result instanceof Promise) { + this.result = result; + } else { + this.result = Promise.resolve(result); + } + + return this; + } + + public static readonly baseStyle = new LogStyle().setBlank(); + + public static readonly successLabel:LogLabel = new LogLabel("√", + new LogStyle().setBlank("0 4px").setBorder("1000px", "1px solid green") + ); + + /** + * 打印结果 + * @param current 当前进度 + * @param total 总进度 + */ + public printResult(current?:number, total?:number) { + + // 如果测试没有运行,先运行它 + if(this.result === void 0) this.runTestCase(); + + this.result?.then((res) => { + + if(res.result) { + console.log( + `%c√%c %c1/1%c %c${ this.key }%c ` + res.message, + "padding:0 4px; border-radius:1000px; border:1px solid green; color:green", + "", "padding:0 4px; border-radius:4px; border:1px solid green; color:green", + "", "padding:0 4px; border-radius:4px; border:1px solid #979797; color:#979797", + "" + ) + } else { + console.log( + `%c√%c %c1/1%c %c${ this.key }%c ` + res.message, + "padding:0 4px; border-radius:1000px; border:1px solid red; color:red", + "", "padding:0 4px; border-radius:4px; border:1px solid red; color:red", + "", "padding:0 4px; border-radius:4px; border:1px solid #979797; color:#979797", + "" + ) + } + console.log(res) + }) + } + + /** + * 收集测试用例 + * @param testCaseClass 测试用例表 + */ + public static collectCase(testCaseClass:ITestCase):CaseCollect[] { + + // 获取静态方法 key + let key = Object.getOwnPropertyNames(testCaseClass); + + // 过滤掉通用的方法和属性 + key = key.filter((key) => !/(length|name|prototype)/.test(key) ); + + // 生成 CaseCollect + let caseCollect = []; + + for (let i = 0; i < key.length; i++) { + caseCollect.push(new CaseCollect(key[i], testCaseClass[key[i]])) + } + + return caseCollect; + } + + /** + * 运行测试样例 + */ + public static async runCollectCase(cases:CaseCollect[]):Promise { + + let running:Promise[] = []; + + for(let i = 0; i < cases.length; i++) { + running.push(cases[i].runTestCase()); + } + + return Promise.all(running); + } + + /** + * 启动单元测试 + */ + public static runUnitTest(testCaseClass:ITestCase) { + + let caseCollect = this.collectCase(testCaseClass); + + CaseCollect.runCollectCase(caseCollect).then((caseCollect:CaseCollect[]) => { + + for(let i = 0; i < caseCollect.length; i++) { + caseCollect[i].printResult() + } + }) + } +} + +/** + * 测试用例接口 + */ +interface ITestCase { + + /** + * 测试用例函数 + */ + [key:string]:TestFunction; +} + +export default ITestCase; +export { ITestCase, TestResult, TestFunction, CaseCollect }; \ No newline at end of file