Day 5 測試編程講義
本篇講義非常偏技術(shù)屬性席怪,需要用到比較多編程知識。閱讀此講義盅粪,需要對以下技術(shù)有一定了解:
- 數(shù)據(jù)庫表存儲
- 循環(huán)與分支語句
- 基礎(chǔ)設(shè)計(jì)模式
- 面向?qū)ο蠓庋b
- Python 庫的使用
0 主要內(nèi)容
- 1 T8_測試數(shù)據(jù)驅(qū)動
- 2 T9_測試業(yè)務(wù)抽離
- 3 T10_底層驅(qū)動封裝
1 T8_測試數(shù)據(jù)驅(qū)動
1.1 什么是數(shù)據(jù)驅(qū)動
-
什么是數(shù)據(jù)驅(qū)動
主要的數(shù)據(jù)驅(qū)動方式有兩種:
- 通過 文本文件或者 Excel 文件存儲數(shù)據(jù)蝇裤,并通過程序讀取數(shù)據(jù)全封,遍歷所有的行
- 通過數(shù)據(jù)庫存儲數(shù)據(jù),并通過程序和 SQL 腳本讀取數(shù)據(jù)榄檬,遍歷所有的行
通過 CSV 文件 或者 MySQL 數(shù)據(jù)庫卜范,是主流的數(shù)據(jù)驅(qū)動方式。當(dāng)然數(shù)據(jù)驅(qū)動也可以結(jié)合單元測試框架的參數(shù)化測試進(jìn)行編寫(此部分本文不做具體描述)鹿榜。
無論使用了 哪一種(CSV 或者 MySQL)海雪,讀取數(shù)據(jù)后都要進(jìn)行遍歷操作。
1.2 使用 csv
csv 是一種純文本的格式舱殿,主要用來存儲數(shù)據(jù)奥裸。
import csv
csv_file = open("xxx.csv", "r", encoding="utf8")
csv_data = csv.reader(csv_file)
for row in csv_data:
# 進(jìn)行測試
# 使用字典類型
data_to_test = {
"key1": row[0],
"key2": row[1]
}
csv_file.close()
1.3 使用 MySQL
import pymysql
connect = pymysql.connect(host="xx", port=3306, user="root", passwd="xxx", db="xx")
cur = connect.cursor()
cur.execute("SELECT...")
mysql_data = cur.fetchall()
for row in mysql_data:
# 進(jìn)行測試
# 使用字典類型
data_to_test = {
"key1": row[0],
"key2": row[1]
}
cur.close()
connect.close()
- 需要掌握的知識點(diǎn):
- python的字典類型
dict
類型 - python的讀寫文件
- python的讀寫數(shù)據(jù)庫
- for循環(huán)
- 注意資源的釋放
- 關(guān)閉數(shù)據(jù)庫游標(biāo)和連接
- 關(guān)閉文件
- python的字典類型
2 T9_測試業(yè)務(wù)抽離
2.1 Page-Object設(shè)計(jì)模式本質(zhì)
-
Page-Object設(shè)計(jì)模式的本質(zhì)
Page Object設(shè)計(jì)模式是Selenium自動化測試項(xiàng)目的最佳設(shè)計(jì)模式之一,強(qiáng)調(diào)測試沪袭、邏輯湾宙、數(shù)據(jù)和驅(qū)動相互分離。
Page Object模式是Selenium中的一種測試設(shè)計(jì)模式冈绊,主要是將每一個頁面設(shè)計(jì)為一個Class侠鳄,其中包含頁面中需要測試的元素(按鈕,輸入框死宣,標(biāo)題等)伟恶,這樣在Selenium測試頁面中可以通過調(diào)用頁面類來獲取頁面元素,這樣巧妙的避免了當(dāng)頁面元素id或者位置變化時毅该,需要改測試頁面代碼的情況知押。當(dāng)頁面元素id變化時叹螟,只需要更改測試頁Class中頁面的屬性即可。
它的好處如下:
- 集中管理元素對象台盯,便于應(yīng)對元素的變化
- 集中管理一個page內(nèi)的公共方法罢绽,便于測試用例的編寫
- 后期維護(hù)方便,不需要重復(fù)的復(fù)制和修改代碼
具體的做法如下:
- 創(chuàng)建一個頁面的類
- 在類的構(gòu)造方法中静盅,傳遞 WebDriver 參數(shù)良价。
- 在測試用例的類中,實(shí)例化頁面的類蒿叠,并且傳遞在測試用例中已經(jīng)實(shí)例化的WebDriver對象明垢。
- 在頁面的類中,編寫該頁面的所有操作的方法
- 在測試用例的類中市咽,調(diào)用這些方法
2.2 Page 如何劃分
一般通過繼承的方式痊银,進(jìn)行按照實(shí)際Web頁面進(jìn)行劃分。
- 主頁
- 子模塊主頁
- 分類頁
- 詳情頁
- 查看
- 編輯
- 增加
2.3 Page-Object 類如何實(shí)現(xiàn)
實(shí)現(xiàn)的示例
-
Page 基類
設(shè)計(jì)了一個基本的 Page類施绎,以便所有的頁面進(jìn)行繼承溯革,該類標(biāo)明了一個sub page類的基本功能和公共的功能。
-
全局變量: self.base_driver谷醉,讓所有的子類都使用的致稀。
# 基類的變量,所有繼承的類俱尼,都可以使用 base_driver = None
-
構(gòu)造方法:
-
傳遞 driver的構(gòu)造方法
# 方法 def __init__(self, driver: BoxDriver): """ 構(gòu)造方法 :param driver: ":BoxDriver" 規(guī)定了 driver 參數(shù)類型 """ self.base_driver = driver
-
-
私有的常量:存放元素的定位符
LOGIN_ACCOUNT_SELECTOR = "s, #account" LOGIN_PASSWORD_SELECTOR = "s, #password" LOGIN_KEEP_SELECTOR = "s, #keepLoginon" LOGIN_SUBMIT_SELECTOR = "s, #submit" LOGIN_LANGUAGE_BUTTON_SELECTOR = "s, #langs > button" LOGIN_LANGUAGE_MENU_SELECTOR = "s, #langs > ul > li:nth-child(%d) > a" LOGIN_FAIL_MESSAGE_SELECTOR = "s, body > div.bootbox.modal.fade.bootbox-alert.in > div > div > div.modal-body"
-
成員方法:
-
每個子類都需要的系統(tǒng)功能:
-
open
def open(self, url): """ 打開頁面 :param url: :return: """ self.base_driver.navigate(url) self.base_driver.maximize_window() sleep(2)
-
-
所有子類(頁面)都具有的業(yè)務(wù)功能
- select_app
- logout
-
-
Sub Pages(s)子類
具體的頁面的類抖单,定義了某個具體的頁面的功能
-
必須繼承基類
class MainPage(BasePage):
特定頁面的業(yè)務(wù)
使用基類的
self.base_driver
成員變量
-
Tests 類
這部分描述的是具體的測試用例。
-
聲明全局變量
base_driver = None base_url = None main_page = None
-
調(diào)用各種頁面(pages)
-
實(shí)例化Page
self.main_page = MainPage(self.base_driver)
-
使用page的對象遇八,調(diào)用成員方法
self.main_page.open(self.base_url) self.main_page.change_language(lang)
-
3 T10_底層驅(qū)動封裝
3.1 為什么需要封裝 Selenium
-
什么是封裝
封裝是一個面向?qū)ο缶幊痰母拍蠲妫敲嫦驅(qū)ο缶幊痰暮诵膶傩裕ㄟ^將代碼內(nèi)部實(shí)現(xiàn)進(jìn)行密封和包裝刃永,從而簡化編程蔑歌。對Selenium進(jìn)行封裝的好處主要有如下三個方面:
- 使用成本低
- 不需要要求所有的測試工程師會熟練使用Selenium,而只需要會使用封裝以后的代碼
- 不需要對所有的測試工程師進(jìn)行完整培訓(xùn)揽碘。也避免工作交接的成本次屠。
- 測試人員使用統(tǒng)一的代碼庫
- 維護(hù)成本低
- 通過封裝,在代碼發(fā)生大范圍變化和遷移的時候雳刺,不需要維護(hù)所有代碼劫灶,只需要變更封裝的部分即可
- 維護(hù)代碼不需要有大量的工程師,只需要有核心的工程師進(jìn)行封裝的維護(hù)即可
- 代碼安全性
- 對作為第三方的Selenium進(jìn)行封裝掖桦,是代碼安全的基礎(chǔ)本昏。
- 對于任何的代碼的安全隱患,必須由封裝來解決枪汪,使得風(fēng)險可控涌穆。
- 使用者并不知道封裝內(nèi)部的代碼結(jié)構(gòu)怔昨。
- 使用成本低
3.2 封裝的概念與基本操作
-
關(guān)鍵方法的封裝思路
封裝的具體示例:
-
找到一個指定輸入框(selector),并且輸入指定的字符(text)
type(selector, text)
不用在業(yè)務(wù)邏輯中宿稀,使用多次的
find_element_by_id(...))
def type(self, selector, text): """ Operation input box. Usage: driver.type("i,el","selenium") """ el = self._locate_element(selector) el.clear() el.send_keys(text)
-
找到一個可以點(diǎn)擊的元素(selector)趁舀,并且點(diǎn)擊(click)
click(selector)
def click(self, selector): """ It can click any text / image can be clicked Connection, check box, radio buttons, and even drop-down box etc.. Usage: driver.click("i,el") """ el = self._locate_element(selector) el.click()
-
找到一個指定的frame,并且切換進(jìn)去
switch_to_frame(selector)
def switch_to_frame(self, selector): """ Switch to the specified frame. Usage: driver.switch_to_frame("i,el") """ el = self._locate_element(selector) self.base_driver.switch_to.frame(el)
-
找到一個指定的select祝沸,并且通過index進(jìn)行選擇
select_by_index(selector, index)
def select_by_index(self, selector, index): """ It can click any text / image can be clicked Connection, check box, radio buttons, and even drop-down box etc.. Usage: driver.select_by_index("i,el") """ el = self._locate_element(selector) Select(el).select_by_index(index)
以上的代碼是封裝了
_locate_element()
的幾種方法矮烹,在具體使用封裝過的代碼的時候,只需要簡單的調(diào)用即可罩锐。接下來的重點(diǎn)奉狈,是介紹_locate_element(selector)
的封裝方式。- 查找元素:
find_element_by_...)
- 支持各種的查找:8種方式都需要支持涩惑,必須通過
selector
顯示出分類-
selector
中需要包含一個特殊符號 - 實(shí)例化 封裝好的類的時候仁期,需要約定好是什么特殊符號
- 強(qiáng)制性用
硬編碼 hard code
來實(shí)例化,例如,
或者?
或者 其他非常用字符=>
- 或者竭恬,構(gòu)造方法中跛蛋,傳遞
this.byChar
- 強(qiáng)制性用
-
- 要把查找到元素的返回給調(diào)用的地方:必須要有返回值,類型是
WebElement
def _locate_element(self, selector): """ to locate element by selector :arg selector should be passed by an example with "i,xxx" "x,//*[@id='langs']/button" :returns DOM element """ if self.by_char not in selector: return self.base_driver.find_element_by_id(selector) selector_by = selector.split(self.by_char)[0].strip() selector_value = selector.split(self.by_char)[1].strip() if selector_by == "i" or selector_by == 'id': element = self.base_driver.find_element_by_id(selector_value) elif selector_by == "n" or selector_by == 'name': element = self.base_driver.find_element_by_name(selector_value) elif selector_by == "c" or selector_by == 'class_name': element = self.base_driver.find_element_by_class_name(selector_value) elif selector_by == "l" or selector_by == 'link_text': element = self.base_driver.find_element_by_link_text(selector_value) elif selector_by == "p" or selector_by == 'partial_link_text': element = self.base_driver.find_element_by_partial_link_text(selector_value) elif selector_by == "t" or selector_by == 'tag_name': element = self.base_driver.find_element_by_tag_name(selector_value) elif selector_by == "x" or selector_by == 'xpath': element = self.base_driver.find_element_by_xpath(selector_value) elif selector_by == "s" or selector_by == 'css_selector': element = self.base_driver.find_element_by_css_selector(selector_value) else: raise NameError("Please enter a valid type of targeting elements.") return element
?
-
-
面向?qū)ο缶幊趟枷氲倪\(yùn)用
- 構(gòu)造方法
- 類
- 普通方法
-
封裝后的方法如何被調(diào)用
使用上面的封裝類萍聊,就需要指定特定的 selector
類型 示例(分隔符以逗號 ,
為例)描述 id "account" 或者 "i,account" 或者 "id,account" 分隔符左右兩側(cè)不可以空格 xpath "x,//*[@id="s-menu-dashboard"]/button/i" css selector "s,#s-menu-dashboard > button > i" link text "l,退出" partial link text "p,退" name "n,name1" tag name "t,input" class name "c,dock-bottom 具體調(diào)用示例
def login(self, account, password, keep): """ 登錄系統(tǒng) :param account: :param password: :param keep: :return: 返回保持登錄復(fù)選框的 checked 值 """ self.base_driver.type(self.LOGIN_ACCOUNT_SELECTOR, account) self.base_driver.type(self.LOGIN_PASSWORD_SELECTOR, password) current_checked = self.get_current_keep_value() if keep: if current_checked is None: self.base_driver.click(self.LOGIN_KEEP_SELECTOR) else: if current_checked == "true": self.base_driver.click(self.LOGIN_KEEP_SELECTOR) actual_checked = self.get_current_keep_value() self.base_driver.click(self.LOGIN_SUBMIT_SELECTOR) sleep(2) return actual_checked
3.3 測試報(bào)告的生成
如何生成測試報(bào)告
測試報(bào)告的種類
-
HTML 測試報(bào)告的生成
HTML測試報(bào)告需要引入HTMLTestRunner
HTMLTestRunner是基于Python2.7的问芬,我們的課程講義基于Python3.x悦析,那么需要對這個文件做一定的修改寿桨。
測試的示例代碼如下
# 聲明一個測試套件 suite = unittest.TestSuite() # 添加測試用例到測試套件 suite.addTest(RanzhiTests("test_ranzhi_login")) # 創(chuàng)建一個新的測試結(jié)果文件 buf = open("./result.html", "wb") # 聲明測試運(yùn)行的對象 runner = HTMLTestRunner.HTMLTestRunner(stream=buf, title="Ranzhi Test Result", description="Test Case Run Result") # 運(yùn)行測試,并且將結(jié)果生成為HTML runner.run(suite) # 關(guān)閉文件輸出 buf.close()
- 相關(guān)學(xué)習(xí)
立師兄Linty:六天入門軟件測試①——測試執(zhí)行講義
立師兄Linty:六天入門軟件測試①——測試執(zhí)行筆記
立師兄Linty:六天入門軟件測試③——測試設(shè)計(jì)講義
立師兄Linty:六天入門軟件測試③——測試設(shè)計(jì)筆記