PyQt5桌面应用开发(20):界面设计结果自动测试(一)

news2024/11/18 13:51:26

本文目录

  • PyQt5桌面应用系列
  • PyQt5的测试驱动开发(Test-Driven Development,TDD)
  • QTest
    • UI动作函数
    • 信号测试
  • 最平凡的例子
  • unittest框架
  • 总结

PyQt5桌面应用系列

  • PyQt5桌面应用开发(1):需求分析
  • PyQt5桌面应用开发(2):事件循环
  • PyQt5桌面应用开发(3):并行设计
  • PyQt5桌面应用开发(4):界面设计
  • PyQt5桌面应用开发(5):对话框
  • PyQt5桌面应用开发(6):文件对话框
  • PyQt5桌面应用开发(7):文本编辑+语法高亮与行号
  • PyQt5桌面应用开发(8):从QInputDialog转进到函数参数传递
  • PyQt5桌面应用开发(9):经典布局QMainWindow
  • PyQt5桌面应用开发(10):界面布局基本支持
  • PyQt5桌面应用开发(11):摸鱼也要讲基本法,两个字,16
  • PyQt5桌面应用开发(12):QFile与线程安全
  • PyQt5桌面应用开发(13):QGraphicsView框架
  • PyQt5桌面应用开发(14):数据库+ModelView+QCharts
  • PyQt5桌面应用开发(15):界面动画
  • PyQt5桌面应用开发(16):定制化控件-QPainter绘图
  • PyQt5桌面应用开发(17):类结构+QWebEngineView
  • PyQt5桌面应用开发(18):自定义控件界面设计与实现
  • PyQt5桌面应用开发(19):事件过滤器
  • PyQt5桌面应用开发(20):界面设计结果自动测试(一)

PyQt5的测试驱动开发(Test-Driven Development,TDD)

测试驱动开发(Test-Driven Development,TDD)是一种软件开发过程,它要求在编写功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。测试驱动开发的目标是使代码更加简洁、可读和可维护。

测试驱动开发可以使程序员在编写代码之前思考软件设计。

测试驱动开发的步骤:

  1. 添加一个测试
  2. 运行所有测试,新添加的测试应该失败
  3. 编写实现代码
  4. 运行所有测试,若有测试失败,重复步骤3-4
  5. 重构代码
  6. 重复步骤1-5
  7. 完成

QTest

QTest是Qt提供的一个单元测试框架,它提供了一些宏,可以用来测试Qt程序的各个部分,包括GUI部分。
大概探索一下pyqt中实现的,QTest,包括几个部分,几个内置的类(大写开头),内置的枚举类(大写开头),内置的函数(小写开头)。
其中函数主要的作用就是模拟UI上的操作,比如键盘动作、鼠标动作、触控动作等。
下面总结了等待休眠函数(q打头)、键盘动作函数(key打头)、鼠标动作函数(mouse打头)、触控动作函数(touch打头)的函数名称和重载的参数。

