智能界面压力测试

“智能界面压力测试”功能(又称智能 Monkey 测试)是一款专为桌面应用设计的自动化压力测试工具。它能够智能地识别界面控件,并模拟真实用户的随机操作(点击、输入、拖拽等),从而帮助开发者发现应用在长时间运行或极高频操作下的稳定性问题、内存泄漏或崩溃异常。

Note

本功能目前主要针对 Qt 应用 和 Windows 应用。它通过遍历控件树实现智能探索,比传统的屏幕像素级随机点击具有更高的有效操作率。

为什么使用它?

传统的自动化测试侧重于验证功能逻辑的正确性,而 Monkey 测试侧重于:

  • 稳定性测试:通过成千上万次的随机操作,触发难以预见的边界情况或竞态条件。
  • 鲁棒性测试:验证应用在非预期操作序列下的行为。
  • 资源泄漏检测:长时间运行以观察内存、CPU 等资源的使用趋势。

快速上手步骤

1. 打开功能面板

在 CukeTest 的 AI 助手 侧边栏中,点击或搜索进入 “智能界面压力测试” 功能。

2. 配置测试参数

在界面左侧填写相关参数:

  • 被测应用: 选择已知的应用,或点击“...”按钮指定可执行文件路径 (.exe)。
  • 操作次数上限: 测试过程中允许进行的随机操作总数。
  • 执行时间 (分钟): 测试的最长持续时间。
  • 排除控件: 输入不希望被点击的控件的 text 属性(如“注销”、“退出”),多个控件用半角分号 ; 分隔。设置后,该控件及其下的所有子控件都会被排除。

Tip

如果未设置操作次数和执行时间上限,系统将默认在完整遍历界面所有可识别控件一次后停止。

3. 生成并运行代码

确认参数后,点击 “生成执行代码” 按钮。界面右侧将自动创建脚本文件(例如 monkey_script_1.js)。

脚本解析

生成的脚本基于 leanpro.monkey 库。下面分别以 Qt 应用和 Windows 应用为例介绍其核心代码结构:

示例 1:测试 Qt 应用 (默认)

JavaScript
const { Keyboard } = require('leanpro.common');
const { QtAuto } = require("leanpro.qt");
const { SmartMonkey } = require("leanpro.monkey");

async function smartTest() {
    Keyboard.disableIme(); // 禁用输入法防止干扰

    const monkey = new SmartMonkey({
        launchApp: () => { return QtAuto.launchQtProcessAsync("C:/path/to/your/app.exe"); },
        appName: "YourAppName",
        maxActions: 50,            // 操作次数
        maxDuration: 3 * 60,       // 最长持续时间,单位为秒
        code: true, // 生成代码
        tech: 'qt', // qt 或 win
        excludes: ["Bold", "Italic", "Underline", {
            "type": "MenuBar",
            "className": "QMenuBar"
        }], // 被排除控件列表。设置后,该控件及其下的所有子控件都会被排除
        verbose: true
    });

    // 可以在此处自定义控件筛选逻辑
    monkey.onControl(async (control, interested) => {
        if (!interested) return;
        // console.log('正在探索控件:', await control.modelProperties());
    });

    // 定义压力测试的起始视图/容器
    let view = QtAuto.getGeneric([...]); 
    
    // 启动测试,仅探索该容器内的控件树
    await monkey.start(view, {onlySubtree: true});

    // 生成报告
    await monkey.generateReport();
}

smartTest();

运行与报告

运行测试

您可以在 CukeTest 界面直接运行生成的脚本。运行过程中,被测应用会自动启动,并开始在界面上进行快速的模拟操作。

Note

如果需要中途停止,可以直接关闭测试终端、点击 CukeTest 的“停止运行”按钮,或者使用 快捷键 Ctrl+Alt+C (macOS 上为 ⌘⌥C) 停止运行。

查看报告

测试完成后,项目目录下会生成以 monkey_YYYY-MM-DD 命名的文件夹。

  • JSON 报告: 包含所有操作的结构化原始数据。
  • HTML 报告: 提供可视化的测试概览,操作序列以及由于操作导致的错误统计。

高级配置 (SmartMonkey 选项)

通过手动修改脚本中的 SmartMonkey 构造参数,可以更精细地控制测试:

