Electron前端单元测试框架

框架概述
该单元测试框架旨在提供一套全面的工具集, 专注于前端交互测试, 同时集成了报告生成, 系统管理, 日志收集和通知机制等关键功能. 框架主要由以下两个核心部分组成:
- 前端交互工具库: 该库专门解决前端交互的问题, 包括元素点击, 页面滚动等操作. 通过简洁的API, 开发者可以轻松模拟用户行为, 进行界面测试. 这部分功能确保了测试脚本能够高效, 准确地与前端应用进行交互.
- 综合测试管理框架: 在前端交互工具库的基础上, 框架进一步提供了报告生成, 系统管理, 日志收集和通知机制等功能. 这些功能并不包含在前端交互工具库中, 而是作为框架的附加特性, 为开发者提供一个全面的测试解决方案. 这部分功能的集成使得整个测试流程更加自动化和系统化, 大大提升了测试的效率和可靠性.
特性
- 环境版本检查与依赖管理: 在测试前, 框架会自动检查环境中各个工具和依赖的版本, 确保测试环境的稳定性.
- 跨操作系统兼容性: 无论是在Windows还是Unix系统上, 框架都能稳定运行, 确保测试流程的一致性.
- 自动化报告生成: 通过集成org-mode, 框架能够自动生成结构化(带有目录, 日志和截图)的测试报告(html或者pdf), 便于分析和分享测试结果.
- 实时日志处理: 框架能够实时监控和处理日志信息, 帮助开发者快速定位问题.
- 通知机制: 集成了钉钉等通知平台, 测试完成后可自动发送报告, 确保团队成员及时获取最新的测试结果.

