Pytest UI自动化测试实战实例

环境准备

序号库/插件/工具安装命令
1确保您已经安装了python3.x
2配置python3+pycharm+selenium2开发环境
3安装pytest库pip install pytest
4安装pytest -html 报告插件pip install pytest-html
5安装pypiwin32库(用来模拟按键)pip install pypiwin32
6安装openpyxl解析excel文件库pip install openpyxl
7安装yagmail发送报告库pip install yagmail
8确保已配置火狐或谷歌浏览器及对应驱动
9确保已经正确配置好发送邮件的邮箱

项目简介

测试地址

https://mail.126.com

测试范围

1.126电子邮箱登录功能测试-验证正确帐号密码登录成功-验证错误用户名密码登录失败(有很多情况,用例里面做了充分的校验)

2.126电子邮箱添加联系人功能测试-验证正确填写必填项数据添加联系人成功-验证缺省必填项数据添加联系人失败-验证必填项字段数据格式错误添加联系人失败

3.126电子邮箱发送邮件功能测试-验证普通邮件发送成功-验证带附件邮件发送成功

项目设计

1.python编程语言设计测试脚本

2.webdriver驱动浏览器并操作页面元素

3.二次封装webdriver Api 操作方法

4.采用PageObject设计模式,设计测试业务流程

5.通过UI对象库存储页面操作元素

6.通过数据文件存储数据,读取数据,参数化测试用例并驱动测试执行

7.通过第三方插件pytest-html生成测试报告

8.通过yagmail第三方库,编写发送报告接口,测试工作完成后自动发送测试报告

代码分析目录结构

1 PytestAutoTestFrameWork2 |—|config3 |——|__init__.py4 |——|conf.py5 |——|config.ini6 |—|data7 |——|__init__.py8 |——|tcData.xlsx9 |—Page
10 |——|PageObject.py
11 |———|__init__.py
12 |———|ContactPage.py
13 |———|HomePage.py
14 |———|LoginPage.py
15 |———|SendMailPage.py
16 |——|__init__.py
17 |——|BasePage.py
18 |—|report
19 |—|TestCases
20 |——|__init__.py
21 |——|conftest.py
22 |——|test_confactCase.py
23 |——|test_loginCase.py
24 |——|test_sendMailCase.py
25 |—|util
26 |——|__init__.py
27 |——|clipboard.py
28 |——|keyboard.py
29 |——|parseConFile.py
30 |——|parseExcelFile.py
31 |——|sendMailForReport.py
32 |—|conftest.py
33 |—|pytest.ini
34 |—|RunTestCase.py

代码实现

通过126邮箱测试范围分析,我们需要通过设计剪切板,模拟键盘完成附件上传操作,因此我们首先来编写这两个方法

1 """2 ------------------------------------3 @Time : 2019/4/15 12:044 @Auth : linux超5 @File : clipboard.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import win32con
11 import win32clipboard as WC
12 
13 
14 class ClipBoard(object):
15     '''设置剪切板内容和获取剪切板内容'''
16 
17     @staticmethod
18     def getText():
19         '''获取剪切板的内容'''
20         WC.OpenClipboard()
21         value = WC.GetClipboardData(win32con.CF_TEXT)
22         WC.CloseClipboard()
23         return value
24 
25     @staticmethod
26     def setText(value):
27         '''设置剪切板的内容'''
28         WC.OpenClipboard()
29         WC.EmptyClipboard()
30         WC.SetClipboardData(win32con.CF_UNICODETEXT, value)
31         WC.CloseClipboard()
32 
33 
34 if __name__ == '__main__':
35     from selenium import webdriver
36 
37     value = 'python'
38     driver = webdriver.Firefox()
39     driver.get('http://www.baidu.com')
40     query = driver.find_element_by_id('kw')
41     ClipBoard.setText(value)
42     clValue = ClipBoard.getText()
43     query.send_keys(clValue.decode('utf-8'))

 1 """2 ------------------------------------3 @Time : 2019/4/15 12:054 @Auth : linux超5 @File : keyboard.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 
11 # 模拟按键
12 import win32api
13 import win32con
14 import time
15 
16 
17 class KeyBoard(object):
18     """模拟按键"""
19     # 键盘码
20     vk_code = {
21         'enter' : 0x0D,
22         'tab' : 0x09,
23         'ctrl' : 0x11,
24         'v' : 0x56,
25         'a' : 0x41,
26         'x' : 0x58
27     }
28 
29     @staticmethod
30     def keyDown(key_name):
31         """按下键"""
32         key_name = key_name.lower()
33         try:
34             win32api.keybd_event(KeyBoard.vk_code[key_name], 0, 0, 0)
35         except Exception as e:
36             print('未按下enter键')
37             print(e)
38 
39     @staticmethod
40     def keyUp(key_name):
41         """抬起键"""
42         key_name = key_name.lower()
43         win32api.keybd_event(KeyBoard.vk_code[key_name], 0, win32con.KEYEVENTF_KEYUP, 0)
44 
45     @staticmethod
46     def oneKey(key):
47         """模拟单个按键"""
48         key = key.lower()
49         KeyBoard.keyDown(key)
50         time.sleep(2)
51         KeyBoard.keyUp(key)
52 
53     @staticmethod
54     def twoKeys(key1, key2):
55         """模拟组合按键"""
56         key1 = key1.lower()
57         key2 = key2.lower()
58         KeyBoard.keyDown(key1)
59         KeyBoard.keyDown(key2)
60         KeyBoard.keyUp(key1)
61         KeyBoard.keyUp(key2)
62 
63 
64 if __name__ == '__main__':
65     from selenium import webdriver
66     driver = webdriver.Firefox()
67     driver.get('http://www.baidu.com')
68     driver.find_element_by_id('kw').send_keys('python')
69     KeyBoard.twoKeys('ctrl', 'a')
70     KeyBoard.twoKeys('ctrl', 'x')

通过测试项目设计,我们需要把测试数据存放在Excel文件中,把页面操作元素存在UI对象库中也就是一个配置文件,那么我们需要对Excel 和 ini文件解析,因此我们开始编写这两个方法,设计UI对象库和测试数据文件