UI动作函数

  1. 小写字母q打头的函数,测试UI的控制函数,包括睡眠、等待、等待窗口激活、等待窗口暴露。

    1. qSleep
      1. qSleep(ms: int)
    2. qWait
      1. qWait(ms: int)
    3. qWaitForWindowActive
      1. qWaitForWindowActive(window: QWindow, timeout: int = 5000) -> bool
      2. qWaitForWindowActive(widget: QWidget, timeout: int = 5000) -> bool
    4. qWaitForWindowExposed
      1. qWaitForWindowExposed(window: QWindow, timeout: int = 5000) -> bool
      2. qWaitForWindowExposed(widget: QWidget, timeout: int = 5000) -> bool
  2. key开头的函数

    1. keyClick

      1. keyClick(widget: QWidget, key: Qt.Key, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier,
        delay: int = -1)
      2. keyClick(widget: QWidget, key: str, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier, delay:
        int = -1)
      3. keyClick(window: QWindow, key: Qt.Key, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier,
        delay: int = -1)
      4. keyClick(window: QWindow, key: str, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier, delay:
        int = -1)
    2. keyClicks

      1. keyClicks(widget: QWidget, sequence: str, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier,
        delay: int = -1)
    3. keyEvent

      1. keyEvent(action: QTest.KeyAction, widget: QWidget, key: Qt.Key, modifier:
        Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.NoModifier, delay: int = -1)
      2. keyEvent(action: QTest.KeyAction, widget: QWidget, ascii: str, modifier:
        Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.NoModifier, delay: int = -1)
      3. keyEvent(action: QTest.KeyAction, window: QWindow, key: Qt.Key, modifier:
        Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.NoModifier, delay: int = -1)
      4. keyEvent(action: QTest.KeyAction, window: QWindow, ascii: str, modifier:
        Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.NoModifier, delay: int = -1)
    4. keyPress

      1. keyPress(widget: QWidget, key: Qt.Key, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier, delay: int = -1)
      2. keyPress(widget: QWidget, key: str, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier, delay: int = -1)
      3. keyPress(window: QWindow, key: Qt.Key, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier, delay: int = -1)
      4. keyPress(window: QWindow, key: str, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier, delay: int = -1)
    5. keyRelease

      1. keyRelease(widget: QWidget, key: Qt.Key, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier, delay: int = -1)
      2. keyRelease(widget: QWidget, key: str, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier, delay: int = -1)
      3. keyRelease(window: QWindow, key: Qt.Key, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier, delay: int = -1)
      4. keyRelease(window: QWindow, key: str, modifier: Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] =
        Qt.NoModifier, delay: int = -1)
    6. keySequence

      1. keySequence(widget: QWidget, keySequence: Union[QKeySequence, QKeySequence.StandardKey, str, int])
      2. keySequence(window: QWindow, keySequence: Union[QKeySequence, QKeySequence.StandardKey, str, int])
  3. mouse开头的函数

    1. mouseClick

      1. mouseClick(widget: QWidget, button: Qt.MouseButton, modifier:
        Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.KeyboardModifiers(), pos: QPoint = QPoint(), delay:
        int = -1)
      2. mouseClick(window: QWindow, button: Qt.MouseButton, modifier:
        Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.KeyboardModifiers(), pos: QPoint = QPoint(), delay:
        int = -1)
    2. mouseDClick

      1. mouseDClick(widget: QWidget, button: Qt.MouseButton, modifier:
        Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.KeyboardModifiers(), pos: QPoint = QPoint(), delay:
        int = -1)
      2. mouseDClick(window: QWindow, button: Qt.MouseButton, modifier:
        Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.KeyboardModifiers(), pos: QPoint = QPoint(), delay:
        int = -1)
    3. mouseMove

      1. mouseMove(widget: QWidget, pos: QPoint = QPoint(), delay: int = -1)
      2. mouseMove(window: QWindow, pos: QPoint = QPoint(), delay: int = -1)
    4. mousePress

      1. mousePress(widget: QWidget, button: Qt.MouseButton, modifier:
        Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.KeyboardModifiers(), pos: QPoint = QPoint(), delay:
        int = -1)
      2. mousePress(window: QWindow, button: Qt.MouseButton, modifier:
        Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.KeyboardModifiers(), pos: QPoint = QPoint(), delay:
        int = -1)
    5. mouseRelease

      1. mouseRelease(widget: QWidget, button: Qt.MouseButton, modifier:
        Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.KeyboardModifiers(), pos: QPoint = QPoint(), delay:
        int = -1)
      2. mouseRelease(window: QWindow, button: Qt.MouseButton, modifier:
        Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] = Qt.KeyboardModifiers(), pos: QPoint = QPoint(), delay:
        int = -1)
  4. touch开头的函数

    1. touchEvent
      1. touchEvent(widget: QWidget, device: QTouchDevice) -> QTest.QTouchEventSequence
      2. touchEvent(window: QWindow, device: QTouchDevice) -> QTest.QTouchEventSequence

内置的枚举类, KeyAction代表了按键的动作类型。

class KeyAction(int):
    Press = ...  # type: QTest.KeyAction
    Release = ...  # type: QTest.KeyAction
    Click = ...  # type: QTest.KeyAction
    Shortcut = ...  # type: QTest.KeyAction

QTouchEventSequence是触控板事件,这里不做详细介绍。

信号测试

QtTest还提供了一个信号测试的类, QTest.SignalSpy,它可以用来测试信号的触发情况。

