PyQt5快速开发与实战

文章目录

  • PyQt5快速开发与实战
    • 9. 第9章 PyQt5 扩展应用
      • 9.7 UI层的自动化测试
        • 9.7.1 手工测试与自动化测试
        • 9.7.2 模拟鸡尾酒的调酒器窗口
        • 9.7.3 单元测试程序
        • 9.7.4 运行测试用例
        • 9.7.5 生成测试报告

9. 第9章 PyQt5 扩展应用

9.7 UI层的自动化测试

一般来说,UI层的自动化测试是通过工具或编写脚本的方式来模拟手工测试的过程,通过运行脚本来执行测试用例,从而模拟人工对软件的功能进行验证。

PyQt是Qt框架的Python语言实现,对于单元测试,Python可以使用它内部自带的单元测试模块unittest。对于模拟手工操作,PyQt可以使用它内部的测试模块QTest。

9.7.1 手工测试与自动化测试

特点总结:

  • 手工测试由人手工去执行测试用例;自动化测试由程序代替人去执行测试用例。
  • 手工测试非常消耗时间,持续进行手工测试会使测试人员感到疲惫;自动化测试可以代替一部分机械重复的手工测试。
  • 手工测试永远无法被自动化测试取代。在整个软件开发周期中,手工测试发现 Bug所占的比例大,大约为80%;而自动化测试只能发现大约20%的 Bug。
  • 手工测试适合测试业务逻辑;自动化测试适合进行回归测试。回归测试用于测试已有功能,而不是新增功能。自动化测试有利于测试项目底层的细节,比如可以测试出软件的崩溃、API的错误返回值、业务逻辑异常和软件的内存使用等。
9.7.2 模拟鸡尾酒的调酒器窗口

ui转py