1 """2 ------------------------------------3 @Time : 2019/4/22 16:124 @Auth : linux超5 @File : parseExcelFile.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """10 from openpyxl import load_workbook11 from config.conf import excelPath12 13 14 class ParseExcel(object):15 16     def __init__(self):17         self.wk = load_workbook(excelPath)18         self.excelFile = excelPath19 20     def getSheetByName(self, sheetName):21         """获取sheet对象"""22         sheet = self.wk[sheetName]23         return sheet24 25     def getRowNum(self, sheet):26         """获取有效数据的最大行号"""27         return sheet.max_row28 29     def getColsNum(self, sheet):30         """获取有效数据的最大列号"""31         return sheet.max_column32 33     def getRowValues(self, sheet, rowNum):34         """获取某一行的数据"""35         maxColsNum = self.getColsNum(sheet)36         rowValues = []37         for colsNum in range(1, maxColsNum + 1):38             value = sheet.cell(rowNum, colsNum).value39             if value is None:40                 value = ''41             rowValues.append(value)42         return tuple(rowValues)43 44     def getColumnValues(self, sheet, columnNum):45         """获取某一列的数据"""46         maxRowNum = self.getRowNum(sheet)47         columnValues = []48         for rowNum in range(2, maxRowNum + 1):49             value = sheet.cell(rowNum, columnNum).value50             if value is None:51                 value = ''52             columnValues.append(value)53         return tuple(columnValues)54 55     def getValueOfCell(self, sheet, rowNum, columnNum):56         """获取某一个单元格的数据"""57         value = sheet.cell(rowNum, columnNum).value58         if value is None:59             value = ''60         return value61 62     def getAllValuesOfSheet(self, sheet):63         """获取某一个sheet页的所有测试数据,返回一个元祖组成的列表"""64         maxRowNum = self.getRowNum(sheet)65         columnNum = self.getColsNum(sheet)66         allValues = []67         for row in range(2, maxRowNum + 1):68             rowValues = []69             for column in range(1, columnNum + 1):70                 value = sheet.cell(row, column).value71                 if value is None:72                     value = ''73                 rowValues.append(value)74             allValues.append(tuple(rowValues))75         return allValues76 77 78 if __name__ == '__main__':79     # excel = ParseExcel()80     # sheet = excel.getSheetByName('login')81     # print('行号:', excel.getRowNum(sheet))82     # print('列号:', excel.getColsNum(sheet))83     #84     # rowvalues = excel.getRowValues(sheet, 1)85     # columnvalues = excel.getColumnValues(sheet, 2)86     # valueofcell = excel.getValueOfCell(sheet, 1, 2)87     # allvalues = excel.getAllValuesOfSheet(sheet)88     #89     # print('第{}行数据{}'.format(1, rowvalues))90     # print('第{}列数据{}'.format(2, columnvalues))91     # print('{}{}单元格的内容{}'.format(1, 2, valueofcell))92     # print('login{}'.format(allvalues))93 94     excel = ParseExcel()95     sheet = excel.getSheetByName('mail')96     print('行号:', excel.getRowNum(sheet))97     print('列号:', excel.getColsNum(sheet))98 99     allvalues = excel.getAllValuesOfSheet(sheet)
100 
101     print('sendmail{}'.format(allvalues))

 1 """2 ------------------------------------3 @Time : 2019/4/18 10:544 @Auth : linux超5 @File : parseConFile.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import configparser
11 from config.conf import configDir
12 
13 
14 class ParseConFile(object):
15 
16     def __init__(self):
17         self.file = configDir
18         self.conf = configparser.ConfigParser()
19         self.conf.read(self.file, encoding='utf-8')
20 
21     def getAllSections(self):
22         """获取所有的section,返回一个列表"""
23         return self.conf.sections()
24 
25     def getAllOptions(self, section):
26         """获取指定section下所有的option, 返回列表"""
27         return self.conf.options(section)
28 
29     def getLocatorsOrAccount(self, section, option):
30         """获取指定section, 指定option对应的数据, 返回元祖和字符串"""
31         try:
32             locator = self.conf.get(section, option)
33             if ('->' in locator):
34                 locator = tuple(locator.split('->'))
35             return locator
36         except configparser.NoOptionError as e:
37             print('error:', e)
38         return 'error: No option "{}" in section: "{}"'.format(option, section)
39 
40     def getOptionValue(self, section):
41         """获取指定section下所有的option和对应的数据,返回字典"""
42         value = dict(self.conf.items(section))
43         return value
44 
45 
46 if __name__ == '__main__':
47     cf = ParseConFile()
48     print(cf.getAllSections())
49     print(cf.getAllOptions('126LoginAccount'))
50     print(cf.getLocatorsOrAccount('126LoginAccount', 'username'))
51     print(cf.getOptionValue('126LoginAccount'))

1 [126LoginAccount];126邮箱正确的登录账号和密码;运行用例时请更换正确的用户名和密码2 username=linuxxiaochao3 password=xiaochao115204 [HomePageElements];126邮箱首页菜单栏元素5 homePage=id->_mail_tabitem_0_3text6 mailList=id->_mail_tabitem_1_4text7 applicationCenter=id->_mail_tabitem_2_5text8 inBox=id->_mail_tabitem_3_6text9 [LoginPageElements];126邮箱登录页面的元素
10 frame=xpath->//div[@id="loginDiv"]/iframe
11 username=xpath->//input[@name="email"]
12 password=xpath->//input[@name="password"]
13 loginBtn=xpath->//a[@id="dologin"]
14 ferrorHead=xpath->//div[@class="ferrorhead"]
15 [ContactPageElements];126邮箱添加联系人页面元素
16 new_contact=xpath->//span[text()="新建联系人"]
17 name=id->input_N
18 mail=xpath->//div[@id="iaddress_MAIL_wrap"]//input[@class="nui-ipt-input"]
19 star=xpath->//span[@class="nui-chk-text"]/preceding-sibling::span/b
20 phone=xpath->//div[@id='iaddress_TEL_wrap']//input[@class='nui-ipt-input']
21 comment=id->input_DETAIL
22 commit=xpath->//span[text()='确 定']
23 tooltip=xpath->//span[text()='请正确填写邮件地址。']
24 [SendMailPageElements];126邮箱发送邮件页面元素
25 writeMail=xpath->//div[@id='dvNavContainer']//span[text()='写 信']
26 addressee=xpath->//input[@aria-label='收件人地址输入框,请输入邮件地址,多人时地址请以分号隔开']
27 subject=xpath->//input[contains(@id, '_subjectInput')]
28 iframe=xpath->//iframe[@class="APP-editor-iframe"]
29 text=xpath->/html/body
30 sendBtn=xpath->//header//span[text()='发送']
31 expect=xpath->//h1[contains(@id,'_succInfo')]
32 uploadAttachment=xpath->//div[@title="点击添加附件"]
33 delete=xpath->//a[text()='删除']
新建excel文件,分3个sheet,分别为:login,contact,mail #每个sheet中数据可自行填写,驱动测试用例执行不同的数据进行测试

login

contact

mail

数据,UI对象库,解析方法都已经有了,接下来通过PageObject模式设计编写每个页面的操作及封装126邮箱的功能,以便后续设计用例调用

1 """2 ------------------------------------3 @Time : 2019/4/20 8:454 @Auth : linux超5 @File : BasePage.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """10 import time11 from selenium.webdriver.support import expected_conditions as EC12 from selenium.webdriver.support.wait import WebDriverWait as wd13 from selenium.webdriver.common.by import By14 from selenium.common.exceptions import NoSuchWindowException, TimeoutException, \15     NoAlertPresentException, NoSuchFrameException16 from selenium import webdriver17 18 from util.clipboard import ClipBoard19 from util.keyboard import KeyBoard20 from util.parseConFile import ParseConFile21 from util.parseExcelFile import ParseExcel22 23 24 class BasePage(object):25     """26     结合显示等待封装一些selenium 内置方法27     """28     cf = ParseConFile()29     excel = ParseExcel()30 31     def __init__(self, driver, outTime=30):32         self.byDic = {33         'id': By.ID,34         'name': By.NAME,35         'class_name': By.CLASS_NAME,36         'xpath': By.XPATH,37         'link_text': By.LINK_TEXT38         }39         self.driver = driver40         self.outTime = outTime41 42     def findElement(self, by, locator):43         """44         find alone element45         :param by: eg: id, name, xpath, css.....46         :param locator: id, name, xpath for str47         :return: element object48         """49         try:50             print('[Info:Starting find the element "{}" by "{}"!]'.format(locator, by))51             element = wd(self.driver, self.outTime).until(lambda x : x.find_element(by, locator))52         except TimeoutException as t:53             print('error: found "{}" timeout!'.format(locator), t)54         except NoSuchWindowException as e:55             print('error: no such "{}"'.format(locator), e)56         except Exception as e:57             raise e58         else:59             # print('[Info:Had found the element "{}" by "{}"!]'.format(locator, by))60             return element61 62     def findElements(self, by, locator):63         """64         find group elements65         :param by: eg: id, name, xpath, css.....66         :param locator: eg: id, name, xpath for str67         :return: elements object68         """69         try:70             print('[Info:start find the elements "{}" by "{}"!]'.format(locator, by))71             elements = wd(self.driver, self.outTime).until(lambda x : x.find_element(by, locator))72         except TimeoutException as t:73             print(t)74         except NoSuchWindowException as e:75             print(e)76         except Exception as e:77             raise e78         else:79             # print('[Info:Had found the elements "{}" by "{}"!]'.format(locator, by))80             return elements81 82     def isElementExsit(self, by, locator):83         """84         assert element if exist85         :param by: eg: id, name, xpath, css.....86         :param locator: eg: id, name, xpath for str87         :return: if element return True else return false88         """89         if by.lower() in self.byDic:90             try:91                 wd(self.driver, self.outTime).\92                     until(EC.visibility_of_element_located((self.byDic[by], locator)))93             except TimeoutException:94                 print('Error: element "{}" time out!'.format(locator))95                 return False96             except NoSuchWindowException:97                 print('Error: element "{}" not exsit!'.format(locator))98                 return False99             return True
100         else:
101             print('the "{}" error!'.format(by))
102 
103     def isClick(self, by, locator):
104         """判断是否可点击,返回元素对象"""
105         if by.lower() in self.byDic:
106             try:
107                 element = wd(self.driver, self.outTime).\
108                     until(EC.element_to_be_clickable((self.byDic[by], locator)))
109             except Exception:
110                 return False
111             return element
112         else:
113             print('the "{}" error!'.format(by))
114 
115     def isAlertAndSwitchToIt(self):
116         """
117         assert alert if exsit
118         :return: alert obj
119         """
120         try:
121             re = wd(self.driver, self.outTime).until(EC.alert_is_present())
122         except NoAlertPresentException:
123             return False
124         except Exception:
125             return False
126         return re
127 
128     def switchToFrame(self, by, locator):
129         """判断frame是否存在,存在就跳到frame"""
130         print('info:switching to iframe "{}"'.format(locator))
131         if by.lower() in self.byDic:
132             try:
133                 wd(self.driver, self.outTime).\
134                     until(EC.frame_to_be_available_and_switch_to_it((self.byDic[by], locator)))
135             except TimeoutException as t:
136                 print('error: found "{}" timeout!'.format(locator), t)
137             except NoSuchFrameException as e:
138                 print('error: no such "{}"'.format(locator), e)
139             except Exception as e:
140                 raise e
141         else:
142             print('the "{}" error!'.format(by))
143 
144     def switchToDefaultFrame(self):
145         """返回默认的frame"""
146         print('info:switch back to default iframe')
147         try:
148             self.driver.switch_to.default_content()
149         except Exception as e:
150             print(e)
151 
152     def getAlertText(self):
153         """获取alert的提示信息"""
154         if self.isAlertAndSwitchToIt():
155             alert = self.isAlertAndSwitchToIt()
156             return alert.text
157         else:
158             return None
159 
160     def getElementText(self, by, locator, name=None):
161         """获取某一个元素的text信息"""
162         try:
163             element = self.findElement(by, locator)
164             if name:
165                 return element.get_attribute(name)
166             else:
167                 return element.text
168         except:
169             print('get "{}" text failed return None'.format(locator))
170             return None
171 
172     def loadUrl(self, url):
173         """加载url"""
174         print('info: string upload url "{}"'.format(url))
175         self.driver.get(url)
176 
177     def getSource(self):
178         """获取页面源码"""
179         return self.driver.page_source
180 
181     def sendKeys(self, by, locator, value=''):
182         """写数据"""
183         print('info:input "{}"'.format(value))
184         try:
185             element = self.findElement(by, locator)
186             element.send_keys(value)
187         except AttributeError as e:
188             print(e)
189 
190     def clear(self, by, locator):
191         """清理数据"""
192         print('info:clearing value')
193         try:
194             element = self.findElement(by, locator)
195             element.clear()
196         except AttributeError as e:
197             print(e)
198 
199     def click(self, by, locator):
200         """点击某个元素"""
201         print('info:click "{}"'.format(locator))
202         element = self.isClick(by, locator)
203         if element:
204             element.click()
205         else:
206             print('the "{}" unclickable!')
207 
208     def sleep(self, num=0):
209         """强制等待"""
210         print('info:sleep "{}" minutes'.format(num))
211         time.sleep(num)
212 
213     def ctrlV(self, value):
214         """ctrl + V 粘贴"""
215         print('info:pasting "{}"'.format(value))
216         ClipBoard.setText(value)
217         self.sleep(3)
218         KeyBoard.twoKeys('ctrl', 'v')
219 
220     def enterKey(self):
221         """enter 回车键"""
222         print('info:keydown enter')
223         KeyBoard.oneKey('enter')
224 
225     def waitElementtobelocated(self, by, locator):
226         """显示等待某个元素出现,且可见"""
227         print('info:waiting "{}" to be located'.format(locator))
228         try:
229             wd(self.driver, self.outTime).until(EC.visibility_of_element_located((self.byDic[by], locator)))
230         except TimeoutException as t:
231             print('error: found "{}" timeout!'.format(locator), t)
232         except NoSuchWindowException as e:
233             print('error: no such "{}"'.format(locator), e)
234         except Exception as e:
235             raise e
236 
237     def assertValueInSource(self, value):
238         """断言某个关键字是否存在页面源码中"""
239         print('info:assert "{}" in page source'.format(value))
240         source = self.getSource()
241         assert value in source, '关键字"{}"不存在源码中!'.format(value)
242 
243     def assertStringContainsValue(self, String, value):
244         """断言某段字符串包含另一个字符串"""
245         print('info:assert "{}" contains "{}"'.format(String, value))
246         assert value in String, '"{}"不包含"{}"!'.format(String, value)
247 
248 
249     @staticmethod
250     def getSheet(sheetName):
251         """获取某个sheet页的对象"""
252         sheet = BasePage.excel.getSheetByName(sheetName)
253         return sheet
254 
255 
256 if __name__ == "__main__":
257     driver = webdriver.Firefox()
258     frame = ('xpath', '//div[@id="loginDiv"]/ifram')
259     wait = BasePage(driver)
260     driver.get('https://mail.126.com/')
261     wait.switchToFrame(*frame)
262     username = wait.findElement('xpath', '//input[@name="email"]')
263     username.send_keys('账号')
264     if wait.isElementExsit('xpath', '//input[@name="password"]'):
265         wait.findElement('xpath', '//input[@name="password"]').send_keys('xiaochao11520')
266     wait.click('xpath', '//a[@id="dologin"]')

 1 """2 ------------------------------------3 @Time : 2019/4/20 12:284 @Auth : linux超5 @File : HomePage.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 from Page.BasePage import BasePage