class QSignalSpy(QtCore.QObject):

    @typing.overload
    def __init__(self, signal: pyqtBoundSignal) -> None: ...
    @typing.overload
    def __init__(self, obj: QtCore.QObject, signal: QtCore.QMetaMethod) -> None: ...

    def __delitem__(self, i: int) -> None: ...
    def __setitem__(self, i: int, value: typing.Iterable[typing.Any]) -> None: ...
    def __getitem__(self, i: int) -> typing.List[typing.Any]: ...
    def __len__(self) -> int: ...
    def wait(self, timeout: int = ...) -> bool: ...
    def signal(self) -> QtCore.QByteArray: ...
    def isValid(self) -> bool: ...    

这个类跟Qt5中的实现由一些不同。因为在Python中,列表的接口是很容易通过魔术函数实现的,所以这里的QSignalSpy类就是一个两层列表,它的元素本身也是一个列表。

这个类有两个构造函数,一个是传入一个信号,一个是传入一个QObject和信号的元方法。这两个构造函数都会返回一个QSignalSpy对象。第一个就是用于通过@pyqtSignal定义的信号,这个信号必须与一个对象绑定。

第二个构造函数也挺有意思,把绑定的对象与信号分开,在PyQt5中要获得这个QMetaMethod对象,需要通过QMetaObject类的方法来获取元对象,然后在元对象中查找信号的元方法。这里的QMetaMethod对象是通过信号的元方法来获取的。

最平凡的例子

这里用一个很无趣的例子来说明QtTest的使用方法。

import sys
from functools import partial

from PyQt5 import QtTest
from PyQt5.QtCore import pyqtSignal, QObject, Qt
from PyQt5.QtTest import QSignalSpy
from PyQt5.QtWidgets import QApplication, QPushButton


class SmokeQtTest(QObject):
    smoke = pyqtSignal(str)


def dump_signal(spy: QSignalSpy):
    print(spy.signal(), ' signal count:', len(spy))
    for i in range(len(spy)):
        print("\t", spy[i])


if __name__ == '__main__':
    app = QApplication(sys.argv)
    smoke_test = SmokeQtTest()
    spy = QtTest.QSignalSpy(smoke_test.smoke)
    smoke_test.smoke.connect(print)
    smoke_test.smoke.emit('Hello, World!')

    widget = QPushButton('Click me')
    widget.clicked.connect(partial(dump_signal, spy))

    # 直接用第一种方法就行,实在是Copilot很无聊,自动生成了第二种方法
    # button_spy = QSignalSpy(widget.clicked)
    button_spy = QSignalSpy(widget,
                            widget.metaObject().method(widget.metaObject().indexOfSignal("clicked(bool)")))
    widget.clicked.connect(partial(dump_signal, button_spy))

    widget.show()

    QtTest.QTest.qWaitForWindowExposed(widget)
    QtTest.QTest.mouseClick(widget, Qt.LeftButton)

    sys.exit(app.exec_())

程序运行后,会弹出一个按钮,点击按钮后,会打印出信号的内容。

Hello, World!
b'smoke(QString)'  signal count: 1
	 ['Hello, World!']
b'clicked(bool)'  signal count: 1
	 [False]

这里有两个SigalSpy,一个监听自定义的信号,一个监听clicked信号。后面用QTest来模拟点击按钮的事件。

unittest框架

unittest框架是Python自带的一个单元测试框架,它的使用方法跟QtTest类似,也是通过继承TestCase类来实现的。它的使用方法也是通过assertEqual、assertTrue等来实现的。

import unittest


class SmokeTest(unittest.TestCase):
    def setUp(self) -> None:
        pass

    def tearDown(self) -> None:
        pass

    def test_equal(self):
        self.assertEqual(1, 1)

    def test_true(self):
        self.assertTrue(True)

TestCase类的setUp和tearDown方法分别是在每个测试用例开始和结束时调用的。这两个方法可以用来做一些初始化和清理工作。以及测试用例的方法必须以test开头,否则unittest框架不会执行。TestCase提供了很多的断言方法,例如assertEqual、assertTrue、assertIn等,这些方法都是用来判断测试结果是否符合预期的。