# -*- coding: utf-8 -*-# Form implementation generated from reading ui file 'MatrixWinUi.ui'
#
# Created by: PyQt5 UI code generator 5.9.2
#
# WARNING! All changes made in this file will be lost!from PyQt5 import QtCore, QtGui, QtWidgetsclass Ui_MatrixWin(object):def setupUi(self, MatrixWin):MatrixWin.setObjectName("MatrixWin")MatrixWin.resize(742, 461)self.groupBox = QtWidgets.QGroupBox(MatrixWin)self.groupBox.setGeometry(QtCore.QRect(10, 210, 451, 191))self.groupBox.setObjectName("groupBox")self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox)self.gridLayout_2.setObjectName("gridLayout_2")self.speedButton1 = QtWidgets.QRadioButton(self.groupBox)self.speedButton1.setObjectName("speedButton1")self.speedButtonGroup = QtWidgets.QButtonGroup(MatrixWin)self.speedButtonGroup.setObjectName("speedButtonGroup")self.speedButtonGroup.addButton(self.speedButton1)self.gridLayout_2.addWidget(self.speedButton1, 0, 0, 1, 1)self.speedButton3 = QtWidgets.QRadioButton(self.groupBox)self.speedButton3.setObjectName("speedButton3")self.speedButtonGroup.addButton(self.speedButton3)self.gridLayout_2.addWidget(self.speedButton3, 0, 2, 1, 1)self.speedButton4 = QtWidgets.QRadioButton(self.groupBox)self.speedButton4.setObjectName("speedButton4")self.speedButtonGroup.addButton(self.speedButton4)self.gridLayout_2.addWidget(self.speedButton4, 1, 0, 1, 1)self.speedButton5 = QtWidgets.QRadioButton(self.groupBox)self.speedButton5.setChecked(True)self.speedButton5.setObjectName("speedButton5")self.speedButtonGroup.addButton(self.speedButton5)self.gridLayout_2.addWidget(self.speedButton5, 1, 1, 1, 1)self.speedButton6 = QtWidgets.QRadioButton(self.groupBox)self.speedButton6.setObjectName("speedButton6")self.speedButtonGroup.addButton(self.speedButton6)self.gridLayout_2.addWidget(self.speedButton6, 1, 2, 1, 1)self.speedButton9 = QtWidgets.QRadioButton(self.groupBox)self.speedButton9.setObjectName("speedButton9")self.speedButtonGroup.addButton(self.speedButton9)self.gridLayout_2.addWidget(self.speedButton9, 3, 2, 1, 1)self.speedButton8 = QtWidgets.QRadioButton(self.groupBox)self.speedButton8.setObjectName("speedButton8")self.speedButtonGroup.addButton(self.speedButton8)self.gridLayout_2.addWidget(self.speedButton8, 3, 1, 1, 1)self.speedButton7 = QtWidgets.QRadioButton(self.groupBox)self.speedButton7.setObjectName("speedButton7")self.speedButtonGroup.addButton(self.speedButton7)self.gridLayout_2.addWidget(self.speedButton7, 3, 0, 1, 1)self.speedButton2 = QtWidgets.QRadioButton(self.groupBox)self.speedButton2.setObjectName("speedButton2")self.speedButtonGroup.addButton(self.speedButton2)self.gridLayout_2.addWidget(self.speedButton2, 0, 1, 1, 1)self.resultGroup = QtWidgets.QGroupBox(MatrixWin)self.resultGroup.setGeometry(QtCore.QRect(470, 210, 261, 191))self.resultGroup.setObjectName("resultGroup")self.resultText = QtWidgets.QTextEdit(self.resultGroup)self.resultText.setGeometry(QtCore.QRect(10, 20, 241, 161))self.resultText.setObjectName("resultText")self.layoutWidget = QtWidgets.QWidget(MatrixWin)self.layoutWidget.setGeometry(QtCore.QRect(10, 420, 390, 30))self.layoutWidget.setObjectName("layoutWidget")self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget)self.horizontalLayout.setContentsMargins(0, 0, 0, 0)self.horizontalLayout.setObjectName("horizontalLayout")self.okBtn = QtWidgets.QPushButton(self.layoutWidget)self.okBtn.setObjectName("okBtn")self.horizontalLayout.addWidget(self.okBtn)self.clearBtn = QtWidgets.QPushButton(self.layoutWidget)self.clearBtn.setObjectName("clearBtn")self.horizontalLayout.addWidget(self.clearBtn)self.cancelBtn = QtWidgets.QPushButton(self.layoutWidget)self.cancelBtn.setObjectName("cancelBtn")self.horizontalLayout.addWidget(self.cancelBtn)self.groupBox_2 = QtWidgets.QGroupBox(MatrixWin)self.groupBox_2.setGeometry(QtCore.QRect(10, 10, 721, 191))self.groupBox_2.setObjectName("groupBox_2")self.label = QtWidgets.QLabel(self.groupBox_2)self.label.setGeometry(QtCore.QRect(20, 30, 151, 21))self.label.setObjectName("label")self.label_2 = QtWidgets.QLabel(self.groupBox_2)self.label_2.setGeometry(QtCore.QRect(20, 60, 151, 21))self.label_2.setObjectName("label_2")self.label_7 = QtWidgets.QLabel(self.groupBox_2)self.label_7.setGeometry(QtCore.QRect(20, 90, 131, 21))self.label_7.setObjectName("label_7")self.label_4 = QtWidgets.QLabel(self.groupBox_2)self.label_4.setGeometry(QtCore.QRect(20, 120, 151, 22))self.label_4.setObjectName("label_4")self.tequilaScrollBar = QtWidgets.QScrollBar(self.groupBox_2)self.tequilaScrollBar.setEnabled(True)self.tequilaScrollBar.setGeometry(QtCore.QRect(130, 30, 361, 21))self.tequilaScrollBar.setMaximum(11)self.tequilaScrollBar.setProperty("value", 8)self.tequilaScrollBar.setSliderPosition(8)self.tequilaScrollBar.setOrientation(QtCore.Qt.Horizontal)self.tequilaScrollBar.setObjectName("tequilaScrollBar")self.tripleSecSpinBox = QtWidgets.QSpinBox(self.groupBox_2)self.tripleSecSpinBox.setGeometry(QtCore.QRect(130, 60, 250, 21))self.tripleSecSpinBox.setMaximum(11)self.tripleSecSpinBox.setProperty("value", 4)self.tripleSecSpinBox.setObjectName("tripleSecSpinBox")self.limeJuiceLineEdit = QtWidgets.QLineEdit(self.groupBox_2)self.limeJuiceLineEdit.setGeometry(QtCore.QRect(130, 90, 257, 21))self.limeJuiceLineEdit.setObjectName("limeJuiceLineEdit")self.iceHorizontalSlider = QtWidgets.QSlider(self.groupBox_2)self.iceHorizontalSlider.setGeometry(QtCore.QRect(130, 120, 250, 22))self.iceHorizontalSlider.setMinimum(0)self.iceHorizontalSlider.setMaximum(20)self.iceHorizontalSlider.setProperty("value", 12)self.iceHorizontalSlider.setOrientation(QtCore.Qt.Horizontal)self.iceHorizontalSlider.setObjectName("iceHorizontalSlider")self.label_6 = QtWidgets.QLabel(self.groupBox_2)self.label_6.setGeometry(QtCore.QRect(610, 30, 61, 21))self.label_6.setObjectName("label_6")self.label_3 = QtWidgets.QLabel(self.groupBox_2)self.label_3.setGeometry(QtCore.QRect(610, 50, 61, 21))self.label_3.setObjectName("label_3")self.label_8 = QtWidgets.QLabel(self.groupBox_2)self.label_8.setGeometry(QtCore.QRect(610, 80, 61, 21))self.label_8.setObjectName("label_8")self.label_5 = QtWidgets.QLabel(self.groupBox_2)self.label_5.setGeometry(QtCore.QRect(610, 120, 61, 21))self.label_5.setObjectName("label_5")self.selScrollBarLbl = QtWidgets.QLabel(self.groupBox_2)self.selScrollBarLbl.setGeometry(QtCore.QRect(520, 30, 51, 21))self.selScrollBarLbl.setText("")self.selScrollBarLbl.setObjectName("selScrollBarLbl")self.selIceSliderLbl = QtWidgets.QLabel(self.groupBox_2)self.selIceSliderLbl.setGeometry(QtCore.QRect(520, 120, 51, 21))self.selIceSliderLbl.setText("")self.selIceSliderLbl.setObjectName("selIceSliderLbl")self.retranslateUi(MatrixWin)self.okBtn.clicked.connect(MatrixWin.uiAccept)self.cancelBtn.clicked.connect(MatrixWin.uiReject)self.clearBtn.clicked.connect(MatrixWin.uiClear)self.iceHorizontalSlider.valueChanged['int'].connect(MatrixWin.uiIceSliderValueChanged)self.tequilaScrollBar.valueChanged['int'].connect(MatrixWin.uiScrollBarValueChanged)QtCore.QMetaObject.connectSlotsByName(MatrixWin)def retranslateUi(self, MatrixWin):_translate = QtCore.QCoreApplication.translateMatrixWin.setWindowTitle(_translate("MatrixWin", "玛格丽特鸡尾酒*调酒器"))self.groupBox.setToolTip(_translate("MatrixWin", "Speed of the blender"))self.groupBox.setTitle(_translate("MatrixWin", "9种搅拌速度"))self.speedButton1.setText(_translate("MatrixWin", "&Mix"))self.speedButton3.setText(_translate("MatrixWin", "&Puree"))self.speedButton4.setText(_translate("MatrixWin", "&Chop"))self.speedButton5.setText(_translate("MatrixWin", "&Karate Chop"))self.speedButton6.setText(_translate("MatrixWin", "&Beat"))self.speedButton9.setText(_translate("MatrixWin", "&Vaporize"))self.speedButton8.setText(_translate("MatrixWin", "&Liquefy"))self.speedButton7.setText(_translate("MatrixWin", "&Smash"))self.speedButton2.setText(_translate("MatrixWin", "&Whip"))self.resultGroup.setTitle(_translate("MatrixWin", "操作结果"))self.okBtn.setText(_translate("MatrixWin", "OK"))self.clearBtn.setText(_translate("MatrixWin", "Clear"))self.cancelBtn.setText(_translate("MatrixWin", "Cancel"))self.groupBox_2.setTitle(_translate("MatrixWin", "原料"))self.label.setText(_translate("MatrixWin", "龙舌兰酒"))self.label_2.setText(_translate("MatrixWin", "三重蒸馏酒"))self.label_7.setText(_translate("MatrixWin", "柠檬汁"))self.label_4.setText(_translate("MatrixWin", "冰块"))self.tequilaScrollBar.setToolTip(_translate("MatrixWin", "Jiggers of tequila"))self.tripleSecSpinBox.setToolTip(_translate("MatrixWin", "Jiggers of triple sec"))self.limeJuiceLineEdit.setToolTip(_translate("MatrixWin", "Jiggers of lime juice"))self.limeJuiceLineEdit.setText(_translate("MatrixWin", "12.0"))self.iceHorizontalSlider.setToolTip(_translate("MatrixWin", "Chunks of ice"))self.label_6.setText(_translate("MatrixWin", "升"))self.label_3.setText(_translate("MatrixWin", "升"))self.label_8.setText(_translate("MatrixWin", "升"))self.label_5.setText(_translate("MatrixWin", "个"))