11 
12 
13 class HomePage(BasePage):
14     # 配置文件读取元素
15     homePage = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'homePage')
16     mailList = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'mailList')
17     applicationCenter = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'applicationCenter')
18     inBox = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'inBox')
19     '''首页菜单选项'''
20     def selectMenu(self, Menu='mailList'):
21         """邮箱首页选择菜单"""
22         if Menu == 'mailList':
23             self.click(*HomePage.mailList)
24         elif Menu == 'homePage':
25             self.click(*HomePage.homePage)
26         elif Menu == 'applicationCenter':
27             self.click(*HomePage.applicationCenter)
28         elif Menu == 'inBox':
29             self.click(*HomePage.inBox)
30         else:
31             raise ValueError('''
32             菜单选择错误!
33             homePage->首页
34             mailList->通讯录
35             applicationCenter->应用中心
36             inBox->收件箱''')
37 
38 if __name__=='__main__':
39     from selenium import webdriver
40     from Page.PageObject.LoginPage import LoginPage
41     driver = webdriver.Firefox()
42     login = LoginPage(driver)
43     login.login('账号', 'xiaochao11520')
44 
45     home = HomePage(driver)
46     home.selectMenu()

 1 """2 ------------------------------------3 @Time : 2019/4/20 12:284 @Auth : linux超5 @File : LoginPage.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 from Page.BasePage import BasePage
11 
12 
13 class LoginPage(BasePage):
14 
15     # 配置文件读取元素
16     frame = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'frame')
17     username = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'username')
18     password = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'password')
19     loginBtn = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'loginBtn')
20     ferrorHead = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'ferrorHead')  # 登录失败提示
21 
22     def login(self, userName, passWord):
23         '''登录'''
24         print('-------staring login-------')
25         self.loadUrl('https://mail.126.com')
26         self.switchToFrame(*LoginPage.frame)
27         self.clear(*LoginPage.username)
28         self.sendKeys(*LoginPage.username, userName)
29         self.clear(*LoginPage.password)
30         self.sendKeys(*LoginPage.password, passWord)
31         self.click(*LoginPage.loginBtn)
32         self.switchToDefaultFrame()
33         print('---------end login---------')
34 
35     # add at 2019/04/19
36     def assertTextEqString(self, expected, name = None):
37         '''断言提示信息是否与期望的值相等'''
38         self.switchToFrame(*LoginPage.frame)
39         text = self.getElementText(*LoginPage.ferrorHead, name)
40         self.switchToDefaultFrame()
41         print('info: assert "{}" == "{}"'.format(text, expected))
42         assert text == expected, '{} != {}'.format(text, expected)
43 
44 if __name__=="__main__":
45     from selenium import webdriver
46     driver = webdriver.Firefox()
47     login = LoginPage(driver, 30)
48     login.login('lin', '')
49     login.assertTextEqString('请输入密码')

 1 """2 ------------------------------------3 @Time : 2019/4/20 12:294 @Auth : linux超5 @File : ContactPage.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 from Page.BasePage import BasePage