运行测试用例的方法有两种,一种是通过unittest.main()来运行,另一种是通过TestSuite来运行。

if __name__ == '__main__':
    unittest.main()

这种方法会运行所有的测试用例,如果想要运行指定的测试用例,可以通过TestSuite来实现。

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTest(SmokeTest('test_equal'))
    suite.addTest(SmokeTest('test_true'))
    runner = unittest.TextTestRunner()
    runner.run(suite)

这里是手动添加了两个测试用例,然后通过TextTestRunner来运行。TextTestRunner是一个文本运行器,它会将测试结果输出到控制台。除了这个运行器,还有HTMLTestRunner、XMLTestRunner等,它们会将测试结果输出到HTML或者XML文件中。

还可以通过suite = unittest.TestLoader().loadTestsFromTestCase(SmokeTest)来加载测试用例,这样就不需要手动添加了。

以HTMLTestRunner为例,它的使用方法如下:

    # HTMLTestRunner = __import__('HTMLTestRunner')
runner = HTMLTestRunner(output='report', report_name='report', add_timestamp=True, combine_reports=True)
# unittest.TextTestRunner(verbosity=2).run(suite)
runner.run(suite)

当然首先要通过pip install html-testRunner
安装这个库。这里的output是输出的目录,report_name是报告的名称,add_timestamp是是否添加时间戳,combine_reports是是否合并报告。

然后运行python unittest_smoke.py,就会在当前目录下生成一个report目录,里面有一个report-xxxxxxxx.html文件,打开这个文件就可以看到测试结果了。

在这里插入图片描述

注意这里不要运行python -m unittest unittest_smoke.py,unittest会采用默认的运行期来运行默认的测试用例。

总结

  1. QTest的职责是模拟UI中的事件,例如鼠标点击、键盘输入等。
  2. QSignalSpy可用于监听信号的发射,然后获取信号的参数。
  3. unittest测试框架组织自动化测试。
  4. HTMLTestRunner将测试结果输出到HTML文件中。

下一章将完整编写一个PyQt5程序的测试驱动的开发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/633962.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Apache Pinot】简单聊聊前面没讲的 Deep Store 和 Cluster

背景 前面3篇文章讲解了 Pinot 用的最多的几个组件,现在就聊最后剩下的两个,一个是 Cluster,另外一个就是 Deep Store。 Cluster 其实 Cluster 比较简单,就是一个概念的集合,他说有 Server,Broker 和 Co…

代码随想录算法训练营第五十六天 | 力扣 583. 两个字符串的删除操作, 72. 编辑距离

583. 两个字符串的删除操作 题目 583. 两个字符串的删除操作 给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 解析 1.确定dp数组(dp table)以及下标的含义 …

学会这个Python库,做接口测试不是手拿把掐吗?

我们在做接口测试时,大多数返回的都是json属性,我们需要通过接口返回的json提取出来对应的值,然后进行做断言或者提取想要的值供下一个接口进行使用。 但是如果返回的json数据嵌套了很多层,通过查找需要的词,就很不方便…

三、Typora软件的介绍及安装

1、Typora软件的介绍 (1)Typora时一款Markdown编辑器和阅读器。 (2)Typora使用起来十分简洁,十分方便,可用于记录日常的笔记等。 (3)Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档。 2、Typora软件的安装 …

都说未来AI测试辅助自动化测试,难道手工测试真的要被淘汰了吗?

目录 前言 AI测试的迷思 第一个问题:AI辅助测试真的能用吗? 第二个问题:AI辅助测试已经发展到什么程度了? 第三个问题:哪些软件系统能用AI辅助测试? 总结 总结: 前言 近年来,…

FPGA实现简易的自动售货机模型

文章目录 前言一、系统设计1、模块框图2、状态机框图3、RTL视图 二、源码1.蜂鸣器驱动模块2.按键消抖模块3、PWM模块4、sale_goods模块(状态机部分)5、数码管驱动模块6、Sales(顶层模块) 三、效果四、总结五、参考资料 前言 环境: 1、Quartus18.1 2、vscode 3、板子…

华为OD机试 JavaScript 实现【简单密码】【牛客练习题 HJ21】,附详细解题思路