调用主窗口

# -*- coding: utf-8 -*-import sys
from PyQt5.QtWidgets import *
from MatrixWinUi import *class CallMatrixWinUi(QWidget ):def __init__(self, parent=None):    super(CallMatrixWinUi, self).__init__(parent)self.ui = Ui_MatrixWin()self.ui.setupUi(self)self.initUi()# 初始化窗口    def initUi(self):scrollVal = self.ui.tequilaScrollBar.value()   self.ui.selScrollBarLbl.setText( str(scrollVal) )  sliderVal = self.ui.iceHorizontalSlider.value()self.ui.selIceSliderLbl.setText( str(sliderVal) )     # 获得一量杯酒的重量,单位:克def getJiggers(self):# 返回玛格丽特就得总容量,以jigger量酒器为单位。# 一个量酒器可以容纳0.0444升的酒。jiggersTequila = self.ui.tequilaScrollBar.value()jiggersTripleSec = self.ui.tripleSecSpinBox.value()jiggersLimeJuice = float(self.ui.limeJuiceLineEdit.text())jiggersIce = self.ui.iceHorizontalSlider.value()return jiggersTequila + jiggersTripleSec + jiggersLimeJuice + jiggersIce# 获得一量杯酒的体积,单位:升def getLiters(self):'''返回鸡尾酒的总容量(升)'''return 0.0444 * self.getJiggers()# 获得搅拌速度def getSpeedName(self):speedButton = self.ui.speedButtonGroup.checkedButton()if speedButton is None:return Nonereturn speedButton.text()# 点击ok按钮后,把响应的结果显示在resultText文本框里  def uiAccept(self):print('* CallMatrixWinUi accept ')print('The volume of drinks is {0} liters ({1} jiggers).'.format(self.getLiters() , self.getJiggers() ))print('The blender is running at speed "{0}"'.format(self.getSpeedName() ))msg1 = '饮料量为: {0} 升 ({1} 个量酒器)。'.format(self.getLiters() , self.getJiggers() )msg2 = '调酒器的搅拌速度是: "{0}"。'.format(self.getSpeedName() )self.ui.resultText.clear() self.ui.resultText.append(msg1)self.ui.resultText.append(msg2)# 点击cancel按钮,关闭窗口  def uiReject(self):print('* CallMatrixWinUi reject ')'''Cancel.'''self.close()# 点击clear按钮,清空操作结果       def uiClear(self):print('* CallMatrixWinUi uiClear ')self.ui.resultText.clear()    def uiScrollBarValueChanged(self):print('* uiScrollBarValueChanged ---------')pos = self.ui.tequilaScrollBar.value() self.ui.selScrollBarLbl.setText( str(pos) )       def uiIceSliderValueChanged( self):print('* uiIceSliderValueChanged ---------')pos = self.ui.iceHorizontalSlider.value()self.ui.selIceSliderLbl.setText( str(pos) )if __name__=="__main__":from pyqt5_plugins.examples.exampleqmlitem import QtCoreQtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)app = QApplication(sys.argv)  demo = CallMatrixWinUi()  demo.show()  sys.exit(app.exec_())

运行

9.7.3 单元测试程序
  1. 编写单元测试类

    class MatrixWinTest(unittest.TestCase):  # 初始化工作  def setUp(self):  print('*** setUp ***')self.app = QApplication(sys.argv)  self.form = CallMatrixWinUi.CallMatrixWinUi()self.form.show()      # 新建对象,传入参数。每5秒执行一个测试用例 TestCase。self.bkThread = BackWorkThread(int( 5 ))# 连接子进程的信号和槽函数self.bkThread.finishSignal.connect(self.closeWindow)#self.bkThread.finishSignal.connect(self.app.exec_)# 启动线程,开始执行run()函数里的内容self.bkThread.start()# 退出清理工作  def tearDown(self):  print('*** tearDown ***')self.app.exec_()
    
  2. 定时关闭窗口

    # 继承 QThread 类
    class BackWorkThread(QThread):  # 声明一个信号,同时返回一个strfinishSignal = pyqtSignal(str)# 构造函数里增加形参def __init__(self, sleepTime,parent=None):super(BackWorkThread, self).__init__(parent)# 储存参数self.sleepTime = sleepTime#重写run()函数,在里面定时执行业务。def run(self):# 休眠一段时间time.sleep(self.sleepTime)# 休眠结束,发送一个信号告诉主线程窗口self.finishSignal.emit('ok , begin to close Window')
    
  3. 测试调酒器窗口的默认值