11 
12 
13 class ContactPage(BasePage):
14     # 配置文件读取元素
15     new_contact = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'new_contact')
16     name = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'name')
17     mail = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'mail')
18     star = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'star')
19     phone = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'phone')
20     comment = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'comment')
21     commit = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'commit')
22     errortip = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'tooltip')  # 错误提示
23 
24     def newContact(self, Name, Mail, Star, Phone, Comment):
25         """添加联系人"""
26         print('--------string add contact--------')
27         self.click(*ContactPage.new_contact)
28         self.sendKeys(*ContactPage.name, Name)
29         self.sendKeys(*ContactPage.mail, Mail)
30         if Star == '1':
31             self.click(*ContactPage.star)
32         self.sendKeys(*ContactPage.phone, Phone)
33         self.sendKeys(*ContactPage.comment, Comment)
34         self.click(*ContactPage.commit)
35         print('--------end add contact--------')
36 
37     def assertErrorTip(self, excepted):
38         """断言联系人添加失败时是否有提示信息"""
39         text = self.getElementText(*ContactPage.errortip)
40         print('info: assert "{}"=="{}"'.format(text, excepted))
41         assert text == excepted
42 
43 if __name__ == '__main__':
44     from selenium import webdriver
45     from Page.PageObject.LoginPage import LoginPage
46     from Page.PageObject.HomePage import HomePage
47     driver = webdriver.Firefox()
48     home = HomePage(driver)
49     login = LoginPage(driver)
50     contact = ContactPage(driver)
51 
52     login.login('账号', 'xiaochao11520')
53     home.selectMenu()
54     contact.newContact('281754041@qq.com')

 1 """2 ------------------------------------3 @Time : 2019/4/20 9:164 @Auth : linux超5 @File : SendMailPage.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 from Page.BasePage import BasePage
11 
12 
13 class SendMailPage(BasePage):
14     # 配置文件读取元素
15     writeMail = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'writeMail')
16     addressee = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'addressee')
17     subject = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'subject')
18     iframe = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'iframe')
19     text = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'text')
20     sendBtn = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'sendBtn')
21     expect = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'expect')
22     uploadAttachment = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'uploadAttachment')
23     delete = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'delete')
24 
25     def sendMail(self, Address, Subject, Text, PFA=''):
26         """发送邮件功能"""
27         print('------------string send mail---------------------')
28         self.click(*SendMailPage.writeMail)
29         self.sendKeys(*SendMailPage.addressee, Address)
30         self.sendKeys(*SendMailPage.subject, Subject)
31         self.switchToFrame(*SendMailPage.iframe)
32         self.sendKeys(*SendMailPage.text, Text)
33         self.switchToDefaultFrame()
34         if PFA:
35             self.click(*SendMailPage.uploadAttachment)
36             self.ctrlV(PFA)
37             self.enterKey()
38             self.waitElementtobelocated(*SendMailPage.delete)
39         self.click(*SendMailPage.sendBtn)
40         print('------------end send mail---------------------')
41 
42 if __name__=='__main__':
43     from Page.PageObject.LoginPage import LoginPage
44     from selenium import webdriver
45     driver = webdriver.Firefox()
46 
47     login = LoginPage(driver)
48     login.login('账号', 'xiaochao11520')
49     sendMail = SendMailPage(driver)
50     sendMail.sendMail('281754043@qq.com', 'pytest', 'pytest实战实例', 1, 'D:\KeyWordDriverTestFrameWork\geckodriver.log')

所有的准备工作都已经做好了,还有一个问题,我们的添加联系人和发送邮件应该是否应该在已经登录的前提下测试呢?答案是肯定的。所以我们在用例同目录下新建conftest.py文件并调用登录功能

1 """2 ------------------------------------3 @Time : 2019/4/20 15:104 @Auth : linux超5 @File : conftest.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import pytest
11 from Page.PageObject.LoginPage import LoginPage
12 
13 
14 # 从配置文件中获取正确的用户名和密码
15 userName = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'username')
16 passWord = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'password')
17 @pytest.fixture(scope='function')
18 def login(driver):
19     '''除登录用例,每一个用例的前置条件'''
20     print('------------staring login------------')
21     loginFunc = LoginPage(driver, 30)
22     loginFunc.login(userName, passWord)
23     yield
24     print('------------end login------------')
25     driver.delete_all_cookies()