跨操作系统的系统级函数
为了确保框架在不同操作系统(windows/macos)下的兼容性, 首先需要判断当前运行的操作系统类型, 并根据不同的系统执行相应的命令. 框架中提供了以下几个基础函数:
win? 函数
用于判断当前操作系统是否为Windows 10或Windows 11.
(defn win?
"检查当前操作系统是否为Windows
- 无参数
- 返回布尔值, 如果是Windows 10/11则返回true, 否则返回false"
[]
(let [os-name (System/getProperty "os.name")]
(or (= "Windows 10" os-name)
(= "Windows 11" os-name))))
进程管理与命令执行
跨平台的命令执行和进程管理是测试框架的重要组成部分. 框架提供了多种方式来执行命令, 管理进程以及获取进程信息.
run-cmd 函数
以阻塞的方式同步的方式执行命令, 命令进程退出(exit)之后, 一次性输出结果, 才能继续执行.
执行指定的命令并返回执行结果. 根据操作系统的不同, 选择合适的命令执行方式(windows用powershell, 其他系统都自带shell).
(defn run-cmd
"执行指定的命令并返回执行结果
- 参数 `cmd`: 字符串, 要执行的命令
- 返回一个包含命令执行结果的映射, 包括: `:out`(标准输出), `:err`(错误输出)和`:exit`(退出状态)"
[cmd]
(if (win?)
(sh "powershell" "Invoke-Expression" (format "'%s'" cmd))
(sh "sh" "-c" cmd)))
sh-perline 函数
执行过程中, 实时逐行输出命令的标准输出, 适用于需要实时监控命令执行过程的场景.
(defn sh-perline
"执行指定命令并实时逐行输出其标准输出
- 参数 `cmd-str`: 字符串, 要执行的命令
- 返回进程对象"
[cmd-str]
(let [cmds (if (win?)
["powershell" "Invoke-Expression" (format "'%s'"cmd-str)]
["sh" "-c" (str "exec " cmd-str)])
process-builder (ProcessBuilder. (into-array String cmds))
process (.start process-builder)]
(println "进程号是" (.pid process))
(future (with-open [reader (BufferedReader. (InputStreamReader. (.getInputStream process)))]
(doseq [line (line-seq reader)]
(println line))))
process))
进程管理函数
测试前端过程中, 需要对后端进行检测和管理, 有时候需要启停某些进程. 以下是一组工具函数.
get-pids: 根据正则表达式查找匹配的进程ID列表.get-lein-pids: 获取运行Leiningen的所有进程ID.pkill: 根据提供的进程ID列表杀死相应的进程.start-all和kill-all: 分别用于启动和杀死所有Leiningen进程.
(defn get-pids
"根据正则表达式查找匹配的进程ID列表
- 参数 `regx`: 字符串, 用于匹配进程信息的正则表达式
- 返回匹配的进程ID列表"
[regx]
(let [jps (run-cmd (format "jps -v | grep %s" regx))]
(->> jps
:out
(str/split-lines)
(mapv #(str/split % #" "))
(mapv first)
(filter (complement empty?)))))
(defn get-lein-pids
"获取运行Leiningen的所有进程ID
- 无参数
- 返回运行Leiningen的进程ID列表"
[]
(get-pids "dev-config.edn"))
(defn pkill
"根据提供的进程ID列表杀死相应的进程
- 参数 `pids`: 进程ID列表
- 无返回值"
[pids]
(run! #(run-cmd (if (win?)
(format "taskkill /F /PID %s" %1)
(format "kill -9 %s" %1)))
pids))
(defn start-all
"使用Leiningen启动所有项目, 并记录日志
- 参数 `log`: 日志文件名
- 无返回值"
[log]
(sh-perline (format "lein run %s" log)))
(defn kill-all
"杀死所有Leiningen进程
- 无参数
- 无返回值"
[]
(pkill (get-lein-pids)))
日志处理与StepReader协议
日志是测试过程中重要的信息来源. 框架通过定义StepReader协议和相关函数, 实现了对日志的实时监控和处理.
能够做到testcase和日志的精确对应, 极大提升了测试后failed case的分析效率.
StepReader协议
定义了用于日志处理的基本函数, 包括启动日志读取, 获取当前执行的函数, 获取下一步骤以及获取下一个步骤的超时处理.
此外有些case的成功/失败判断经由日志中的结果, 更容易判断.
(defprotocol StepReader
"定义了StepReader协议, 包含用于日志处理的函数
- `start`: 启动日志读取
- `current-function`: 获取当前执行的函数
- `next-step`: 获取下一个步骤
- `next-step-timeout`: 获取下一个步骤 "
(start [this] "启动日志读取流程")
(current-function [this] "返回当前正在执行的函数")
(next-step [this] "返回下一个执行步骤")
(next-step-timeout [this] "超时时间内返回下一个执行步骤"))
LogReader实现
通过LogReader记录器, 实现了StepReader协议, 对日志文件进行实时监控, 并通过core.async通道传递日志信息.
(defrecord LogReader [log-file interval func-chan step-chan]
StepReader
(start [_] (let [f (fn [file-path interval]
(let [file (File. file-path)
reader (BufferedReader. (FileReader. file))
init-len (.length file)]
(.skip reader init-len)
(loop [last-len init-len]
(Thread/sleep interval)
(let [current-len (.length file)]
(when (< last-len current-len)
(loop []
(let [line (.readLine reader)]
(when line
(let [[f_ f-name] (fun-name line)]
(when (seq f-name)
(timbre/info "Put Value Into Func-c")
(async/put! func-chan f-name)))
(when-let [_step (step? line)]
(async/put! step-chan line))
(recur)))))
(recur current-len)))))]
(future (f log-file 1000))))
(current-function [_] (fn [] (async/<!! func-chan)))
(next-step [_] (fn [] (async/<!! step-chan)))
(next-step-timeout [_] (fn [timeout]
(let [[v _c] (async/alts!! [(async/timeout
(* 1000 timeout))
step-chan])]
(or v (throw (ex-info "等待日志超时" {})))))))
日志处理函数
fun-name: 从日志行中提取函数名.step?: 检查日志行是否表示一个步骤.log-handler: 处理日志文件, 返回获取当前函数和下一步骤的函数.
(defn fun-name
"从日志行中提取函数名
- 参数 `log-line`: 日志中的一行
- 返回该行中提到的函数名"
[log-line]
(first (re-seq #"([A-zA-z>0-9-]+) Starts" log-line)))
(defn step?
"检查日志行是否表示一个步骤
- 参数 `log-line`: 日志中的一行
- 返回布尔值, 指示该行是否包含步骤信息"
[log-line]
(let [res [#"open valves:"
#"open pumps:"
#"close valves"
#"close pumps:"
#"([A-zA-z>0-9-]+) Ends"
#"interval"
#"speed"
#"motor"
#"receive-M1"
#"receive-M2"] ;; 此处保留各类扩展
fs (mapv #(partial re-seq %) res)]
(some (complement nil?) (mapv #(% log-line) fs))))
(defn log-handler
"处理日志文件, 返回两个函数来获取当前执行的函数和下一步骤
- 参数 `log-file`: 日志文件路径
- 参数 `interval`: 检查新日志的时间间隔(毫秒)
- 返回三个个函数: 1. 获取当前函数, 2. 获取下一步骤 3. 必须在x秒之内获得下一步骤"
[log-file interval]
(let [r (->LogReader log-file interval (async/chan (async/sliding-buffer 1)) (async/chan))]
(start r)
[(current-function r) (next-step r) (next-step-timeout r)]))
版本检查与依赖管理
确保测试环境中的各个工具和依赖版本符合要求, 是保证测试稳定性的重要步骤. 框架提供了一系列函数, 用于检查Java, Node.js, Leiningen等工具的版本.
compare-versions 函数
用于比较两个版本号, 支持大版本.小版本.编译序号的格式.
(defn compare-versions
"比较两个版本号, 版本号构成`大版本.小版本.编译序号`
- 参数 `v1`和`v2`: 字符串, 表示要比较的版本号
- 返回值: 如果v1较新, 返回1; 如果相同, 返回0; 如果v2较新, 返回-1"
[v1 v2]
(let [parts1 (map read-string (clojure.string/split v1 #"\."))
parts2 (map read-string (clojure.string/split v2 #"\."))
comparison (map compare parts1 parts2)]
(cond
(some pos? comparison) 1 ; v1 is greater
(some neg? comparison) -1 ; v2 is greater
:else 0))) ; versions are equal
check-version 函数
检查所有依赖的版本是否符合要求, 并输出检查结果.
(defn check-version
"检查所有依赖的版本是否符合要求
- 无参数
- 无返回值, 但会打印每个依赖的版本检查结果"
[]
(run-version-check "node --version" #"v(.*)" "16.0.0" "node.js")
(run-version-check "lein --version" #"Leiningen ([0-9.]+)" "2.10.0" "lein")
(run-version-check "bb --version" #"babashka v(.*)" "1.3.100" "bb")
(run-version-check "clj-kondo --version" #"clj-kondo v(.*)" "2023.10.20" "clj-kondo")
(java-verison))
测试报告生成与集成
为了更好地展示测试结果, 框架集成了org-mode, 并提供了生成和插入测试报告的功能. 最终的测试报告以org-mode格式生成, 便于在Emacs或其他支持org-mode的编辑器中查看和管理.
报告生成逻辑
测试报告的生成逻辑主要包括以下几个步骤:
- 初始化报告文件: 使用
gen-file函数, 根据测试名称生成一个新的org-mode报告文件, 并插入主题和测试标题. - 插入测试内容: 在测试过程中, 通过
org-insert-txt和org-insert-image函数将测试结果和截图插入到报告文件中. - 结构化分段: 报告文件按照测试的层级结构进行分段, 使用
*,**等org-mode的标题级别来区分不同的测试部分和细节.
报告生成函数
gen-file 函数
用于生成新的org-mode报告文件, 并初始化文件内容.
(defn gen-file "使用`test-name`生成报告文件, 主题为文件的一级目录为`test-name`"
[test-name]
(let [file-name (org-file-name test-name)]
(fs/delete-if-exists file-name)
(spit file-name (str
org-header
"* " test-name "\n"))))
org-insert-txt 函数
在报告文件中插入文本内容, 支持插入测试描述, 结果等信息.
(defn org-insert-txt "在`test-name`测试报告中插入文本`txt`"
[test-name txt]
(let [file-name (org-file-name test-name)]
(spit file-name (str txt "\n") :append true)))
org-insert-image 函数
在报告文件中插入图片截图, 便于直观展示测试过程中出现的问题或关键步骤.
(defn org-insert-image "在测试报告中插入图片"
[test-name img-name]
(let [image-path (str/replace (image-path test-name img-name) "target/" "")
file-name (org-file-name test-name)]
(spit file-name (str
"#+attr_html: :width 800px\n #+attr_latex: :width 800px\n #+ATTR_LATEX: :width 15cm\n"
"[[file:" image-path "]]\n") :append true)))
报告文件结构与分段
org-mode报告文件的结构化分段如下:
- 一级标题(
*): 测试名称, 作为报告的主要标题. - 二级标题(
**): 测试的各个部分或模块, 例如不同的功能测试, 集成测试等. - 三级标题及以下(
***,****等): 具体的测试用例或步骤, 详细描述每个测试的执行情况, 结果和截图.
例如:
#+SETUPFILE: https://gitee.com/zhaoyuKevin/org-theme/raw/master/org/theme-readtheorg.setup
* 多肽测试报告
** 用户登录测试
*** 测试用例1: 成功登录
- 测试步骤:
1. 打开登录页面
2. 输入用户名和密码
3. 点击登录按钮
- 预期结果: 登录成功, 跳转到首页
- 实际结果: 登录成功
[[file:target/multipeptide-test/2024-04-27-123456-789012.png]]
*** 测试用例2: 登录失败
- 测试步骤:
1. 打开登录页面
2. 输入错误的用户名和密码
3. 点击登录按钮
- 预期结果: 显示错误提示信息
- 实际结果: 显示错误提示信息
[[file:target/multipeptide-test/2024-04-27-123457-789013.png]]
测试宏扩展
为了简化测试过程中报告的生成和更新, 框架提供了两个宏: deftest-with-rpt 和 testing-with-rpt, 它们扩展了clojure.test的功能, 在测试过程中自动生成和更新测试报告.
deftest-with-rpt 宏
扩展了clojure.test/deftest, 在测试开始时插入测试标题, 并在测试结束后记录日志.
(defmacro deftest-with-rpt [name & body]
`(clojure.test/deftest ~name
(let [bc (backend-log-count)
pc (plc-log-count)]
(utils/org-insert-txt *test-name* (str "** " '~name))
~@body
#_(when-not (zero? pc)
(let [logs (drop pc (all-plc-log))]
(utils/org-insert-txt *test-name* (plc-logs 3 logs))))
#_(when-not (zero? bc)
(let [logs (drop bc (all-backend-log))]
(utils/org-insert-txt *test-name* (backend-logs 3 logs))))))
testing-with-rpt 宏
扩展了clojure.test/testing, 在每个测试步骤中插入相应的报告内容, 包括步骤描述和日志信息.
(defmacro testing-with-rpt [name & body]
`(clojure.test/testing ~name
(let [bc (backend-log-count)
pc (plc-log-count)]
(utils/org-insert-txt *test-name* (str "*** " ~name))
(try
~@body
(catch Exception e
(println "exception !!!!!:" (ex-data e))))
(when-not (zero? pc)
(let [logs (remove-query (drop pc (all-plc-log)))]
(utils/org-insert-txt *test-name* (plc-logs 4 logs))))
(when-not (zero? bc)
(let [logs (drop bc (all-backend-log))]
(utils/org-insert-txt *test-name* (backend-logs 4 logs))))))
报告内容插入示例
在测试用例中使用这些宏, 可以自动将测试结果和日志信息插入到报告文件中.
(deftest-with-rpt 用户登录测试
(testing-with-rpt "成功登录"
;; 测试逻辑
(is-with-rpt (login? driver) "登录应成功"))
(testing-with-rpt "登录失败"
;; 测试逻辑
(is-with-rpt (not (login? driver)) "登录应失败")))
上述代码将在报告文件中生成如下结构:
* 多肽测试报告
** 用户登录测试
*** 成功登录
| 测试表达式 | 结果 | 备注 |
|" (login? driver)" |✅ | 登录应成功 |
*** 登录失败
| 测试表达式 | 结果 | 备注 |
|" (not (login? driver))" |✅ | 登录应失败 |
并根据测试过程中生成的截图和日志信息, 自动插入相应的内容.
通知与自动化
测试完成后, 框架支持通过钉钉(DingTalk)发送测试报告和统计信息, 方便团队及时了解测试结果.
发送钉钉消息
通过定义钉钉机器人的Webhook地址, 框架可以将生成的测试报告发送到指定的钉钉群.
(defn ding-md
"发送叮叮消息"
[hook msg]
(http/post hook
{:headers {:content-type "application/json"}
:body (json/encode msg)}))
生成Markdown消息
将测试报告转换为Markdown格式, 以便在钉钉中以美观的方式展示.
(defn package->md-msg []
{:msgtype "markdown"
:at {:isAtAll true}
:markdown {:title "多肽版本发布"
:text (format "## 多肽版本发布
- 请到 http://192.168.1.129/ 登录下载
## git信息
%s
" (log-md-format (git-log)))}})
发送报告消息
通过调用相应的函数, 将生成的Markdown消息发送到钉钉群.
(defn send-package-msg []
(when-let [msg (package->md-msg)]
(ding-md ut-hook msg)))
(defn send-xray-msg []
(when-let [msg (xray-org-file->md-msg xray-org)]
(ding-md xray-hook msg)))
结论
通过上述功能模块的整合, 这个基于Clojure的单元测试封装框架不仅提升了测试的自动化和跨平台兼容性, 还通过实时日志处理和详细的测试报告生成, 帮助开发团队更高效地发现和解决问题. 同时, 集成的通知功能确保测试结果能够及时传达到相关人员, 进一步优化了开发和测试流程.