# 测试用例-在默认状态下的测试GUI
def test_defaults(self):'''测试GUI处于默认状态'''print('*** testCase test_defaults begin ***')self.form.setWindowTitle('开始测试用例 test_defaults ')self.assertEqual(self.form.ui.tequilaScrollBar.value(), 8)self.assertEqual(self.form.ui.tripleSecSpinBox.value(), 4)self.assertEqual(self.form.ui.limeJuiceLineEdit.text(), "12.0")self.assertEqual(self.form.ui.iceHorizontalSlider.value(), 12)self.assertEqual(self.form.ui.speedButtonGroup.checkedButton().text(), "&Karate Chop")        print('*** speedName='+ self.form.getSpeedName() )# 用鼠标左键按OK    okWidget = self.form.ui.okBtnQTest.mouseClick(okWidget, Qt.LeftButton)# 即使没有按OK,Class也处于默认状态self.assertEqual(self.form.getJiggers() , 36.0)self.assertEqual(self.form.getSpeedName(), "&Karate Chop")print('*** testCase test_defaults end ***')

  1. 测试PyQt的QScrollBar

    # 设置窗口中所有部件的值为0,状态为初始状态。
    def setFormToZero(self):print('* setFormToZero *')              self.form.ui.tequilaScrollBar.setValue(0)self.form.ui.tripleSecSpinBox.setValue(0)self.form.ui.limeJuiceLineEdit.setText("0.0")self.form.ui.iceHorizontalSlider.setValue(0)self.form.ui.selScrollBarLbl.setText("0")  self.form.ui.selIceSliderLbl.setText("0")
    
    # 测试用例-测试滚动条
    def test_moveScrollBar(self):'''测试用例test_moveScrollBar'''   print('*** testCase test_moveScrollBar begin ***')self.form.setWindowTitle('开始测试用例 test_moveScrollBar ') self.setFormToZero()# 测试将龙舌兰酒的滚动条的值设定为 12 ,ui中它实际的最大值为 11self.form.ui.tequilaScrollBar.setValue( 12 )print('* 当执行self.form.ui.tequilaScrollBar.setValue(12) 后,ui.tequilaScrollBar.value() => ' + str( self.form.ui.tequilaScrollBar.value() ) )self.assertEqual(self.form.ui.tequilaScrollBar.value(), 11 )# 测试将龙舌兰酒的滚动条的值设定为 -1 ,ui中它实际的最小值为 0self.form.ui.tequilaScrollBar.setValue(-1)print('* 当执行self.form.ui.tequilaScrollBar.setValue(-1) 后,ui.tequilaScrollBar.value() => ' + str( self.form.ui.tequilaScrollBar.value() ) )self.assertEqual(self.form.ui.tequilaScrollBar.value(), 0)# 重新将将龙舌兰酒的滚动条的值设定为 5self.form.ui.tequilaScrollBar.setValue(5)# 用鼠标左键按OK按钮okWidget = self.form.ui.okBtnQTest.mouseClick(okWidget, Qt.LeftButton)self.assertEqual(self.form.getJiggers() , 5)print('*** testCase test_moveScrollBar end ***')
    

  2. 测试PyQt的QSpinBox

    # 测试用例-测试滚动条
    def test_tripleSecSpinBox(self):'''测试用例 test_tripleSecSpinBox '''  print('*** testCase test_tripleSecSpinBox begin ***')self.form.setWindowTitle('开始测试用例 test_tripleSecSpinBox ')  '''测试修改spinBox部件的最大最小值测试它的最小和最大值作为读者的练习。'''    self.setFormToZero()# tripleSecSpinBox在界面中的取值范围为 0 到 11, 将它的最大值设为 12,看是否显示正常。self.form.ui.tripleSecSpinBox.setValue(12)print('* 当执行self.form.ui.tripleSecSpinBox.setValue(12) 后,ui.tripleSecSpinBox.value() => ' + str( self.form.ui.tripleSecSpinBox.value() ) )          self.assertEqual(self.form.ui.tripleSecSpinBox.value(), 11 )   # tripleSecSpinBox在界面中的取值范围为 0 到 11, 将它的最小值设为 -1, 看是否显示正常。self.form.ui.tripleSecSpinBox.setValue(-1)print('* 当执行self.form.ui.tripleSecSpinBox.setValue(-1) 后,ui.tripleSecSpinBox.value() => ' + str( self.form.ui.tripleSecSpinBox.value() ) )          self.assertEqual(self.form.ui.tripleSecSpinBox.value(), 0 )    self.form.ui.tripleSecSpinBox.setValue(2)# 用鼠标左键按OK按钮okWidget = self.form.ui.okBtnQTest.mouseClick(okWidget, Qt.LeftButton)self.assertEqual(self.form.getJiggers(), 2)       print('*** testCase test_tripleSecSpinBox end ***')
    

  3. 测试PyQt的QLineEdit

    # 测试用例-测试柠檬汁单行文本框
    def test_limeJuiceLineEdit(self):'''测试用例 test_limeJuiceLineEdit ''' print('*** testCase test_limeJuiceLineEdit begin ***')self.form.setWindowTitle('开始测试用例 test_limeJuiceLineEdit ')       '''测试修改juice line edit部件的最大最小值测试它的最小和最大值作为读者的练习。'''self.setFormToZero()      # 清除lineEdit小部件值,然后在lineEdit小部件中键入“3.5”self.form.ui.limeJuiceLineEdit.clear()    QTest.keyClicks(self.form.ui.limeJuiceLineEdit, "3.5")# 用鼠标左键按OK按钮okWidget = self.form.ui.okBtnQTest.mouseClick(okWidget, Qt.LeftButton)self.assertEqual(self.form.getJiggers() , 3.5)print('*** testCase test_limeJuiceLineEdit end ***')
    

  4. 测试PyQt的QSlider

    # 测试用例-测试iceHorizontalSlider
    def test_iceHorizontalSlider(self):'''测试用例 test_iceHorizontalSlider '''   print('*** testCase test_iceHorizontalSlider begin ***')   self.form.setWindowTitle('开始测试用例 test_iceHorizontalSlider ')   '''测试ice slider.测试它的最小和最大值作为读者的练习。'''self.setFormToZero()self.form.ui.iceHorizontalSlider.setValue(4)# 用鼠标左键按OK按钮okWidget = self.form.ui.okBtnQTest.mouseClick(okWidget, Qt.LeftButton)self.assertEqual(self.form.getJiggers(), 4)       print('*** testCase test_iceHorizontalSlider end ***')
    

  5. 测试PyQt的QRadioButton

    def test_blenderSpeedButtons(self):print('*** testCase test_blenderSpeedButtons begin ***')      '''测试选择搅拌速度按钮'''self.form.ui.speedButton1.click()self.assertEqual(self.form.getSpeedName(), "&Mix")    self.form.ui.speedButton2.click()self.assertEqual(self.form.getSpeedName(), "&Whip")self.form.ui.speedButton3.click()self.assertEqual(self.form.getSpeedName(), "&Puree")      self.form.ui.speedButton4.click()self.assertEqual(self.form.getSpeedName(), "&Chop")self.form.ui.speedButton5.click()self.assertEqual(self.form.getSpeedName(), "&Karate Chop")    self.form.ui.speedButton6.click()self.assertEqual(self.form.getSpeedName(), "&Beat")self.form.ui.speedButton7.click()self.assertEqual(self.form.getSpeedName(), "&Smash")self.form.ui.speedButton8.click()self.assertEqual(self.form.getSpeedName(), "&Liquefy")self.form.ui.speedButton9.click()self.assertEqual(self.form.getSpeedName(), "&Vaporize")       print('*** testCase test_blenderSpeedButtons end ***')
    

