🍅?點擊文末小卡片?,免費獲取軟件測試全套資料,資料在手,漲薪更快??
1、測試框架簡介
1)測試框架的優點
- 代碼復用率高,如果不使用框架的話,代碼會顯得很冗余。
- 可以組裝日志、報告、郵件等一些高級功能。
- 提高元素等數據的可維護性,元素發生變化時,只需要更新一下配置文件。
- 使用更靈活的PageObject設計模式。
2)測試框架的整體目錄
【注意】 init.py 文件用以標識此目錄為一個python包。
2、首先時間管理
首先,因為很多的模塊都會用到當前時間的時間戳,或者日期等等字符串,所以先單獨把時間操作(我們需要獲取的不同格式的時間信息)封裝成一個模塊。
然后讓其他模塊來調用即可。在 utils 目錄新建 times.py 模塊。
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import time
import datetime
from functools import wrapsdef timestamp():"""時間戳"""return time.time()def dt_strftime(fmt="%Y%m"):"""datetime格式化時間:param fmt "%Y%m%d %H%M%S"""return datetime.datetime.now().strftime(fmt)def sleep(seconds=1.0):"""睡眠時間"""time.sleep(seconds)def running_time(func):"""函數運行時間"""@wraps(func)def wrapper(*args, **kwargs):start = timestamp()res = func(*args, **kwargs)print("校驗元素done!用時%.3f秒!" % (timestamp() - start))return resreturn wrapperif __name__ == '__main__':print(dt_strftime("%Y%m%d%H%M%S"))
3、添加配置文件
1)conf.py
UI自動化測試框架中應該有一個文件對整體的目錄進行管理;
在項目中的 config 目錄創建 conf.py 文件,所有的目錄配置信息寫在這個文件里面。
import os
from selenium.webdriver.common.by import By
from utils.times import dt_strftimeclass ConfigManager(object):# 項目目錄BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))# 頁面元素目錄ELEMENT_PATH = os.path.join(BASE_DIR, 'page_element')# 報告文件REPORT_FILE = os.path.join(BASE_DIR, 'report.html')# 元素定位的類型LOCATE_MODE = {'css': By.CSS_SELECTOR,'xpath': By.XPATH,'name': By.NAME,'id': By.ID,'class': By.CLASS_NAME}# 郵件信息EMAIL_INFO = {'username': 'xxxxxxx@qq.com', # 切換成你自己的地址'password': 'QQ郵箱授權碼','smtp_host': 'smtp.qq.com','smtp_port': 465}# 收件人ADDRESSEE = ['xxxxxxxx@qq.com',]@propertydef log_file(self):"""日志目錄"""log_dir = os.path.join(self.BASE_DIR, 'logs')if not os.path.exists(log_dir):os.makedirs(log_dir)return os.path.join(log_dir, '{}.log'.format(dt_strftime()))@propertydef ini_file(self):"""配置文件"""ini_file = os.path.join(self.BASE_DIR, 'config', 'config.ini')if not os.path.exists(ini_file):raise FileNotFoundError("配置文件%s不存在!" % ini_file)return ini_filecm = ConfigManager()
if __name__ == '__main__':print(cm.BASE_DIR)
【注意】:QQ郵箱授權碼:點擊查看生成教程
2)config.ini
在項目 config 目錄新建一個 config.ini 文件,里面暫時先放入需要測試的URL。
[HOST]
HOST = https://www.baidu.com
3)讀取配置文件
①配置文件創建好了,接下來我們需要讀取這個配置文件以使用里面的信息。
②使用python內置的 configparser 模塊對 config.ini 文件信息進行了讀取。
③對于測試url值的提取,使用python高階語法 @property 屬性值,寫法更簡單。
④在 common 目錄中新建一個 readconfig.py 文件。
import configparser
from config.conf import cmHOST = 'HOST'class ReadConfig(object):"""配置文件"""def __init__(self):self.config = configparser.RawConfigParser() # 當有%的符號時請使用Raw讀取self.config.read(cm.ini_file, encoding='utf-8')def _get(self, section, option):"""獲取"""return self.config.get(section, option)def _set(self, section, option, value):"""更新"""self.config.set(section, option, value)with open(cm.ini_file, 'w') as f:self.config.write(f)@propertydef url(self):return self._get(HOST, HOST)ini = ReadConfig()if __name__ == '__main__':print(ini.url)
4、記錄操作日志
在 utils 目錄中新建 logger.py 文件。
import logging
from config.conf import cmclass Log:def __init__(self):self.logger = logging.getLogger()if not self.logger.handlers:self.logger.setLevel(logging.DEBUG)# 創建一個handle寫入文件fh = logging.FileHandler(cm.log_file, encoding='utf-8')fh.setLevel(logging.INFO)# 創建一個handle輸出到控制臺ch = logging.StreamHandler()ch.setLevel(logging.INFO)# 定義輸出的格式formatter = logging.Formatter(self.fmt)fh.setFormatter(formatter)ch.setFormatter(formatter)# 添加到handleself.logger.addHandler(fh)self.logger.addHandler(ch)@propertydef fmt(self):return '%(levelname)s\t%(asctime)s\t[%(filename)s:%(lineno)d]\t%(message)s'log = Log().loggerif __name__ == '__main__':log.info('hello world')
5、項目中元素相關
POM模型:PO模式詳解
元素定位:①selenium UI自動化測試xpath定位詳解 ;②selenium UI自動化測試之CSS元素定位語法詳解
6、管理頁面元素
1)新建page_element目錄并在該目錄下新建search.yaml文件
①本教程選擇的測試地址是百度首頁,所以對應的元素也是百度首頁的。
②項目框架設計中有一個page_element 目錄就是專門來存放定位元素的文件的。
③通過對各種配置文件的對比,我在這里選擇的是YAML文件格式。其易讀,交互性好。
④在 page_element 中新建一個 search.yaml 文件。文件內容如下:
搜索框:"id==kw"
候選:"css==.bdsug-overflow"
搜索候選:"css==#form div li"
搜索按鈕:"id==su"
2)在common目錄中創建readelement.py文件。
①在 common 目錄中創建 readelement.py 文件。實現了定位元素的存儲和調用。文件內容如下:
②通過特殊方法 getitem 實現調用任意屬性,讀取yaml中的值。參考博客:python之__getitem__ 方法學習與使用 || python方法下劃線命名規則
import os
import yaml
from config.conf import cmclass Element(object):"""獲取元素"""def __init__(self, name):self.file_name = '%s.yaml' % nameself.element_path = os.path.join(cm.ELEMENT_PATH, self.file_name)if not os.path.exists(self.element_path):raise FileNotFoundError("%s 文件不存在!" % self.element_path)with open(self.element_path, encoding='utf-8') as f:self.data = yaml.safe_load(f)def __getitem__(self, item):"""獲取屬性"""data = self.data.get(item)if data:name, value = data.split('==')return name, valueraise ArithmeticError("{}中不存在關鍵字:{}".format(self.file_name, item))if __name__ == '__main__':search = Element('search')print(search['搜索框'])
3)新建script腳本文件目錄并新建inspect.py文件
在 script 腳本文件目錄中創建 inspect.py 文件,對所有的元素yaml文件內容進行審查。
import os
import yaml
from config.conf import cm
from utils.times import running_time@running_time
def inspect_element():"""檢查所有的元素是否正確只能做一個簡單的檢查"""for files in os.listdir(cm.ELEMENT_PATH):_path = os.path.join(cm.ELEMENT_PATH, files)with open(_path, encoding='utf-8') as f:data = yaml.safe_load(f)for k in data.values():try:pattern, value = k.split('==')except ValueError:raise Exception("元素表達式中沒有`==`")if pattern not in cm.LOCATE_MODE:raise Exception('%s中元素【%s】沒有指定類型' % (_path, k))elif pattern == 'xpath':assert '//' in value,\'%s中元素【%s】xpath類型與值不配' % (_path, k)elif pattern == 'css':assert '//' not in value, \'%s中元素【%s]css類型與值不配' % (_path, k)else:assert value, '%s中元素【%s】類型與值不匹配' % (_path, k)if __name__ == '__main__':inspect_element()
7、封裝Selenium基類
①工廠模式的寫法:很直白,簡單,又明了。
【創建driver對象,打開百度網頁,搜索selenium,點擊搜索,然后停留5秒,查看結果,最后關閉瀏覽器。】
import time
from selenium import webdriverdriver = webdriver.Chrome()
driver.get('https://www.baidu.com')
driver.find_element_by_xpath("//input[@id='kw']").send_keys('selenium')
driver.find_element_by_xpath("//input[@id='su']").click()
time.sleep(5)
driver.quit()
②那為什么要封裝selenium的方法呢?
首先我們上述這種較為原始的方法,基本不適用于平時做UI自動化測試的:因為在UI界面實際運行情況遠遠比較復雜,可能因為網絡原因,或者控件原因,我們元素還沒有顯示出來,就進行點擊或者輸入。
所以我們需要封裝selenium方法,通過內置的顯式等待或一定的條件語句,才能構建一個穩定的方法。而且把selenium方法封裝起來,有利于平時的代碼維護。
新建page目錄并創建webpage.py文件
①在 page 目錄創建 webpage.py 文件。文件內容如下:
②在文件中我們對主要用了顯示等待對selenium的 click , send_keys 等方法,做了二次封裝。提高了運行的成功率。
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutExceptionfrom config.conf import cm
from utils.times import sleep
from utils.logger import log"""
selenium基類
本文件存放了selenium基類的封裝方法
"""class WebPage(object):"""selenium基類"""def __init__(self, driver):# self.driver = webdriver.Chrome()self.driver = driverself.timeout = 20self.wait = WebDriverWait(self.driver, self.timeout)def get_url(self, url):"""打開網址并驗證"""self.driver.maximize_window()self.driver.set_page_load_timeout(60)try:self.driver.get(url)self.driver.implicitly_wait(10)log.info("打開網頁:%s" % url)except TimeoutException:raise TimeoutException("打開%s超時請檢查網絡或網址服務器" % url)@staticmethoddef element_locator(func, locator):"""元素定位器"""name, value = locatorreturn func(cm.LOCATE_MODE[name], value)def find_element(self, locator):"""尋找單個元素"""return WebPage.element_locator(lambda *args: self.wait.until(EC.presence_of_element_located(args)), locator)def find_elements(self, locator):"""查找多個相同的元素"""return WebPage.element_locator(lambda *args: self.wait.until(EC.presence_of_all_elements_located(args)), locator)def elements_num(self, locator):"""獲取相同元素的個數"""number = len(self.find_elements(locator))log.info("相同元素:{}".format((locator, number)))return numberdef input_text(self, locator, txt):"""輸入(輸入前先清空)"""sleep(0.5)ele = self.find_element(locator)ele.clear()ele.send_keys(txt)log.info("輸入文本:{}".format(txt))def is_click(self, locator):"""點擊"""self.find_element(locator).click()sleep()log.info("點擊元素:{}".format(locator))def element_text(self, locator):"""獲取當前的text"""_text = self.find_element(locator).textlog.info("獲取文本:{}".format(_text))return _text@propertydef get_source(self):"""獲取頁面源代碼"""return self.driver.page_sourcedef refresh(self):"""刷新頁面F5"""self.driver.refresh()self.driver.implicitly_wait(30)
8、創建頁面對象
新建page_object目錄并創建一個searchpage.py文件
①在 page_object 目錄下創建一個 searchpage.py 文件。
②在該文件中我們對,輸入搜索關鍵詞,點擊搜索,搜索聯想,進行了封裝。【在平時中我們應該養成寫注釋的習慣,因為過一段時間后,沒有注釋,代碼讀起來很費勁。】
9、應用pytest測試框架
pytest.ini文件創建
①pytest項目中的配置文件,可以對pytest執行過程中操作做全局控制。
②在項目根目錄新建 pytest.ini 文件。文件內容如下:
[pytest]
addopts = --html=report.html --self-contained-html
③addopts 指定執行時的其他參數說明:
- –html=report/report.html --self-contained-html 生成pytest-html帶樣式的報告
- -s 輸出我們用例中的調式信息
- -q 安靜的進行測試
- -v 可以輸出用例更加詳細的執行信息,比如用例所在的文件及用例名稱等
10、編寫測試用例
新建TestCase目錄并創建test_search.py文件
①在 TestCase 目錄中創建 test_search.py 文件。文件內容如下:
② pytest.fixture裝飾器實現了和unittest的setup,teardown一樣的前置啟動,后置清理的裝飾器。
③第一個測試用例:實現了在百度搜索selenium關鍵字,并點擊搜索按鈕,并在搜索結果中,用正則查找結果頁源代碼,返回數量大于10我們就認為通過。
④第二個測試用例:實現了百度搜索selenium關鍵字,然后斷言搜索候選中的所有結果有沒有selenium關鍵字。
import re
import pytest
from utils.logger import log
from common.readconfig import ini
from page_object.searchpage import SearchPageclass TestSearch:@pytest.fixture(scope='function', autouse=True)def open_baidu(self, drivers):"""打開百度"""search = SearchPage(drivers)search.get_url(ini.url)def test_001(self, drivers):"""搜索"""search = SearchPage(drivers)search.input_search("selenium")search.click_search()result = re.search(r'selenium', search.get_source)log.info(result)assert resultdef test_002(self, drivers):"""測試搜索候選"""search = SearchPage(drivers)search.input_search("selenium")log.info(list(search.imagine))assert all(["selenium" in i for i in search.imagine])if __name__ == '__main__':pytest.main(['TestCase/test_search.py'])
conftest.py
①在項目根目錄下新建一個 conftest.py 文件。
②conftest.py是測試框架pytest的膠水文件,里面用到了fixture函數,封裝并傳遞出了driver。
import pytest
from py.xml import html
from selenium import webdriverdriver = None@pytest.fixture(scope='session', autouse=True)
def drivers(request):""":param request: python內置的fixture函數,本函數中用來注冊終結函數:return: 返回driver實例"""global driverif driver is None:driver = webdriver.Chrome()driver.maximize_window()def fn():driver.quit()request.addfinalizer(fn)return driver@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):"""當測試失敗的時候,自動截圖,展示到html報告中:param item:"""pytest_html = item.config.pluginmanager.getplugin('html')outcome = yieldreport = outcome.get_result()report.description = str(item.function.__doc__)extra = getattr(report, 'extra', [])if report.when == 'call' or report.when == "setup":xfail = hasattr(report, 'wasxfail')if (report.skipped and xfail) or (report.failed and not xfail):file_name = report.nodeid.replace("::", "_") + ".png"screen_img = _capture_screenshot()if file_name:html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:1024px;height:768px;" ' \'οnclick="window.open(this.src)" align="right"/></div>' % screen_imgextra.append(pytest_html.extras.html(html))report.extra = extradef pytest_html_results_table_header(cells):cells.insert(1, html.th('用例名稱'))cells.insert(2, html.th('Test_nodeid'))cells.pop(2)def pytest_html_results_table_row(report, cells):cells.insert(1, html.td(report.description))cells.insert(2, html.td(report.nodeid))cells.pop(2)def pytest_html_results_table_html(report, data):if report.passed:del data[:]data.append(html.div('通過的用例未捕獲日志輸出.', class_='empty log'))def _capture_screenshot():'''截圖保存為base64:return:'''return driver.get_screenshot_as_base64()
11、發送郵件
①當項目執行完成之后,需要發送到自己或者其他人郵箱里查看結果。
②編寫發送郵件的模塊。
③在 utils 目錄中新建 send_mail.py 文件,文件內容如下:
import zmail
from config.conf import cmdef send_report():"""發送報告"""with open(cm.REPORT_FILE, encoding='utf-8') as f:content_html = f.read()try:mail = {'from': 'xxxxxx@qq.com','subject': '最新的測試報告郵件','content_html': content_html,'attachments': [cm.REPORT_FILE, ]}server = zmail.server(*cm.EMAIL_INFO.values())server.send_mail(cm.ADDRESSEE, mail)print("測試郵件發送成功!")except Exception as e:print("Error: 無法發送郵件,{}!", format(e))if __name__ == "__main__":'''請先在config/conf.py文件設置QQ郵箱的賬號和密碼'''send_report()
④在 config/conf.py 文件中設置我們自己的QQ郵箱賬號以及QQ郵箱授權碼。運行 send_report() 函數。
最后感謝每一個認真閱讀我文章的人,禮尚往來總是要有的,雖然不是什么很值錢的東西,如果你用得到的話可以直接拿走:
這些資料,對于做【軟件測試】的朋友來說應該是最全面最完整的備戰倉庫,這個倉庫也陪伴我走過了最艱難的路程,希望也能幫助到你!凡事要趁早,特別是技術行業,一定要提升技術功底。