个人技术分享

1. 背景

为了减轻测试人员在进行MFC程序压力测试时的重复手动操作,本文档描述了开发一个自动化压力测试工具的过程。该工具能够根据程序界面某块区域的预定状态变化,自动执行鼠标点击或键盘输入操作。

2. 技术概览

  • 串口控制:用于控制外部设备,如继电器。
  • MFC CRectTracker:实现截图功能,创建简单的截图对话框。
  • GDI+:用于图像处理和屏幕捕获。
  • mouse_event:模拟鼠标点击。
  • keybd_event:模拟键盘输入。
  • MD5:用于图像内容校验。

3. 串口通信控制

串口通信用于控制继电器的上下电状态,进而控制USB设备的电源。

3.1 串口初始化

bool CComTest::InitialCom(int iComID, int iComPort, DWORD iBaudRate) {

    if (INVALID_HANDLE_VALUE != m_hCom)
    {
        CloseCom(iComID);
    }

    if (INVALID_HANDLE_VALUE != m_hCom)
    {
        CloseHandle(m_hCom);
    }

    m_iComPort = iComPort;      //串口通信端口
    m_iBaudRate = iBaudRate;    //串口通信速率

    DCB Dcb;
    CString str;
    COMMTIMEOUTS TimeOut;

    int Data = 8;
    int Stop = 0;
    int Parity = 0;

    str.Format(_T("COM%d"), iComPort);
    m_hCom = CreateFile(str, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, NULL, NULL);
    if (m_hCom == INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }

    // 配置串口参数
    DCB dcbSerialParams = {0};
    GetCommState(m_hCom, &dcbSerialParams);
    dcbSerialParams.BaudRate = iBaudRate;
    dcbSerialParams.ByteSize = 8;
    dcbSerialParams.StopBits = STOPBITS_ONE;
    dcbSerialParams.Parity = NOPARITY;
    SetCommState(m_hCom, &dcbSerialParams);

    // 设置串口超时
    COMMTIMEOUTS timeouts = {0};
    timeouts.ReadIntervalTimeout = MAXDWORD;
    SetCommTimeouts(m_hCom, &timeouts);

    // 完成串口初始化
    return TRUE;
}

3.2 读取串口

DWORD CComTest::ReadCom(BYTE *pBuff, int nCount) {
    DWORD dwRead = 0;
    ReadFile(m_hCom, pBuff, nCount, &dwRead, NULL);
    return dwRead;
}

3.3 写串口

BOOL CComTest::WriteCom(BYTE *pBuff, int nCount) {
    DWORD dwWritten = 0;
    return WriteFile(m_hCom, pBuff, nCount, &dwWritten, NULL);
}

3.4 发送串口命令示例