一、题目描述 现在有一种密码变换算法。 九键手机键盘上的数字与字母的对应: 1--1, abc--2, def--3, ghi--4, jkl--5, mno--6, pqrs--7, tuv--8 wxyz--9, 0--0,把密码中出现的小写字母都变成九键键盘对应的数字,如:a …

Python实现面向对象版学员管理系统

如有错误,敬请谅解! 此文章仅为本人学习笔记,仅供参考,如有冒犯,请联系作者删除!! 1.1需求分析 1.1.1使用面向对象编程思想完成学员管理系统的开发,具体如下: 系统要求…

城镇供水产销差问题分析与对策

城镇自来水与其它商品的经营活动一样存在着产销差,产销差的高低,直接影响着供水企业的经济效益。供水企业的经营活动中不单考虑企业的经济效益,还要考虑社会效益。产销差是客观存在的,造成产销差的原因是多样的,复杂的…

初探图神经网络——GNN

title: 图神经网络(GNN) date: tags: 随笔知识点 categories:[学习笔记] 初探图神经网络(GNN) 文章来源:https://distill.pub/2021/gnn-intro/ 前言:说一下为什么要写这篇文章,因为自己最近一直听说“图神经网络”,但是一直不了…

【LeetCode】24.两两交换链表中的节点

24.两两交换链表中的节点(中等) 方法一:递归 思路 代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), n…

聊一聊mysql的MVC

技术主题 在mysql世纪使用中,经常涉及到MVCC的概念(Multi-Vsersion Concurrency Control),即多版本并发控制,一种并发控制方法,根本目的是主为了提升数据库的并发性能。 mvcc为什么产生 数据库最原生的锁…

解开索引迷局:聚簇索引与非聚簇索引的差异大揭秘!

大家好,我是小米!今天我们来聊一聊数据库中的索引,具体地说就是聚簇索引和非聚簇索引。这两者在数据库中扮演着重要的角色,对于我们理解数据库的存储和查询机制非常有帮助。下面就让我来给大家详细解释一下它们的区别吧&#xff0…

为不同的调制方案设计一个单载波系统(映射器-信道-去映射器)(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

RPC介绍

RPC介绍 1 介绍1.1 概述1.2 RPC的分裂发展 2 历史发展1969年11月,ARPAnet 开始建立。1974年:Jon Postel 和 Jim White发表了RFC6741975年:RFC684 作为RFC 674 的注释发表,对RFC 674 的争议进行回复。1976年:RFC 707 发…

C++学习之旅 -类和对象(重点)

文章目录 封装封装的意义案例1案例2 访问权限C中class和struct的区别成员属性私有化构造函数和析构函数构造函数析构函数构造函数的分类以及调用构造&调用 拷贝构造函数调用时机深拷贝&浅拷贝初始化列表类对象作为类成员静态成员C对象模型&this指针成员变量和成员函…

Mybatis Generator源码修改

文章目录 报java.net.MalformedURLException错误解决问题原因:编译的时候没有把下面的dtd文件打进去解决方法 XML文件判空优化-增加空字符串修改InsertSelectiveElementGenerator修改UpdateByPrimaryKeySelectiveElementGenerator XML文件判空优化-最佳解决方案 报j…

一文详解!接口测试 API 自动化测试框架

目录 前言 框架定位 框架架构图 框架介绍 技术栈 Case 展示 执行展示 框架优势: 前言 接口测试 API 自动化测试框架可以提高测试效率和自动化程度,通常包括 HTTP 客户端、测试数据管理、测试报告生成、测试用例管理和调度等功能。下面是一个常用…

【论文】attention is all you need

重点在第三节 attention is all you need摘要1. 绪论2. 背景3. 模型架构3.1 编码器和解码器堆叠 3.2 注意力3.2.1 缩放点积注意力(Scaled Dot-Product Attention)3.2.2 多头注意力机制3.2.3 模型中注意力的应用 3.3 职位感知前馈网络(Positio…

单链表OJ题:LeetCode--142.环形链表Ⅱ(判断第一次入环的节点)

朋友们、伙计们,我们又见面了,本期来给大家解读一下LeetCode中第142道单链表OJ题,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! 数据结构与算法专栏:数据结构与算法 个 人…