ok,开始编写测试用例啦

1 """2 ------------------------------------3 @Time : 2019/4/20 14:104 @Auth : linux超5 @File : test_loginCase.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import pytest
11 from Page.PageObject.LoginPage import LoginPage
12 
13 
14 @pytest.mark.loginTest
15 class TestLogin(object):
16 
17     # 测试数据
18     loginSheet = LoginPage.getSheet('login')
19     data = LoginPage.excel.getAllValuesOfSheet(loginSheet)
20 
21     # 正确的帐号和密码
22     userName = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'username')
23     passWord = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'password')
24 
25     @pytest.fixture()
26     def teardown_func(self, driver):
27         """
28         执行每个用例之后要清除一下cookie,
29         否则你第一个账号登录之后,重新加载网址还是登录状态,无法测试后面的账号
30         """
31         yield
32         driver.delete_all_cookies()
33 
34     @pytest.mark.parametrize('username, password, expect', data)
35     def test_login(self, teardown_func, driver, username, password, expect):
36         """测试登录"""
37         login = LoginPage(driver, 30)
38         login.login(username, password)
39         login.sleep(5)
40         # 增加登录失败时, 对提示信息的验证
41         if username == TestLogin.userName and password == TestLogin.passWord:
42             login.assertValueInSource(expect)
43         elif username == '':
44             login.assertTextEqString(expect)
45         elif username != '' and password == '':
46             login.assertTextEqString(expect)
47         elif username == '' and password == '':
48             login.assertTextEqString(expect)
49         else:
50             login.assertTextEqString(expect)
51 
52 
53 if __name__ == "__main__":
54     pytest.main(['-v', 'test_loginCase.py'])

1 """2 ------------------------------------3 @Time : 2019/4/20 16:154 @Auth : linux超5 @File : test_contactCase.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import re
11 import pytest
12 from Page.PageObject.HomePage import HomePage
13 from Page.PageObject.ContactPage import ContactPage
14 
15 
16 @pytest.mark.conatctTest
17 class TestAddContact(object):
18 
19     # 测试数据
20     contactSheet = ContactPage.getSheet('contact')
21     data = ContactPage.excel.getAllValuesOfSheet(contactSheet)
22 
23     @pytest.mark.newcontact
24     @pytest.mark.parametrize('Name, Mail, Star, Phone, Comment, expect', data)
25     def test_NewContact(self, driver, login, Name, Mail, Star, Phone, Comment, expect):
26         """测试添加联系人"""
27         home_page = HomePage(driver)
28         contact_page = ContactPage(driver)
29         home_page.selectMenu()
30         contact_page.newContact(Name, Mail, Star, Phone, Comment)
31         home_page.sleep(5)
32         # 校验错误的邮箱是否提示信息正确
33         if re.match(r'^.{1,}@[0-9a-zA-Z]{1,13}\..*$', Mail):
34             contact_page.assertValueInSource(expect)
35         else:
36             contact_page.assertErrorTip(expect)
37 
38 if __name__ == '__main__':
39     pytest.main(['-v', 'test_contactCase.py'])

 1 """2 ------------------------------------3 @Time : 2019/4/20 10:044 @Auth : linux超5 @File : test_sendMailCase.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import pytest
11 from Page.PageObject.SendMailPage import SendMailPage
12 
13 @pytest.mark.sendMailTest
14 class TestSendMail(object):
15 
16     sendMailSheet = SendMailPage.getSheet('mail')
17     data = SendMailPage.excel.getAllValuesOfSheet(sendMailSheet)
18 
19     @pytest.mark.sendmail
20     @pytest.mark.parametrize('Address, Subject, Text, PFA', data)
21     def test_sendMail(self, driver, login, Address, Subject, Text,PFA):
22         """测试发送邮件,包括带附件的邮件"""
23         send_mail = SendMailPage(driver)
24         send_mail.sendMail(Address, Subject, Text, PFA)
25         send_mail.sleep(5)
26         assert send_mail.isElementExsit(*SendMailPage.expect)
27 
28 if __name__=='__main__':
29     pytest.main(['-v', 'test_sendMailCase.py'])

问题

用例已经写完了,有两个问题

1.有没有发现我们的报告怎么生成的?也没有失败用例截图?

2.我们貌似并没有编写驱动浏览器的代码?

现在我们来解决这个两个问题根据pytest的conftest.py文件的原理,我们可以把驱动浏览器的代码写在一个全局的conftest.py文件里面。报告生成其实是通过命令 pytest --html=‘report.html’ --self-contained-html生成的,但是这样的报告对用例的描述不是很清晰,且没有对失败用例截图,也不方便我们分析项目的缺陷,我们也可以填写代码放到这个文件里面

1 """2 ------------------------------------3 @Time : 2019/4/12 14:104 @Auth : linux超5 @File : conftest.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import pytest
11 from selenium import webdriver
12 from py._xmlgen import html
13 
14 _driver = None
15 # 测试失败时添加截图和测试用例描述(用例的注释信息)
16 
17 @pytest.mark.hookwrapper
18 def pytest_runtest_makereport(item):
19     """
20     当测试失败的时候,自动截图,展示到html报告中
21     :param item:
22     """
23     pytest_html = item.config.pluginmanager.getplugin('html')
24     outcome = yield
25     report = outcome.get_result()
26     extra = getattr(report, 'extra', [])
27 
28     if report.when == 'call' or report.when == "setup":
29         xfail = hasattr(report, 'wasxfail')
30         if (report.skipped and xfail) or (report.failed and not xfail):
31             file_name = report.nodeid.replace("::", "_")+".png"
32             screen_img = _capture_screenshot()
33             if file_name:
34                 html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:600px;height:300px;" ' \
35                        'onclick="window.open(this.src)" align="right"/></div>' % screen_img
36                 extra.append(pytest_html.extras.html(html))
37         report.extra = extra
38         report.description = str(item.function.__doc__)
39         report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape")
40 
41 
42 @pytest.mark.optionalhook
43 def pytest_html_results_table_header(cells):
44     cells.insert(1, html.th('Description'))
45     cells.insert(2, html.th('Test_nodeid'))
46     cells.pop(2)
47 
48 
49 @pytest.mark.optionalhook
50 def pytest_html_results_table_row(report, cells):
51     cells.insert(1, html.td(report.description))
52     cells.insert(2, html.td(report.nodeid))
53     cells.pop(2)
54 
55 
56 def _capture_screenshot():
57     """
58     截图保存为base64
59     :return:
60     """
61     return _driver.get_screenshot_as_base64()
62 # 这里我设置的级别是模块级别,也就是每个测试文件运行一次
63 # 可以设置为session,全部用例执行一次,但是针对126邮箱的话
64 # 登录次数太多会叫你验证,如果验证就没法执行用例了,我没有对验证处理(处理比较复杂)
65 
66 
67 @pytest.fixture(scope='module')
68 def driver():
69     global _driver
70     print('------------open browser------------')
71     _driver = webdriver.Firefox()
72 
73     yield _driver
74     print('------------close browser------------')
75     _driver.quit()

