PyQt的一个UI单元测试框架思路
專 欄
❈丁果,Python中文社区作者。对django、pyqt、opencv、tornado感兴趣。
GitHub:https://github.com/lidingke
❈
一、思路
PyQt是个 相当灵活的UI框架,不过,这个Qt的Python版本一直没有一个好用的针对UI的单元测试工具。
PyQt里的逻辑层都是采用信号槽的方式连接的,我们可以通过拦截并重建信号槽的方式,动态生成一个单元测试的脚本。按这个思路写了一个单元测试的工具。如果需要的人多的话,我就把这个模块做成一个单元测试的框架。
二、demo
一个好用的工具应该是非侵入式的,接口合理且命名规范,符合大多数人使用习惯的,我认为这样一个PyQt的单元测试用例应该长这样子。
主要的动作就是,按照源代码中按钮的信号槽连接的调用链,触发按钮的点击实践,执行点击按钮后的逻辑。
在上述代码中,首先单元测试的入口是启动界面的代码,也就是test_start_main_ui函数,这段代码是最简单的一个PyQt的界面启动代码,其中不一样的是启动了一个线程用于执行单元测试。
而单元测试的函数是这样的,首先初始化一个参数为view的实例的类,这个类用于拦截信号槽,并执行信号动作,我把这个类命名为Knife。
接下来就是执行view下面的target_button的点击事件,这一系列的成员函数是根据原始view里面的信号槽连接代码动态生成的,后面会讲具体方法。
触发点击事件后,结果显示在一个label上,assert一下这个结果是否正确就行了。
GIF是一个演示实例,QLineEdit里面输入一个数,按一下-1s的按钮(QPushButton),会在最右的label上将该数减一之后显示,Demo GUI部分的代码看这里。
三、Qt与PyQt
Qt中信号槽是个不可或缺的概念,和元对象系统之类的东西组成了Qt的基础组件。但对于起源于上古时代的Qt,这些东西很多是为了弥补当时C++的不足,对于Python这种强类型的语言来说并不是那么不可或缺,比如信号槽本质上就是观察者模式,完全可以自己实现一个,我自己的实现可以看这里。
而Qt的元对象系统是一个代码生成框架,给C++提供了自省的能力,但Python这种动态语言在语言层面上就有强大的自省功能,所以我平时用PyQt的时候一般就把它当一个UI库用,其他的东西比如线程、信号槽、串口等都用Python版本的。
四、拦截的实现
在PyQt中,信号槽连接的写法一般是这样的。
signal_instance.connect(slot_name)
所以,我这个版本的拦截信号槽的功能的实现思路就是用正则匹配源代码,从符合这一模式的
语句中解析出信号的发送端和槽函数,将槽函数重新添加进新的生成的自定义信号槽中。
信号槽重连接
之前说Python的自省能力强大,现在有个非常实际的例子就是,在Python中可以动态的获取源代码。这个功能用到的Python 标准库中的inpect库,示例如下。import inspect
print inspect.getsource(inspect.isclass)
这段代码的功能是将inspect库中的isclass函数的源代码打印出来。inspect模块是个很神奇的模块,如果你对闭包和协程不理解的话也可以调用该模块中的相应代码看看。
在程序中还用到了code.co_names这个东西,用来高效的查看函数的源代码里有没有"connect"字符串。
五、程序结构
这里是部分程序源代码,省略了代码细节,源代码可以看这个git仓库
这里用一个叫Knife的类来实现,在重建新的信号函数的时候我希望信号函数的调用方式和程序源代码里的调用方式保持一致,这里就得采用动态的生成方式。而涉及到类成员的动态生成,采取一种不一样的写法比较好,比如把生成的时间从init方法中提前到new方法中。
widget_instance就是包含信号槽的类,因为我写GUI都是采用MVC的方式,需要导出并拦截的信号槽都在一个类里面,这个类传入的时候已经是个实例了。动态解析该实例源代码,并动态生成新的信号去装载信号槽。
其中,还有个问题,有些调用可能嵌套的好几层,比如像这样。
self.mother.father.son.dog.clicked()
这样的操作需要用递归生成,就像这样。
调用链中自定义生成的节点类为SubNode,槽函数如果动态获取不到时,会返回一个自定义异常FailAttr。
具体的请看Github
六、知识点详解
这一栏列出一些特殊的知识点。
1、getattr,setattr,hasattr:动态的获取对象的方法,给一个对象动态的添加方法,判断一个对象是否含有某方法。
2、__new__
魔法方法:这个方法在__init__
之前,是真正的类初始化函数。要注意的是new方法需要返回的是类实例,就像源代码中的写法。而在__new__
方法中是使用不了实例方法的,得用staticmethod和classmethod装饰器去修饰。
3、staticmethod,classmethod:都是类方法的装饰器,只不过classmethod装饰过的成员方法第一个参数是cls,staticmethod装饰过的东西不引入这个参数,相当于一个纯函数,叫做静态方法。这里的两个函数都可以用classmethod装饰,不过parser_slots函数中用不到cls,我就用staticmethod装饰了。
4、用类方法去区别一些特殊操作,这一方式最常见的就是Django的ORM,将数据库操作和表单的定义分为类方法和成员方法。所以大家理解不了类方法和元类的时候可以去研究下Django的ORM。
5、列表生成式和正则表达式之类的就不解释了。
感觉篇幅有点长,其他的细节如果有需要的话在下一篇文章里解释。如果大家真需要,可以考虑专门搞成一个开源项目。
- SignalR 在IE中无法工作 - Internet Explorer
- SQL Server 2012 中的 Service Broker功能的一些改进或增强
- 通用日志
- 数据包络分析教程
- 用JAVA的DEA算法衡量社交媒体页面的流行度
- 如何构建智能反垃圾邮件的WordPress插件
- 【深入研究】使用RNN预测股票价格系列一
- 【深入研究】使用RNN预测股票价格系列二
- 教你用一行Python代码实现并行(附代码)
- 在美国国会图书馆标题表的SKOS上运行Apache Spark GraphX算法
- 【精选】破解波动性突破实盘系统
- 从程序员的角度看神经网络的激活功能
- 在线矩阵微积分工具,可以生成 Python/Latex 代码哦!
- 机器学习应用区块链系列(一)——如何开发一套自己的智能合约系统
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 用Python爬取自主品牌汽车,看看国产汽车究竟长什么样?(上)
- 工具包 | 使用Python绘制Sci学术期刊配图
- 深入剖析AQS和CAS,看了都说好
- 用Python爬取自主品牌汽车,看看国产汽车究竟长什么样?(下)
- 读者问:小林怎么学操作系统和计算机网络呀?
- 用Python搭建一个简单的代理池
- 用Python模拟登陆GitHub并获取信息
- APUE学习手札 编写一个与3.12节中dup2功能相同的函数,要求不调用fcntl函数,并且要有正确的出错处理
- Python教程 | 最标准的地图调用方式(国家测绘局提供数据)
- Python数据可视化:Python大佬有哪些?
- 新手一看就懂的线程池
- 「面试」破(B)站之旅
- 想过为你的应用加上skywalking(链路监控)吗?
- 结合注意力机制的UNet降水短临预报框架
- 用Python全自动下载抖音视频!