9.7.4 运行测试用例
  1. 默认执行所有的测试用例

  2. 按照指定顺序执行测试用例

9.7.5 生成测试报告

使用HTMLTestRunner生成测试报告。

HTMLTestRunner.py

"""
A TestRunner for use with the Python unit testing framework. It
generates a HTML report to show the result at a glance.The simplest way to use this is to invoke its main method. E.g.import unittestimport HTMLTestRunner... define your tests ...if __name__ == '__main__':HTMLTestRunner.main()For more customization options, instantiates a HTMLTestRunner object.
HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.# output to a filefp = file('my_report.html', 'wb')runner = HTMLTestRunner.HTMLTestRunner(stream=fp,title='My unit test',description='This demonstrates the report output by HTMLTestRunner.')# Use an external stylesheet.# See the Template_mixin class for more customizable optionsrunner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'# run the testrunner.run(my_test_suite)------------------------------------------------------------------------
Copyright (c) 2004-2007, Wai Yip Tung
All rights reserved.Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:* Redistributions of source code must retain the above copyright notice,this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyrightnotice, this list of conditions and the following disclaimer in thedocumentation and/or other materials provided with the distribution.
* Neither the name Wai Yip Tung nor the names of its contributors may beused to endorse or promote products derived from this software withoutspecific prior written permission.THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""# URL: http://tungwaiyip.info/software/HTMLTestRunner.html__author__ = "Wai Yip Tung"
__version__ = "0.8.2""""
Change HistoryVersion 0.8.2
* Show output inline instead of popup window (Viorel Lupu).Version in 0.8.1
* Validated XHTML (Wolfgang Borgert).
* Added description of test classes and test cases.Version in 0.8.0
* Define Template_mixin class for customization.
* Workaround a IE 6 bug that it does not treat <script> block as CDATA.Version in 0.7.1
* Back port to Python 2.3 (Frank Horowitz).
* Fix missing scroll bars in detail log (Podi).
"""# TODO: color stderr
# TODO: simplify javascript using ,ore than 1 class in the class attribute?import datetime
import io
import sys
import time
import unittest
from xml.sax import saxutils# ------------------------------------------------------------------------
# The redirectors below are used to capture output during testing. Output
# sent to sys.stdout and sys.stderr are automatically captured. However
# in some cases sys.stdout is already cached before HTMLTestRunner is
# invoked (e.g. calling logging.basicConfig). In order to capture those
# output, use the redirectors for the cached stream.
#
# e.g.
#   >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
#   >>>class OutputRedirector(object):""" Wrapper to redirect stdout or stderr """def __init__(self, fp):self.fp = fpdef write(self, s):self.fp.write(s)def writelines(self, lines):self.fp.writelines(lines)def flush(self):self.fp.flush()stdout_redirector = OutputRedirector(sys.stdout)
stderr_redirector = OutputRedirector(sys.stderr)# ----------------------------------------------------------------------
# Templateclass Template_mixin(object):"""Define a HTML template for report customerization and generation.Overall structure of an HTML reportHTML+------------------------+|<html>                  ||  <head>                ||                        ||   STYLESHEET           ||   +----------------+   ||   |                |   ||   +----------------+   ||                        ||  </head>               ||                        ||  <body>                ||                        ||   HEADING              ||   +----------------+   ||   |                |   ||   +----------------+   ||                        ||   REPORT               ||   +----------------+   ||   |                |   ||   +----------------+   ||                        ||   ENDING               ||   +----------------+   ||   |                |   ||   +----------------+   ||                        ||  </body>               ||</html>                 |+------------------------+"""STATUS = {0: 'pass',1: 'fail',2: 'error',}DEFAULT_TITLE = 'Unit Test Report'DEFAULT_DESCRIPTION = ''# ------------------------------------------------------------------------# HTML TemplateHTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>%(title)s</title><meta name="generator" content="%(generator)s"/><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>%(stylesheet)s
</head>
<body>
<script language="javascript" type="text/javascript"><!--
output_list = Array();/* level - 0:Summary; 1:Failed; 2:All */
function showCase(level) {trs = document.getElementsByTagName("tr");for (var i = 0; i < trs.length; i++) {tr = trs[i];id = tr.id;if (id.substr(0,2) == 'ft') {if (level < 1) {tr.className = 'hiddenRow';}else {tr.className = '';}}if (id.substr(0,2) == 'pt') {if (level > 1) {tr.className = '';}else {tr.className = 'hiddenRow';}}}
}function showClassDetail(cid, count) {var id_list = Array(count);var toHide = 1;for (var i = 0; i < count; i++) {tid0 = 't' + cid.substr(1) + '.' + (i+1);tid = 'f' + tid0;tr = document.getElementById(tid);if (!tr) {tid = 'p' + tid0;tr = document.getElementById(tid);}id_list[i] = tid;if (tr.className) {toHide = 0;}}for (var i = 0; i < count; i++) {tid = id_list[i];if (toHide) {document.getElementById('div_'+tid).style.display = 'none'document.getElementById(tid).className = 'hiddenRow';}else {document.getElementById(tid).className = '';}}
}function showTestDetail(div_id){var details_div = document.getElementById(div_id)var displayState = details_div.style.display// alert(displayState)if (displayState != 'block' ) {displayState = 'block'details_div.style.display = 'block'}else {details_div.style.display = 'none'}
}function html_escape(s) {s = s.replace(/&/g,'&amp;');s = s.replace(/</g,'&lt;');s = s.replace(/>/g,'&gt;');return s;
}/* obsoleted by detail in <div>
function showOutput(id, name) {var w = window.open("", //urlname,"resizable,scrollbars,status,width=800,height=450");d = w.document;d.write("<pre>");d.write(html_escape(output_list[id]));d.write("\n");d.write("<a href='javascript:window.close()'>close</a>\n");d.write("</pre>\n");d.close();
}
*/
--></script>%(heading)s
%(report)s
%(ending)s</body>
</html>
"""# variables: (title, generator, stylesheet, heading, report, ending)# ------------------------------------------------------------------------# Stylesheet## alternatively use a <link> for external style sheet, e.g.#   <link rel="stylesheet" href="$url" type="text/css">STYLESHEET_TMPL = """
<style type="text/css" media="screen">
body        { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
table       { font-size: 100%; }
pre         { }/* -- heading ---------------------------------------------------------------------- */
h1 {font-size: 16pt;color: gray;
}
.heading {margin-top: 0ex;margin-bottom: 1ex;
}.heading .attribute {margin-top: 1ex;margin-bottom: 0;
}.heading .description {margin-top: 4ex;margin-bottom: 6ex;
}/* -- css div popup ------------------------------------------------------------------------ */
a.popup_link {
}a.popup_link:hover {color: red;
}.popup_window {display: none;position: relative;left: 0px;top: 0px;/*border: solid #627173 1px; */padding: 10px;background-color: #E6E6D6;font-family: "Lucida Console", "Courier New", Courier, monospace;text-align: left;font-size: 8pt;width: 500px;
}}
/* -- report ------------------------------------------------------------------------ */
#show_detail_line {margin-top: 3ex;margin-bottom: 1ex;
}
#result_table {width: 80%;border-collapse: collapse;border: 1px solid #777;
}
#header_row {font-weight: bold;color: white;background-color: #777;
}
#result_table td {border: 1px solid #777;padding: 2px;
}
#total_row  { font-weight: bold; }
.passClass  { background-color: #6c6; }
.failClass  { background-color: #c60; }
.errorClass { background-color: #c00; }
.passCase   { color: #6c6; }
.failCase   { color: #c60; font-weight: bold; }
.errorCase  { color: #c00; font-weight: bold; }
.hiddenRow  { display: none; }
.testcase   { margin-left: 2em; }/* -- ending ---------------------------------------------------------------------- */
#ending {
}</style>
"""# ------------------------------------------------------------------------# Heading#HEADING_TMPL = """<div class='heading'>
<h1>%(title)s</h1>
%(parameters)s
<p class='description'>%(description)s</p>
</div>""" # variables: (title, parameters, description)HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
""" # variables: (name, value)# ------------------------------------------------------------------------# Report#REPORT_TMPL = """
<p id='show_detail_line'>Show
<a href='javascript:showCase(0)'>Summary</a>
<a href='javascript:showCase(1)'>Failed</a>
<a href='javascript:showCase(2)'>All</a>
</p>
<table id='result_table'>
<colgroup>
<col align='left' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
</colgroup>
<tr id='header_row'><td>Test Group/Test case</td><td>Count</td><td>Pass</td><td>Fail</td><td>Error</td><td>View</td>
</tr>
%(test_list)s
<tr id='total_row'><td>Total</td><td>%(count)s</td><td>%(Pass)s</td><td>%(fail)s</td><td>%(error)s</td><td>&nbsp;</td>
</tr>
</table>
""" # variables: (test_list, count, Pass, fail, error)REPORT_CLASS_TMPL = r"""
<tr class='%(style)s'><td>%(desc)s</td><td>%(count)s</td><td>%(Pass)s</td><td>%(fail)s</td><td>%(error)s</td><td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>
</tr>
""" # variables: (style, desc, count, Pass, fail, error, cid)REPORT_TEST_WITH_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'><td class='%(style)s'><div class='testcase'>%(desc)s</div></td><td colspan='5' align='center'><!--css div popup start--><a class="popup_link" οnfοcus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >%(status)s</a><div id='div_%(tid)s' class="popup_window"><div style='text-align: right; color:red;cursor:pointer'><a οnfοcus='this.blur();' οnclick="document.getElementById('div_%(tid)s').style.display = 'none' " >[x]</a></div><pre>%(script)s</pre></div><!--css div popup end--></td>
</tr>
""" # variables: (tid, Class, style, desc, status)REPORT_TEST_NO_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'><td class='%(style)s'><div class='testcase'>%(desc)s</div></td><td colspan='5' align='center'>%(status)s</td>
</tr>
""" # variables: (tid, Class, style, desc, status)REPORT_TEST_OUTPUT_TMPL = r"""
%(id)s: %(output)s
""" # variables: (id, output)# ------------------------------------------------------------------------# ENDING#ENDING_TMPL = """<div id='ending'>&nbsp;</div>"""# -------------------- The end of the Template class -------------------TestResult = unittest.TestResultclass _TestResult(TestResult):# note: _TestResult is a pure representation of results.# It lacks the output and reporting ability compares to unittest._TextTestResult.def __init__(self, verbosity=1):TestResult.__init__(self)self.stdout0 = Noneself.stderr0 = Noneself.success_count = 0self.failure_count = 0self.error_count = 0self.verbosity = verbosity# result is a list of result in 4 tuple# (#   result code (0: success; 1: fail; 2: error),#   TestCase object,#   Test output (byte string),#   stack trace,# )self.result = []def startTest(self, test):TestResult.startTest(self, test)# just one buffer for both stdout and stderrself.outputBuffer = io.StringIO()stdout_redirector.fp = self.outputBufferstderr_redirector.fp = self.outputBufferself.stdout0 = sys.stdoutself.stderr0 = sys.stderrsys.stdout = stdout_redirectorsys.stderr = stderr_redirectordef complete_output(self):"""Disconnect output redirection and return buffer.Safe to call multiple times."""if self.stdout0:sys.stdout = self.stdout0sys.stderr = self.stderr0self.stdout0 = Noneself.stderr0 = Nonereturn self.outputBuffer.getvalue()def stopTest(self, test):# Usually one of addSuccess, addError or addFailure would have been called.# But there are some path in unittest that would bypass this.# We must disconnect stdout in stopTest(), which is guaranteed to be called.self.complete_output()def addSuccess(self, test):self.success_count += 1TestResult.addSuccess(self, test)output = self.complete_output()self.result.append((0, test, output, ''))if self.verbosity > 1:sys.stderr.write('ok ')sys.stderr.write(str(test))sys.stderr.write('\n')else:sys.stderr.write('.')def addError(self, test, err):self.error_count += 1TestResult.addError(self, test, err)_, _exc_str = self.errors[-1]output = self.complete_output()self.result.append((2, test, output, _exc_str))if self.verbosity > 1:sys.stderr.write('E  ')sys.stderr.write(str(test))sys.stderr.write('\n')else:sys.stderr.write('E')def addFailure(self, test, err):self.failure_count += 1TestResult.addFailure(self, test, err)_, _exc_str = self.failures[-1]output = self.complete_output()self.result.append((1, test, output, _exc_str))if self.verbosity > 1:sys.stderr.write('F  ')sys.stderr.write(str(test))sys.stderr.write('\n')else:sys.stderr.write('F')class HTMLTestRunner(Template_mixin):""""""def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):self.stream = streamself.verbosity = verbosityif title is None:self.title = self.DEFAULT_TITLEelse:self.title = titleif description is None:self.description = self.DEFAULT_DESCRIPTIONelse:self.description = descriptionself.startTime = datetime.datetime.now()def run(self, test):"Run the given test case or test suite."result = _TestResult(self.verbosity)test(result)self.stopTime = datetime.datetime.now()self.generateReport(test, result)# print >> sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime))return resultdef sortResult(self, result_list):# unittest does not seems to run in any particular order.# Here at least we want to group them together by class.rmap = {}classes = []for n,t,o,e in result_list:cls = t.__class__if not cls in rmap:rmap[cls] = []classes.append(cls)rmap[cls].append((n,t,o,e))r = [(cls, rmap[cls]) for cls in classes]return rdef getReportAttributes(self, result):"""Return report attributes as a list of (name, value).Override this to add custom attributes."""startTime = str(self.startTime)[:19]duration = str(self.stopTime - self.startTime)status = []if result.success_count: status.append('Pass %s'    % result.success_count)if result.failure_count: status.append('Failure %s' % result.failure_count)if result.error_count:   status.append('Error %s'   % result.error_count  )if status:status = ' '.join(status)else:status = 'none'return [('Start Time', startTime),('Duration', duration),('Status', status),]def generateReport(self, test, result):report_attrs = self.getReportAttributes(result)generator = 'HTMLTestRunner %s' % __version__stylesheet = self._generate_stylesheet()heading = self._generate_heading(report_attrs)report = self._generate_report(result)ending = self._generate_ending()output = self.HTML_TMPL % dict(title = saxutils.escape(self.title),generator = generator,stylesheet = stylesheet,heading = heading,report = report,ending = ending,)self.stream.write(output.encode('utf8'))def _generate_stylesheet(self):return self.STYLESHEET_TMPLdef _generate_heading(self, report_attrs):a_lines = []for name, value in report_attrs:line = self.HEADING_ATTRIBUTE_TMPL % dict(name = saxutils.escape(name),value = saxutils.escape(value),)a_lines.append(line)heading = self.HEADING_TMPL % dict(title = saxutils.escape(self.title),parameters = ''.join(a_lines),description = saxutils.escape(self.description),)return headingdef _generate_report(self, result):rows = []sortedResult = self.sortResult(result.result)for cid, (cls, cls_results) in enumerate(sortedResult):# subtotal for a classnp = nf = ne = 0for n,t,o,e in cls_results:if n == 0: np += 1elif n == 1: nf += 1else: ne += 1# format class descriptionif cls.__module__ == "__main__":name = cls.__name__else:name = "%s.%s" % (cls.__module__, cls.__name__)doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""desc = doc and '%s: %s' % (name, doc) or namerow = self.REPORT_CLASS_TMPL % dict(style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',desc = desc,count = np+nf+ne,Pass = np,fail = nf,error = ne,cid = 'c%s' % (cid+1),)rows.append(row)for tid, (n,t,o,e) in enumerate(cls_results):self._generate_report_test(rows, cid, tid, n, t, o, e)report = self.REPORT_TMPL % dict(test_list = ''.join(rows),count = str(result.success_count+result.failure_count+result.error_count),Pass = str(result.success_count),fail = str(result.failure_count),error = str(result.error_count),)return reportdef _generate_report_test(self, rows, cid, tid, n, t, o, e):# e.g. 'pt1.1', 'ft1.1', etchas_output = bool(o or e)tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)name = t.id().split('.')[-1]doc = t.shortDescription() or ""desc = doc and ('%s: %s' % (name, doc)) or nametmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL# o and e should be byte string because they are collected from stdout and stderr?if isinstance(o,str):# TODO: some problem with 'string_escape': it escape \n and mess up formating# uo = unicode(o.encode('string_escape'))# uo = o.decode('latin-1')uo = eelse:uo = oif isinstance(e,str):# TODO: some problem with 'string_escape': it escape \n and mess up formating# ue = unicode(e.encode('string_escape'))# ue = e.decode('latin-1')ue = eelse:ue = escript = self.REPORT_TEST_OUTPUT_TMPL % dict(id = tid,output = saxutils.escape(str(uo)+ue),)row = tmpl % dict(tid = tid,Class = (n == 0 and 'hiddenRow' or 'none'),style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),desc = desc,script = script,status = self.STATUS[n],)rows.append(row)if not has_output:returndef _generate_ending(self):return self.ENDING_TMPL##############################################################################
# Facilities for running tests from the command line
############################################################################### Note: Reuse unittest.TestProgram to launch test. In the future we may
# build our own launcher to support more specific command line
# parameters like test title, CSS, etc.
class TestProgram(unittest.TestProgram):"""A variation of the unittest.TestProgram. Please refer to the baseclass for command line parameters."""def runTests(self):# Pick HTMLTestRunner as the default test runner.# base class's testRunner parameter is not useful because it means# we have to instantiate HTMLTestRunner before we know self.verbosity.if self.testRunner is None:self.testRunner = HTMLTestRunner(verbosity=self.verbosity)unittest.TestProgram.runTests(self)main = TestProgram##############################################################################
# Executing this module from the command line
##############################################################################if __name__ == "__main__":main(module=None)
import unittest
import HTMLTestRunner
import time
from MatrixWinTest import MatrixWinTestif __name__ == "__main__":  now = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))  print( now )testunit = unittest.TestSuite()testunit.addTest(unittest.makeSuite(MatrixWinTest ))htmlFile = ".\\"+now+"HTMLtemplate.html"print( 'htmlFile='+ htmlFile)fp = open(htmlFile,'wb')runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u"PyQt5测试报告", description=u"用例测试情况")runner.run(testunit)fp.close()

PyQt5快速开发与实战 9.7 UI层的自动化测试相关推荐

  1. PyQt5快速开发与实战 10.1 获取城市天气预报

    PyQt5快速开发与实战 文章目录 PyQt5快速开发与实战 10. 第10章 PyQt5 实战一:经典程序开发 10.1 获取城市天气预报 10.1.1 获取天气数据 10.1.2 获取不同城市的天 ...

  2. PyQt5快速开发与实战 5.1 表格与树

    PyQt5快速开发与实战 文章目录 PyQt5快速开发与实战 5. 第5章 PyQt5 高级界面控件 5.1 表格与树 5.1.1 QTableView 5.1.2 QListView 5.1.3 Q ...

  3. PyQt5快速开发与实战 5.2 容器:装载更多的控件

    PyQt5快速开发与实战 文章目录 PyQt5快速开发与实战 5. 第5章 PyQt5 高级界面控件 5.2 容器:装载更多的控件 5.2.1 QTabWidget 5.2.2 QStackedWid ...

  4. 推荐一本书:PyQt5快速开发与实战

    PyQt5快速开发与实战 700多m 高清带书签带源码:https://u15622618.ctfile.com/fs/15622618-385580436 转载于:https://www.cnblo ...

  5. PyQt5快速开发与实战 4.5 按钮类控件 and 4.6 QComboBox(下拉列表框)

    PyQt5快速开发与实战 文章目录 PyQt5快速开发与实战 4. 第4章 PyQt5 基本窗口控件 4.5 按钮类控件 4.5.1 QAbstractButton 4.5.2 QPushButton ...

  6. python qt5 gui快速编程_现货正版 Python Qt GUI与数据可视化编程 pyqt5教程书籍 pyqt5快速开发与实战Qt5 GUI快速编程 计算机网络程序设计人民邮电出版社...

    热销单品 查看更多 > RMB:85.00 立即购买 RMB:63.50 立即购买 RMB:73.50 立即购买 RMB:49.50 立即购买 RMB:127.80 立即购买 RMB:66.00 ...

  7. PyQt5 快速开发 与 实战

    From:https://blog.csdn.net/jia666666/category_9278208.html PyQt5 信号 与 槽 信号与槽的入门应用(一):https://blog.cs ...

  8. pyqt5快速开发与实战_用云开发快速制作客户业务需求收集小程序丨实战

    导语 业务场景下,如何快速优化业务流程.及时落地创意idea?看云开发如何助力业务效率的快速提升! ▌一.导语 如何省去企业上门(现场)搜集客户需求的环节,节约企业人力和时间成本,将客户的业务定制需求 ...

  9. PyQt5快速开发与实战.pdf分享

    链接:https://pan.baidu.com/s/1Ae7SSu553I6ksjqJWDb-1w?pwd=gok1 提取码:gok1 博主在学习过程中的部分代码在这篇博客

最新文章

  1. Spring MVC笔记
  2. php 爬虫 类,php爬虫原型
  3. 一文弄懂“分布式锁”
  4. api接口怎么分批传递数据_新手上路:浅谈什么是API接口 API定义是什么
  5. github设置仓库可见性 私人仓库设置他人协作/可见
  6. mysql warning 1618_MySQL - 错误1045 - 拒绝访问
  7. 大数据---数据分析师的完整流程与知识结构体系
  8. JSON 字符串 与 java 对象的转换
  9. linux 学习6 软件包管理 资料链接
  10. Eclipse 快捷键 (应用中自己总结)
  11. android加载dex方法,[原创]分享一个快速加载dex文件的方法
  12. Unity一键修改NGUI字体的编辑器脚本
  13. adc0809工作过程C语言,ADC0809引脚图、时序图、工作流程图详解
  14. Linux查看当前时间
  15. 福利:go语言开发的sock5代理服务器
  16. layui模块显示收缩_修改layui的后台模板的左侧导航栏可以伸缩的方法
  17. 联想小新Air13高定黑使用初体验
  18. FreeRTOS原理剖析:空闲任务分析
  19. zipimport.ZipImportError: can't decompress data
  20. Photoshop CS5初学者必读(23)——应用色彩平衡

热门文章

  1. python中判断生肖和星座哪个准_生肖与星座哪个更准确:星座和属相哪个更准一些?...
  2. 设计模式学习--策略模式
  3. 洛谷1726 上白泽慧音 tarjan模板
  4. 周浩正:写给编辑人的信:创新导向
  5. mv强制覆盖 shell_Linux 使用 mv 命令重命名或移动文件
  6. 绿色创新+绿色质造,群硕OI在“2022国际绿色零碳节”上获奖
  7. zip压缩包密码破解
  8. java+单例+恶汉_Java设计模式之单例模式(恶汉式和懒汉式)
  9. MySkin仿QQ皮肤,零基础拥有漂亮的软件界面
  10. Passthrough is not supported, GL is swiftshader