最后呢,为了减小项目维护成本,我们把一些全局的配置项,放到我们的功能配置文件中共全局使用,包括运行用例的一些命令字符串,可以自行修改

1 """2 ------------------------------------3 @Time : 2019/4/20 16:504 @Auth : linux超5 @File : conf.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 from datetime import datetime
11 import os
12 # 项目根目录
13 projectDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
14 # 报告目录
15 reportDir = os.path.join(projectDir, 'report')
16 # ui对象库config.ini文件所在目录
17 configDir = os.path.join(projectDir, 'config', 'config.ini')
18 # 测试数据所在目录
19 excelPath = os.path.join(projectDir, 'data', 'tcData.xlsx')
20 # 当前时间
21 currentTime = datetime.now().strftime('%H_%M_%S')
22 
23 # 邮件配置信息
24 # 邮件服务器
25 smtpServer = 'smtp.qq.com'
26 # 发送者
27 fromUser = '账号@qq.com'
28 # 发送者密码
29 fromPassWord = 'mhxvqpewblldbjhf'
30 # 接收者
31 toUser = ['账号@qq.com']# 可以同时发送给多人,追加到列表中
32 # 邮件标题
33 subject = 'xx项目自动化测试报告'
34 # 邮件正文
35 contents = '测试报告正文'
36 # 报告名称
37 htmlName = r'{}\testReport{}.html'.format(reportDir, currentTime)
38 
39 # 脚本执行命令
40 args = r'pytest --html=' + htmlName+ ' ' + '--self-contained-html'
41 # modify by linuxchao at 2019/4/25
42 args_login = r'pytest --html='+ htmlName+ ' ' + '-m' + ' ' + 'loginTest'+ ' --self-contained-html'
43 args_contact = r'pytest --html='+ htmlName+ ' ' + '-m' + ' ' + 'contactTest'+ ' --self-contained-html'
44 args_sendmail = r'pytest --html='+ htmlName+ ' ' + '-m' + ' ' + 'sendMailTest'+ ' --self-contained-html'

运行项目

通过命令运行

1.cmd切换到项目的根目录,执行pytest --html=‘report.html’ --self-contained-html命令(此运行方式,无法发送测试报告邮件)这种方式感觉有点low,我们换另外一种方式,可以通过os模块自动执行相关命令,编写运行用例代码

1 """2 ------------------------------------3 @Time : 2019/4/15 16:144 @Auth : linux超5 @File : RunTestCase.py6 @IDE  : PyCharm7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!8 ------------------------------------9 """
10 import sys
11 sys.path.append('.')
12 from config.conf import *
13 from util.sendMailForReprot import SendMailWithReport
14 
15 
16 def main():
17     # 判断项目的根目录是否在sys.path中,没有就添加
18     if projectDir not in sys.path:
19         sys.path.append(projectDir)
20     # 执行用例
21     os.system(args)
22     # 发送邮件
23     SendMailWithReport.send_mail(
24         smtpServer, fromUser, fromPassWord,
25         toUser, subject, contents,
26         htmlName)
27 
28 
29 if __name__ == '__main__':
30     main()

我们可以直接执行这个文件执行所用的测试用例了!

其实我们运行用例往往不只是 使用pytest --html=‘report.html’ --self-contained-html 这样一个简单的命令运行,通常会添加很多的命令选项,比如-v,-q,-s等等,那么怎么办呢?这时候就用到了pytest.ini配置文件了

只添加了几个简单的命令选项

1 [pytest]
2 addopts=-vqs
3 testpaths=./TestCases
4 markers=
5     loginTest: Run login test cases
6     contactTest: Run add contact test cases
7     sendMailTest: Run send mail test cases

测试输出

1.自动生成html格式报告,其中报告里面附带用例执行日志明细,及用例失败自动截图(部分报告展示)

2.自动发送测试邮件给指定用户

项目源码

这可能是B站最详细的pytest自动化测试框架教程,整整100小时,全程实战!!!

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

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

相关文章

R语言使用surveyCV包对NHANES数据(复杂调查加权数据)进行10折交叉验证

美国国家健康与营养调查&#xff08; NHANES, National Health and Nutrition Examination Survey&#xff09;是一项基于人群的横断面调查&#xff0c;旨在收集有关美国家庭人口健康和营养的信息。 地址为&#xff1a;https://wwwn.cdc.gov/nchs/nhanes/Default.aspx 既往咱们…

Linux 服务器 Oracle19C安装

原文:【精选】Oracle | CentOS7安装Oracle19c数据库(RPM包)_oracle-database-preinstall-19c-1.0-1.el7.x86_64.rp_Thorolds Deer的博客-CSDN博客 下载 第一个软件包:Oracle Database 19c Download for Linux x86-64 第二个包:Oracle Linux 7 (x86_64) Latest | Oracle,…

免费(daoban)gpt,同时去除广告

一. 内容简介 免费(daoban)gpt&#xff0c;同时去除广告&#xff0c;https://chat18.aichatos.xyz/&#xff0c;也可当gpt用&#xff0c;就是有点广告&#xff0c;大家也可以支持一下 二. 软件环境 2.1 Tampermonkey 三.主要流程 3.1 创建javascript脚本 点击添加新脚本 …

香港服务器不稳定的几种情况

​  近年来&#xff0c;随着互联网的迅猛发展&#xff0c;香港作为一个重要的网络枢纽地区&#xff0c;扮演着连接中国内地和国际网络的重要角色。一些用户表示在使用香港服务器时可能会遇到不稳定的情况&#xff0c;导致访问困难、加载缓慢甚至无法连接。 为什么香港服务器会…

uni-app 开发的H5 定位功能部署注意事项

一、H5部署的时候&#xff0c;如果设计到定位功能&#xff0c;需要注意以下几点 1、打包部署的时候需要在Web配置-定位和地图里面勾选一个地图&#xff0c;并配置key 2、打包部署需要域名是https协议的&#xff0c;大多数现代浏览器要求在HTTPS协议下才能够访问地理位置信息&a…

CentOS 搭建 Hadoop3 高可用集群

Hadoop FullyDistributed Mode 完全分布式 spark101spark102spark103192.168.171.101192.168.171.102192.168.171.103namenodenamenodejournalnodejournalnodejournalnodedatanodedatanodedatanodenodemanagernodemanagernodemanagerrecource managerrecource managerjob hist…

【Spring Boot】发送邮件功能

发送邮件功能 一.pom.xml文件添加邮件依赖二.发送邮件信息&#xff08;1&#xff09;固定配置在application.yml&#xff08;2&#xff09;发送邮箱配成活&#xff08;3&#xff09;底层发送邮件方法&#xff08;4&#xff09;QQ邮箱开通smtp服务&#xff08;5&#xff09;网易…

低代码平台如火如荼,告诉我它具体能做什么?

目录 一、前言 二、低代码平台 三、低代码平台的优劣 四、低代码能解决哪些问题&#xff1f; 五、好用且强大的低代码平台 六、结语 一、前言 目前低代码平台如火如荼。这一新兴技术为企业提供了一种高效、灵活、快速开发应用程序的方法&#xff0c;并在短时间内取得了巨大成功…

体验SOLIDWORKS钣金切口工具增强 硕迪科技

在工业生产制造中&#xff0c;钣金加工是一种常用的加工方式&#xff0c;在SOLIDWORKS2024新版本中&#xff0c;钣金切口工具再次增强了&#xff0c;从SOLIDWORKS 2024 开始&#xff0c; 您可以使用切口工具在空心或薄壁圆柱体和圆锥体中生成切口。 只需在现有空心或薄壁圆柱体…

Vue监听事件

一、问题场景 项目有个需求&#xff0c;在登录页面&#xff0c;输入好账号密码后&#xff0c;直接可以点击回车就能够登录&#xff0c;效果和点击登录按钮一样&#xff0c;登录页面源码如下 <template><body id"poster"><el-form class"login-…

基于ThinkPHP+MySQL实现的通用的PHP网站后台管理系统

caozha-admin 后台管理框架 1.8.3 caozha-admin是一个通用的PHP网站后台管理框架&#xff0c;基于开源的ThinkPHP开发&#xff0c;特点&#xff1a;易上手&#xff0c;零门槛&#xff0c;界面清爽极简&#xff0c;极便于二次开发。 基础功能 1、系统设置 2、管理员管理 3、…

金蝶云星空任意文件上传漏洞复现(0day)

0x01 产品简介 金蝶云星空是一款云端企业资源管理&#xff08;ERP&#xff09;软件&#xff0c;为企业提供财务管理、供应链管理以及业务流程管理等一体化解决方案。金蝶云星空聚焦多组织&#xff0c;多利润中心的大中型企业&#xff0c;以 “开放、标准、社交”三大特性为数字…

大数据与健康:技术助力医疗卫生事业腾飞

大数据与健康&#xff1a;技术助力医疗卫生事业腾飞 随着科技的飞速发展&#xff0c;大数据技术已经渗透到我们生活的方方面面&#xff0c;包括医疗卫生领域。本文将对大数据在健康医疗领域的应用进行分析&#xff0c;并通过数据图表展示其发展趋势和前景。 一、背景介绍 近…

YOLOV8-gradcam 热力图可视化 即插即用 不需要对源码做任何修改!

YOLOV8 GradCam 热力图可视化. 本文给大家带来yolov8-gradcam热力图可视化&#xff0c;这个可视化是即插即用&#xff0c;不需要对源码做任何修改喔&#xff01;给您剩下的不少麻烦&#xff01; 代码链接&#xff1a;yolo-gradcam 里面还有yolov5和v7的热力图可视化代码&#…

MathType7.6最新免费Offic数学公式编辑器插件

一直以来&#xff0c;如何在文档中优雅地输入数学公式就是一个难题&#xff0c;虽然 Office 中有自带的数学公式编辑器&#xff0c;但是要实现一些高级的编辑操作是非常困难的&#xff0c;比如公式编号&#xff0c;复杂公式编辑&#xff0c;改变公式字体字号&#xff0c;在很长…

第2篇 机器学习基础 —(4)k-means聚类算法

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。聚类算法是一种无监督学习方法&#xff0c;它将数据集中的对象分成若干个组或者簇&#xff0c;使得同一组内的对象相似度较高&#xff0c;不同组之间的对象相似度较低。聚类算法可以用于数据挖掘、图像分割、文本分类等领域…

在Qt中解决opencv的putText函数无法绘制中文的一种解决方法

文章目录 1.问题2.查阅资料3.解决办法 1.问题 在opencv中&#xff0c;假如直接使用putText绘制中文&#xff0c;会在图像上出现问号&#xff0c;如下图所示&#xff1a; 2.查阅资料 查了一些资料&#xff0c;说想要解决这个问题&#xff0c;需要用到freetype库或者用opencv…

这个超实用的门禁技巧,让办公楼安全更简单高效!

门禁监控是现代社会中不可或缺的一部分&#xff0c;用于确保安全和管理进出某个区域的人员。随着科技的不断发展&#xff0c;门禁监控已经远离了传统的机械锁和钥匙&#xff0c;变得更加智能化和高效。 客户案例 企业办公大楼 无锡某大型企业在其办公大楼内部部署了泛地缘科技…

我在Vscode学OpenCV 基本的加法运算

根据上一篇我们可知__图像的属性 链接&#xff1a;《我在Vscode学OpenCV 处理图像》 属性— API 形状 img.shape 图像大小 img.size 数据类型 img.dtype  shape&#xff1a;如果是彩色图像&#xff0c;则返回包含行数、列数、通道数的数组&#xff1b;如果是二值图像或者灰度…

建议收藏《2023华为海思实习笔试-数字芯片真题+解析》(附下载)

华为海思一直以来是从业者想要进入的热门公司。但是岗位就那么多&#xff0c;在面试的时候&#xff0c;很多同学因为准备不充分&#xff0c;与岗位失之交臂&#xff0c;无缘进入该公司。今天为大家带来《2023华为海思实习笔试-数字芯片真题解析》题目来源于众多网友对笔试的记录…