参数 类型 说明
tech string 被测应用的技术类型。可选为 "qt"(默认,Qt 应用)或 "win"(Windows/UIA 应用)。
slowMo number 动作之间的延迟时间(毫秒),默认为 0。增加此值可放慢测试速度。
maxActions number 最大操作步数。
maxDuration number 最大执行时间(秒)。
excludes (string \ object)[] 排除的控件列表。可以是控件的 text 属性,也可以是包含过滤属性的对象(例如 {"type": "Edit", "className": "QLineEdit"} 用于排除特定的控件类型)。设置后,该控件及其下的所有子控件都会被排除。
onEvent Function 监听测试事件(如 start, end, startIteration 等)。

Tip

如何获取控件属性对象: 如果需要排除特定的控件类型,可以通过 模型管理器 来获取其属性。在模型管理器中选中目标控件后,点击“对象属性”工具栏中的 “复制节点属性” 按钮(图标为两个相连的节点),即可一键复制该控件的 type、className 等标识属性,直接用于配置 excludes 数组。

注意事项

Warning

数据安全:由于 Monkey 测试是随机的,存在点击“删除”、“格式化”等毁灭性操作的可能。请务必在独立的测试环境(如测试虚拟机)中运行,切勿在包含重要生产数据的系统上测试。

SmartMonkey类型说明

JavaScript
export interface SmartMonkeyOptions {
    launchApp?: () => Promise<void>; // 启动应用的回调函数
    appName: string;    // 应用名称
    verbose?: boolean;  // 详细日志输出
    slowMo?: number;    // 动作执行减速(毫秒)
    maxActions?: number;    // 最大动作数量
    maxDuration?: number;    // 最大执行时长(毫秒)
    code?: boolean;    // 是否生成代码
    overwrite?: boolean;    // 是否覆盖已有文件
    excludes?: any[];    // 排除的元素列表(支持 text 字符串或过滤属性对象,设置后其下的子控件也会被排除)
    tech?: 'qt' | 'win'; // 应用技术类型,支持 "qt" (默认) 或 "win"
}

// 进程状态枚举
export enum ProcessState {
    skip = 'skip',  // 跳过
    close = 'close',// 关闭窗口,仅对 Window 类型的控件有效
    stop = 'stop',  // 停止
    act = 'act'     // 执行动作
}

// 动作执行结果接口
export interface IActionResult {
    name: string    // 动作名称
    params: any[],  // 动作参数列表
    error?: string  // 动作错误信息
}

// SmartMonkey 事件枚举
export enum MonkeyEvent {
    start = 'start',    // 开始事件
    end = 'end',        // 结束事件
    startIteration = 'startIteration',  // 开始迭代
    endIteration = 'endIteration',      // 结束迭代
    startDialog = 'startDialog',        // 开始对话框
    endDialog = 'endDialog',            // 结束对话框
    launchApp = 'launchApp',            // 启动应用
}

// SmartMonkey 主类
export class SmartMonkey {
    constructor(options: SmartMonkeyOptions);
    // 开始执行自动化测试。支持 options.onlySubtree 参数,设置为 true 后,仅探索该控件的子树区域。
    start(startControl?: any, options?: { onlySubtree?: boolean }): Promise<void>;
    // 获取感兴趣的控件类型
    get interestedControls(): QtControlType;
    // 为每个控件触发回调,ProcessState 可以控制如何处理该控件
    onControl: (listener: (control: IQtControl, interested: boolean) => Promise<void | ProcessState>) => () => void;
    // 事件回调,可以是 start, end, startDialog, endDialog 等事件
    onEvent: (listener: (eventName: MonkeyEvent, ...params: any[]) => Promise<void>) => () => void;
    // 在执行每个动作后触发回调
    onAction: (listener: (control: IQtControl, action: IActionResult) => void) => () => void;
    // 生成测试报告
    generateReport(): Promise<void>;
}

API 介绍

new SmartMonkey(options)

构造函数,用于创建并初始化 SmartMonkey 压力测试实例。

参数

  • options: SmartMonkeyOptions 类型。定义测试所需的各项配置(如超时时间、被测应用名等)。具体配置项请参考 SmartMonkeyOptions。

返回值

  • SmartMonkey。返回一个初始化完成的智能压力测试对象。

示例代码

JavaScript
const monkey = new SmartMonkey({
    appName: "diagramscene",
    maxActions: 50
});

start(startControl, options)

启动界面压力探索测试。

参数

  • startControl: any,可选。指定压力测试的起始控件/视图容器(例如一个面板、窗口或 ToolBox)。若不指定,将默认从整个应用顶层窗口开始探索。
  • options: object,可选。启动选项:
    • onlySubtree: boolean,可选。若设为 true,则 Monkey 将仅限于在传入的 startControl 控件的子树区域内进行操作与探索;默认为 false。