BYTE pData[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
WriteCom(m_iComID, pData, sizeof(pData));

4. 程序界面状态监控

通过截图对话框实现区域状态监控,自动执行下一步动作。

4.1 截图对话框初始化

BOOL CCaptureDlg::OnInitDialog() {
    CDialog::OnInitDialog();
    // 屏幕截图并设置窗口大小
    copyScreenToBitmap(m_ScreenBmp);
    MoveWindow(-3, -3, GetSystemMetrics(SM_CXSCREEN) + 6, GetSystemMetrics(SM_CYSCREEN) + 6);

    // 初始化橡皮筋跟踪器
    m_rectTracker.m_nStyle = CRectTracker::resizeOutside | CRectTracker::dottedLine;
    m_rectTracker.m_rect.SetRect(0, 0, 0, 0);

    return TRUE;
}

4.2 截图对话框背景绘制

BOOL CCaptureDlg::OnEraseBkgnd(CDC *pDC) {
    CDC memDC;
    memDC.CreateCompatibleDC(pDC);
    memDC.SelectObject(&m_ScreenBmp);
    pDC->BitBlt(0, 0, GetClientRect().Width(), GetClientRect().Height(), &memDC, 0, 0, SRCCOPY);
    memDC.DeleteDC();
    return TRUE;
}

4.3 鼠标消息处理

void CCaptureDlg::OnLButtonDown(UINT nFlags, CPoint point) {
    if (m_rectTracker.HitTest(point) == CRectTracker::hitNothing) {
        m_rectTracker.TrackRubberBand(this, point, TRUE);
    } else {
        m_rectTracker.Track(this, point, TRUE);
        m_rectTracker.m_rect.NormalizeRect();
    }
    Invalidate(TRUE);
    CDialog::OnLButtonDown(nFlags, point);
}

4.4 双击事件处理

void CCaptureDlg::OnLButtonDblClk(UINT nFlags, CPoint point) {
    if (m_rectTracker.HitTest(point) != CRectTracker::hitMiddle) {
        MessageBox(_T("截图失败,请重新截图!"));
        return;
    }
      // 检查双击是否在橡皮筋跟踪器的中间区域
    if (m_rectTracker.HitTest(point) != CRectTracker::hitMiddle)
    {
        MessageBox(_T("截图失败, 请重新截图!"));
        return;
    }

  // 保存双击时坐标
 m_startPointX = point.x;
 m_startPointY = point.y;

    // 获取当前屏幕设备上下文
    CDC* pScreenDC = GetDC();
    if (!pScreenDC)
    {
        MessageBox(_T("无法获取屏幕设备上下文"));
        return;
    }

    // 创建内存设备上下文与屏幕DC兼容,并选择m_ScreenBmp位图进该内存DC
    CDC memDC;
    memDC.CreateCompatibleDC(pScreenDC);
    CBitmap* pOldBitmap = memDC.SelectObject(&m_ScreenBmp);

    // 获取橡皮筋跟踪器定义的区域
    CRect captureRect = m_rectTracker.m_rect;

    // 创建一个兼容的位图用于保存截取的区域
    CBitmap captureBmp;
    captureBmp.CreateCompatibleBitmap(pScreenDC, captureRect.Width(), captureRect.Height());

    // 创建另一个内存DC,选择刚才创建的兼容位图进这个DC
    CDC captureDC;
    captureDC.CreateCompatibleDC(pScreenDC);
    CBitmap* pOldCaptureBmp = captureDC.SelectObject(&captureBmp);

    // 从原屏幕位图中截取定义区域的图像到captureDC
    captureDC.BitBlt(0, 0, captureRect.Width(), captureRect.Height(), &memDC, captureRect.left, captureRect.top, SRCCOPY);

    // 使用CImage类来保存位图到文件
    CImage img;
    img.Attach(captureBmp);
    CString strCapturePath = _T("你想要保存的路径\\capture1.jpg");
    img.Save(strCapturePath);

    // 清理资源
    captureDC.SelectObject(pOldCaptureBmp);
    memDC.SelectObject(pOldBitmap);
    captureBmp.DeleteObject();
    memDC.DeleteDC();
    captureDC.DeleteDC();
    ReleaseDC(pScreenDC);

    // 截图操作完成后关闭对话框
    CDialog::OnCancel();
}

在压测前,将被压测程序运行到指定状态,用上述[截图窗口]截取指定区域的位图(目标状态),在压测时,不停的检测此区域的变化,如达到目标状态,则执行模拟点击等操作。

4.5 截取屏幕上指定区域的图像

下面函数用于截取屏幕上指定区域的图像,在压测时,可用于实时检测指定区域的位图。

void CCaptureDlg::Screen(const CRect& cRect, int iNumber)
{
    // 使用局部函数来自动释放资源
    struct AutoRelease
    {
        HDC hDC;
        ~AutoRelease() { if (hDC) ::ReleaseDC(NULL, hDC); }
    } autoReleaseDC;

    // 获取屏幕设备上下文
    autoReleaseDC.hDC = ::GetDC(NULL);
    if (!autoReleaseDC.hDC)
    {
        MessageBox(_T("无法获取屏幕设备上下文。"));
        return;
    }

    CDC memDC;
    memDC.CreateCompatibleDC(NULL); // 创建与屏幕兼容的内存设备上下文

    // 创建与屏幕兼容的位图
    CBitmap memBitmap;
    memBitmap.CreateCompatibleBitmap(autoReleaseDC.hDC, cRect.Width(), cRect.Height());

    // 将新创建的位图选入内存设备上下文中
    CBitmap* pOldBitmap = memDC.SelectObject(&memBitmap);

    // 从屏幕设备上下文中复制图像到内存设备上下文中的位图
    memDC.BitBlt(0, 0, cRect.Width(), cRect.Height(), CDC::FromHandle(autoReleaseDC.hDC), cRect.left, cRect.top, SRCCOPY);

    // 使用CImage类来操作和保存位图
    CImage img;
    if (!img.Attach(memBitmap))
    {
        MessageBox(_T("无法将位图附加到CImage对象。"));
        return;
    }

    // 构造文件保存路径
    CString strCapturePath;
    strCapturePath.Format(_T("保存路径\\capture%d.jpg"), iNumber);

    // 保存位图到文件
    if (!img.Save(strCapturePath))
    {
        AfxMessageBox(_T("保存图像失败。"));
    }

    // 清理资源
    memDC.SelectObject(pOldBitmap); // 恢复原始位图
    img.Detach(); // 从CImage对象中分离位图
}

5. 模拟输入操作

5.1 模拟鼠标点击

void SimulateMouseClick(int x, int y) {
    SetCursorPos(x, y);
    mouse_event(MOUSEEVENTF_LEFTDOWN, x, y, 0, 0);
    mouse_event(MOUSEEVENTF_LEFTUP, x, y, 0, 0);
}

5.2 模拟键盘输入

void SimulateKeyboardInput(const char *szInput) {
    for (int i = 0; szInput[i] != '\0'; ++i) {
        keybd_event(szInput[i], 0, 0, 0);
        keybd_event(szInput[i], 0, KEYEVENTF_KEYUP, 0);
    }
}

结束语

本文档提供了一个自动化压力测试工具的基础实现,包括串口通信、截图监控、状态校验和模拟输入操作。根据具体需求,可进一步扩展和优化工具的功能。