返回值

  • Promise<void>。这是一个异步方法,当测试运行结束(达到操作限制或时间限制)后,Promise 会 resolve。

示例代码

JavaScript
let view = await QtAuto.getApplication({ "appName": "diagramscene" })
    .getWindow({ "windowTitle": "Diagramscene" })
    .getGeneric({ "className": "QToolBox" });

// 仅在 QToolBox 控件内部进行压力探索
await monkey.start(view, { onlySubtree: true });

interestedControls()

获取当前被测技术体系下,Monkey 感兴趣且会主动进行操作的控件类型列表。

返回值

  • QtControlType。返回包含各种可操作控件类型的数组或结构。

示例代码

JavaScript
const controls = monkey.interestedControls;
console.log("感兴趣的控件类型有:", controls);

onControl(listener)

注册一个控件遍历时的拦截器与回调函数。当 Monkey 引擎扫描并遇到任意控件时,都会触发该回调。

通过在此回调中返回不同的 ProcessState 状态,可以非常精细地动态干预测试行为(例如跳过特定控件、主动处理弹窗等)。

参数

  • listener: (control: IQtControl, interested: boolean) => Promise<void | ProcessState>。回调监听函数:
    • control: 当前探索到的控件对象。
    • interested: 控件是否在 Monkey 感兴趣的可操作列表中。
    • 回调返回值: 可以返回 void(默认处理),或者返回以下 ProcessState 值之一:
      • 'skip': 跳过该控件,不进行任何点击或输入操作。
      • 'close': 关闭窗口。注意:此选项仅对 Window 类型的控件有效。
      • 'stop': 立即终止本次压力测试。
      • 'act': 强制对该控件执行默认动作。

返回值

  • () => void。返回取消注册该监听器的函数。

使用场景

  • 动态控件过滤:若某些按钮会导致致命的操作(例如“格式化硬盘”或“退出登录”),而这些又无法仅通过 excludes 排除,可以通过 onControl 动态判断控件位置、文本或属性,返回 'skip'。
  • 异常状态阻断:当遇到特定致命错误弹窗时,记录日志并返回 'stop' 停止测试。

示例代码

JavaScript
// 排除所有带有特定文字(如“删除”)的按钮
const unsubscribe = monkey.onControl(async (control, interested) => {
    if (interested) {
        let name = await control.name();
        if (name.includes("删除")) {
            return "skip"; // 动态跳过
        }
    }
});

onEvent(listener)

注册一个生命周期事件监听器,用于监控测试执行中触发的宏观事件(如开始、结束、弹窗开启等)。

参数

  • listener: (eventName: MonkeyEvent, ...params: any[]) => Promise<void>。事件回调函数:
    • eventName: 触发的事件名称,如 start, end, startIteration, endIteration, startDialog, endDialog, launchApp。
    • params: 随事件携带的附加参数。

返回值

  • () => void。返回取消注册该监听器的函数。

示例代码

JavaScript
monkey.onEvent(async (eventName, ...params) => {
    if (eventName === 'start') {
        console.log("压力测试已经正式启动。");
    }
});

onAction(listener)

注册一个动作执行监听器。Monkey 每次向应用发送模拟操作(如点击、键盘输入、拖拽)后,都会触发此回调。

参数

  • listener: (control: IQtControl, action: IActionResult) => void。操作触发的回调函数:
    • control: 正在操作的控件。
    • action: IActionResult 类型,包含执行动作的 name(名称)、params(参数)以及 error(可能的错误信息)。

返回值

  • () => void。返回取消注册该监听器的函数。

使用场景

  • 用于在控制台实时打印测试步骤:console.log("执行动作:", action.name)。
  • 收集测试覆盖率,监控失败的操作步数。

示例代码

JavaScript
monkey.onAction((control, action) => {
    if (action.error) {
        console.warn(`动作 ${action.name} 执行失败,原因: ${action.error}`);
    }
});

generateReport()

生成本次压力测试的可视化测试报告。

运行后,将在项目路径的 monkey_YYYY-MM-DD 目录下生成 report.html (可视化报告) 与 report.json (原始执行数据)。

返回值

  • Promise<void>。这是一个异步方法。

示例代码

JavaScript
await monkey.generateReport();

results matching ""

    No results matching ""