selenium自動化測試基礎(chǔ)
- 1. 使用測試工具
- 2. 構(gòu)建測試方案
原文閱讀對象是針對測試人員,測試人員普遍開發(fā)能力偏弱,所以講的比較瑣碎,這里做了一定的刪減和改寫,主要針對測試主管或有一定經(jīng)驗的開發(fā)人員或負責(zé)人,快速了解及考慮使用selenium自動化測試方案。
自動化測試并不屬于新鮮的事物,或者說自動化測試的各種方法論已經(jīng)層出不窮,但是,能夠在項目中持之以恒的實踐自動化測試的團隊,卻依舊不是非常多。有的團隊知道怎么做,做的還不夠好;有的團隊還正在探索和摸索怎么做,甚至還有一些多方面的技術(shù)上和非技術(shù)上的舊系統(tǒng)需要重構(gòu)……
本文從
使用
和實踐
兩個視角,嘗試對基于Web UI
自動化測試做細致的分析和解讀,給各位去思考和實踐做一點引路,以便各團隊能找到更好的方式。
1. 使用測試工具
在開始具體的自動化測試之前,我們需要做好更多的準備,包括以下幾個方面:
- 認識自動化測試
- 準備自動化測試工具
- 使用有效的方式
- 針對具體的測試對象
1.1 自動化測試理論介紹
自動化測試不再是一個陌生的話題,作為測試實踐活動的一部分,我們首先分析一下自動化測試的方方面面。
自動化測試的4W1H
-
WHAT
- 什么是自動化測試《軟件測試藝術(shù)》一書中,給出了測試的定義:
“程序測試是為了發(fā)現(xiàn)錯誤而執(zhí)行的過程。”
自動化測試:以人為驅(qū)動的測試行為轉(zhuǎn)化為機器執(zhí)行的一種過程
自動化測試,就是把手工進行的測試過程,轉(zhuǎn)變成機器自動執(zhí)行的測試過程。該過程,依舊是為了發(fā)現(xiàn)錯誤而執(zhí)行。因此自動化測試的關(guān)鍵在于“自動化”三個字。自動化測試的內(nèi)容,也就相應(yīng)的轉(zhuǎn)變成如何“自動化”去實現(xiàn)原本手工進行的測試的過程。
通過程序,可以把手工測試,轉(zhuǎn)變成自動化測試。
-
WHEN
- 在什么時候開展自動化測試自動化測試的開展,依賴于“程序”。那么程序,其實就是由“源代碼”構(gòu)建而來的。那么原則上,只要能做出自動化測試所需要的“程序”的時候,就可以進行自動化測試。但往往,并不是所有的“時候”都是好的“時機”。從這個
W
開始,我們將會加入對于成本的顧慮,也正是因為“成本”的存在,才使得下面的討論,變得有意義。所有的開銷,都是有成本的。構(gòu)建成“程序”的源代碼,也是由工程師寫出來的。那么需要考慮這個過程中的成本。基于這個考慮,在能夠比較穩(wěn)定的構(gòu)建“程序”的時候,不需要花費太多開銷在“源代碼”的時候,就是開展自動化測試的好時機。這個開銷包括
編寫
和修改
源代碼,而源代碼指的是構(gòu)建出用來做自動化測試的程序
的源代碼。 -
WHERE
- 在什么地方進行自動化測試自動化測試的執(zhí)行,依靠的是機器。那么自動化測試必將在“機器”上進行。一般來說,這個機器包括桌面電腦和服務(wù)器。通過將寫好的
源代碼
部署在機器上,構(gòu)建出用來做自動化測試的"程序",并且運行該程序,實現(xiàn)自動化測試。 -
WHICH
- 對什么目標進行自動化測試自動化測試的目標,是被測試的軟件。拋開人工智能的成分,手工測試必將在“人工智能”足夠普及和足夠“智能”之前,替代一大部分不需要“人類智能”的手工測試;以及自動化測試會做一些手工測試無法實施的,或者手工測試無法覆蓋的測試。
- 不需要“人類智能”的普通手工測試
- 界面的普通操作
- 通過固定輸入和固定操作而進行的流程化測試
- 重復(fù)的普通測試
- 手工測試無法實施或者覆蓋的
- 大量的數(shù)據(jù)的輸入
- 大量的步驟的操作
- 源代碼基本的測試
- 系統(tǒng)模塊間接口的調(diào)用測試
- ……
-
HOW
- 如何開展自動化測試- 準備測試用例
- 找到合適的自動化測試工具
- 用準確的編程形成測試腳本
- 在測試腳本中對目標進行“檢查”,即做斷言
- 記錄測試日志,生成測試結(jié)果
和所有的其他測試一樣,自動化測試的流程也是由“用例”執(zhí)行和“缺陷”驗證組成。差別是需要找到合適的“工具”來替代“人手”。不同目標的自動化測試有不同的測試工具,但是任何工具都無不例外的需要“編程”的過程,實現(xiàn)“源代碼”,也可以稱之為測試腳本。
于是開展自動化測試的方式基本上如下:
自動化測試的典型金字塔原理
談到自動化測試,就不得不提的一個人和概念就是:Martin Fowler和他的金字塔原理。首先請看金字塔原理的圖示如下:
該圖說明了三個問題:
- 自動化測試包括三個方面:UI前端界面,Service服務(wù)契約和Unit底層單元
- 越是底層的測試,運行速度越快,時間開銷越少,金錢開銷越少
- 越是頂層的測試,運行速度越慢,時間開銷越多,金錢開銷越多
這是理想中的金字塔原理。
在實際的項目中,尤其是結(jié)合國內(nèi)的項目實踐,其實還隱藏了另一個問題:越是頂層的測試,效果越明顯。有句話說“貴的東西,除了貴,其他都是好的!”能夠很清晰的闡述這個觀點。
金字塔原理在國內(nèi)的適應(yīng)性也有一定的問題
- 自動化測試的起步不是特別早
- 甚至軟件測試很長一段時間都在進行基于業(yè)務(wù)的手工測試,測試人員的代碼能力相對較弱
- 開發(fā)人員在代碼中不太習(xí)慣寫單元測試
- 近些年基于服務(wù)契約的API測試也在興起
相對來說,在基于UI前端界面的自動化測試反倒是開展和實施的不是特別多。盡管基于界面的測試帶來的效果還是能夠立竿見影的。對于產(chǎn)品的質(zhì)量提升,還是比較容易有保證。
自動化測試的適用范圍
自動化測試可以涉及和試用的范圍主要在以下方面:
- 基于
Web UI
的瀏覽器應(yīng)用的界面測試 - 基于
WebService
或者WebAPI
的服務(wù)契約測試 - 基于
WCF
、.net remoting
、Spring
等框架的服務(wù)的集成測試 - 基于
APP UI
的移動應(yīng)用界面測試 - 基于
Java
、C#
等編程文件進行的單元測試
本文集中討論第一條:基于Web UI
的瀏覽器應(yīng)用的界面測試。界面的改動對于測試來說,具有較大的成本風(fēng)險。主要考慮以下方面:
- 任務(wù)測試明確,不會頻繁變動
- 每日構(gòu)建后的測試驗證
- 比較頻繁的回歸測試
- 軟件系統(tǒng)界面穩(wěn)定,變動少
- 需要在多平臺上運行的相同測試案例、組合遍歷型的測試、大量的重復(fù)任務(wù)
- 軟件維護周期長
- 項目進度壓力不太大
- 被測軟件系統(tǒng)開發(fā)比較規(guī)范,能夠保證系統(tǒng)的可測試性
- 具備大量的自動化測試平臺
- 測試人員具備較強的編程能力
- 自動化測試的流程
自動化測試和普通的手工測試遵循的測試流程,與項目的具體實踐相關(guān)。一般來說,也是需要從測試計劃開始涉及自動化測試的。
- 測試計劃:劃定自動化測試的范圍包含哪些需求,涉及到哪些測試過程
- 測試策略:確定自動化測試的工具、編程方案、代碼管理、測試重點
- 測試設(shè)計:使用測試設(shè)計方法對被測試的需求進行設(shè)計,得出測試的測試點、用例思維導(dǎo)圖等
- 測試實施:根據(jù)測試設(shè)計進行用例編寫,并且將測試用例用編程的方式實現(xiàn)測試腳本
- 測試執(zhí)行:執(zhí)行測試用例,運行測試腳本,生成測試結(jié)果
1.2 自動化測試工具
基于Web UI
的自動化測試工具主要有兩大類:付費的商業(yè)版工具和免費使用的開源版工具。典型的有兩種:
-
UFT,QTP被惠普收購以后的新名稱。
- 通過程序的錄制,可以實現(xiàn)測試的編輯
- 錄制的測試腳本是 VBScript 語法
- 成熟版的商業(yè)付費工具
- 工具比較龐大,對具體的項目定制測試有難度
-
SELENIUM,本次選擇的開源工具
- 本身不是測試工具,只是模擬瀏覽器操作的工具
- 背后有 Google 維護源代碼
- 支持全部主流的瀏覽器
- 支持主流的編程語言,包括:Java、Python、C#、PHP、Ruby、JavaScript等
- 工具很小,可以實現(xiàn)對測試項目的定制測試方案
- 基于標準的 WebDriver 語法規(guī)范
Selenium 基本介紹
Selenium`是開源的自動化測試工具,它主要是用于Web 應(yīng)用程序的自動化測試,不只局限于此,同時支持所有基于web 的管理任務(wù)自動化。
-
Selenium
官網(wǎng)的介紹Selenium is a suite of tools to automate web browsers across many platforms.
- runs in many browsers and operating systems
- can be controlled by many programming languages and testing frameworks.
- Selenium 官網(wǎng):http://seleniumhq.org/
- Selenium Github 主頁:https://github.com/SeleniumHQ/selenium
Selenium 是用于測試 Web 應(yīng)用程序用戶界面 (UI) 的常用框架。它是一款用于運行端到端功能測試的超強工具。您可以使用多個編程語言編寫測試,并且 Selenium 能夠在一個或多個瀏覽器中執(zhí)行這些測試。
Selenium 工具集
-
Selenium IDE
Selenium IDE (集成開發(fā)環(huán)境) 是一個創(chuàng)建測試腳本的原型工具。它是一個 Firefox 插件,實現(xiàn)簡單的瀏覽器操作的錄制與回放功能,提供創(chuàng)建自動化測試的建議接口。Selenium IDE 有一個記錄功能,能記錄用戶的操作,并且能選擇多種語言把它們導(dǎo)出到一個可重用的腳本中用于后續(xù)執(zhí)行。
-
Selenium RC
Selenium RC 是selenium 家族的核心工具,Selenium RC 支持多種不同的語言編寫自動化測試腳本,通過selenium RC 的服務(wù)器作為代理服務(wù)器去訪問應(yīng)用從而達到測試的目的。
selenium RC 使用分Client Libraries 和Selenium Server。
- Client Libraries 庫主要主要用于編寫測試腳本,用來控制selenium Server 的庫。
- Selenium Server 負責(zé)控制瀏覽器行為,總的來說,Selenium Server 主要包括3 個部分:Launcher、Http Proxy、Core。
-
Selenium Grid
Selenium Grid 使得 Selenium RC 解決方案能提升針對大型的測試套件或者哪些需要運行在多環(huán)境的測試套件的處理能力。Selenium Grid 能讓你并行的運行你的測試,也就是說,不同的測試可以同時跑在不同的遠程機器上。這樣做有兩個優(yōu)勢,首先,如果你有一個大型的測試套件,或者一個跑的很慢的測試套件,你可以使用 Selenium Grid 將你的測試套件劃分成幾份同時在幾個不同的機器上運行,這樣能顯著的提升它的性能。同時,如果你必須在多環(huán)境中運行你的測試套件,你可以獲得多個遠程機器的支持,它們將同時運行你的測試套件。在每種情況下,Selenium Grid 都能通過并行處理顯著地縮短你的測試套件的處理時間。
-
Selenium WebDriver
WebDriver 是 Selenium 2 主推的工具,事實上WebDriver是Selenium RC的替代品,因為Selenium需要保留向下兼容性的原因,在 Selenium 2 中, Selenium RC才沒有被徹底的拋棄,如果使用Selenium開發(fā)一個新的自動化測試項目,那么我們強烈推薦使用Selenium2 的 WebDriver進行編碼。另外, 在Selenium 3 中,Selenium RC 被移除了。
自動化編程語言Python選擇
- 測試人員的編程能力普遍不是很強,而Python作為一種腳本語言,不僅功能強大,而且語法優(yōu)美,支持多種自動化測試工具,而且學(xué)習(xí)上手比較容易。
- 對于有一定編程基礎(chǔ)的人員,使用Python作為自動化測試的語言可以非常順暢的轉(zhuǎn)換,幾乎沒有學(xué)習(xí)成本。同時Python是標準的面向?qū)ο蟮木幊陶Z言,對于C#、Java等面向?qū)ο蟮恼Z言有著非常好的示例作用,通過Python的示例可以非常輕松的觸類旁通,使用其他語言進行Selenium2.0的WebDriver的使用。
使用的工具集
- IDE: Jetbrains PyCharm
- 語言: Python
- 工具: Selenium WebDriver
- 源代碼管理: SVN/Git
JetBrains PyCharm 使用
- JetBrains PyCharm 的介紹
PyCharm 是 JetBrains 公司針對Python推出的IDE(Integrated Development Environment,集成開發(fā)環(huán)境)。是目前最好的Python IDE之一。目前包含了兩個版本:
- 社區(qū)版,Community Edition
- 專業(yè)版,Professional Edition
- 付費
- 比社區(qū)版主要多了Web開發(fā)框架
我們推薦使用免費的社區(qū)版本,進行Python腳本的編寫和自動化測試執(zhí)行。
PyCharm可以在官網(wǎng)下載,http://www.jetbrains.com
Selenium 的環(huán)境搭建
在 Windows 搭建和部署 Selenium 工具
先安裝python
-
安裝 Selenium 工具包
由于 安裝好的 Python 默認有
pip
Python 包管理工具,可以通過pip
非常方便的安裝 Selenium。
```
pip install selenium
```
```
pip install -U selenium
```
當然,可以事先下載 Selenium 的 Python 安裝包,再進行手動安裝。
官方下載地址:https://pypi.python.org/pypi/selenium
```
python setup.py install
```
?
-
配置瀏覽器和驅(qū)動
Selenium 3 對于所有的瀏覽器都需要安裝驅(qū)動,本文以 Chrome 和 Firefox、IE為例設(shè)置瀏覽器和驅(qū)動。
ChromeDriver下載地址:http://chromedriver.storage.googleapis.com/index.html
ChromeDriver 與 Chrome 對應(yīng)關(guān)系表:
ChromeDriver版本 | 支持的Chrome版本 |
-|-|
v2.31| v58-60 |
v2.30 |v58-60 |
v2.29 |v56-58 |
GeckoDriver下載地址:https://github.com/mozilla/geckodriver/releases
GeckoDriver 與 Firefox 的對應(yīng)關(guān)系表:
GeckoDriver版本 |支持的Firefox版本 |
-|-|
v0.18.0| v56 |
v0.17.0 |v55 |
IEDriverServer下載地址:http://selenium-release.storage.googleapis.com/index.html
IEDriverServer 的版本需要與 Selenium 保持嚴格一致。
**瀏覽器驅(qū)動的配置**
- 首先,將下載好的對應(yīng)版本的瀏覽器安裝。
- 其次,在 Python 的根目錄中,放入瀏覽器驅(qū)動。
- 最好再重啟電腦,一般情況下不重啟也可以的。
?
1.3 Selenium 的最簡腳本
通過上一節(jié)的環(huán)境安裝成功以后,我們可以進行第一個對Selenium 的使用,就是最簡腳本編寫。腳本如下:
# 聲明一個司機,司機是個Chrome類的對象
driver = webdriver.Chrome()
# 讓司機加載一個網(wǎng)頁
driver.get("http://demo.xxx.com")
# 給司機3秒鐘去打開
sleep(3)
# 開始登錄
# 1. 讓司機找用戶名的輸入框
we_account = driver.find_element_by_css_selector('#account')
we_account.clear()
we_account.send_keys("demo")
# 2. 讓司機找密碼的輸入框
we_password = driver.find_element_by_css_selector('#password')
we_password.clear()
we_password.send_keys("demo")
# 3. 讓司機找 登錄按鈕 并 單擊
driver.find_element_by_css_selector('#submit').click()
sleep(3)
實際上一段20行的代碼,也不能算太少了。但是這段代碼的使用,確實體現(xiàn)了 Selenium 的最簡單的使用。這些代碼有開發(fā)基礎(chǔ)的同學(xué),一看都懂。
注意:Chrome 是 WebDriver 的子類,是 WebDriver 類的一種
1.4 Selenium WebDriver API 的使用
通過上述最簡腳本的使用,我們可以來進一步了解 Selenium 的使用。事實上,上一節(jié)用的,便是 Selenium 的 WebDriver API。
Selenium WebDriver API 官方參考:http://seleniumhq.github.io/selenium/docs/api/py/
具體API文檔地址:https://seleniumhq.github.io/selenium/docs/api/py/api.html
控制瀏覽器
瀏覽器的控制也是自動化測試的一個基本組成部分,我們可以將瀏覽器最大化,設(shè)置瀏覽器的高度和寬度以及對瀏覽器進行導(dǎo)航操作等。
# 瀏覽器打開網(wǎng)址
driver.get("https://www.baidu.com")
# 瀏覽器最大化
driver.maximize_window()
# 設(shè)置瀏覽器的高度為800像素,寬度為480像素
driver.set_window_size(480, 800)
# 瀏覽器后退
driver.back()
# 瀏覽器前進
driver.forward()
# 瀏覽器關(guān)閉
driver.close()
# 瀏覽器退出
driver.quit()
元素定位操作
WebDriver提供了一系列的定位符以便使用元素定位方法。常見的定位符有以下幾種:
- id
- name
- class name
- tag
- link text
- partial link text
- xpath
- css selector
那么我們以下的操作將會基于上述的定位符進行定位操作。
對于元素的定位,WebDriver API可以通過定位簡單的元素和一組元素來操作。在這里,我們需要告訴Selenium如何去找元素,以至于他可以充分的模擬用戶行為,或者通過查看元素的屬性和狀態(tài),以便我們執(zhí)行一系列的檢查。
在Selenium2中,WebDriver提供了多種多樣的find_element_by
方法在一個網(wǎng)頁里面查找元素。這些方法通過提供過濾標準來定位元素。當然WebDriver也提供了同樣多種多樣的find_elements_by
的方式去定位多個元素。
盡管上述的方式,可以進行元素定位,實際上我們也是更多的用組合的方式進行元素定位。
方法Method | 描述Description | 參數(shù)Argument | 示例Example |
---|---|---|---|
id |
該方法通過ID的屬性值去定位查找單個元素 | id: 需要被查找的元素的ID | find_element_by_id('search') |
name |
該方法通過name的屬性值去定位查找單個元素 | name: 需要被查找的元素的名稱 | find_element_by_name('q') |
class name |
該方法通過class的名稱值去定位查找單個元素 | class_name: 需要被查找的元素的類名 | find_element_by_class_name('input-text') |
tag_name |
該方法通過tag的名稱值去定位查找單個元素 | tag: 需要被查找的元素的標簽名稱 | find_element_by_tag_name('input') |
link_text |
該方法通過鏈接文字去定位查找單個元素 | link_text: 需要被查找的元素的鏈接文字 | find_element_by_link_text('Log In') |
partial_link_text |
該方法通過部分鏈接文字去定位查找單個元素 | link_text: 需要被查找的元素的部分鏈接文字 | find_element_by_partial_link_text('Long') |
xpath |
該方法通過XPath的值去定位查找單個元素 | xpath: 需要被查找的元素的xpath | find_element_by_xpath('//*[@id="xx"]/a') |
css_selector |
該方法通過CSS選擇器去定位查找單個元素 | css_selector: 需要被查找的元素的ID | find_element_by_css_selector('#search') |
而find_elements_by
的方法依據(jù)匹配的具體標準返回一系列的元素。
下面代碼通過ID的屬性值去定義一個查找文本框的查找:
<input id="search" type="text" name="q" value=""
class="input-text" maxlength="128" autocomplete="off"/>
根據(jù)上述代碼,這里我們使用find_element_by_id()
的方法去查找搜索框并且檢查它的最大長度maxlength
屬性。我們通過傳遞ID的屬性值作為參數(shù)去查找,參考如下的代碼示例:
def test_search_text_field_max_length(self):
# get the search textbox
search_field = self.driver.find_element_by_id("search")
# check maxlength attribute is set to 128
self.assertEqual("128", search_field.get_attribute("maxlength"))
如果使用find_elements_by_id()
方法,將會返回所有的具有相同ID屬性值的一系列元素。
這里還是根據(jù)上述ID查找的HTML代碼,使用find_element_by_name
的方法進行查找。參考如下的代碼示例:
# get the search textbox
self.search_field = self.driver.find_element_by_name("q")
同樣,如果使用find_elements_by_name()
方法,將會返回所有的具有相同name屬性值的一系列元素。
依據(jù)class name查找
除了上述的ID和name的方式查找,我們還可以使用class name的方式進行查找和定位。
事實上,通過ID,name或者類名class name查找元素是最提倡推薦的和最快的方式。
<button type="submit" title="Search" class="button">
<span><span>Search</span></span>
</button>
根據(jù)上述代碼,使用find_element_by_class_name()
方法去定位元素。
def test_search_button_enabled(self):
# get Search button
search_button = self.driver.find_element_by_class_name("button")
# check Search button is enabled
self.assertTrue(search_button.is_enabled())
其他方式都可以參考jquery或一些爬蟲關(guān)于html節(jié)點查找的方法。
依據(jù)XPath進行查找
XPath是一種在XML文檔中搜索和定位節(jié)點node的一種查詢語言。所有的主流Web瀏覽器都支持XPath。
常用的XPath的方法有starts-with()
,contains()
和ends-with()
等
若想要了解更多關(guān)于XPath的內(nèi)容,請查看http://www.w3schools.com/XPath/
如下有一段HTML代碼,其中里面的<img>
沒有使用ID,name或者類屬性,所以我們無法使用之前的方法。這里我們可以通過<img>
的alt
屬性,定位到指定的tag。
<ul class="promos">
<li>
<a >
<img src="/media/wysiwyg/homepage-three-column-promo-
01B.png" alt="Physical & Virtual Gift Cards">
</a>
</li>
<li>
<a >
<img src="/media/wysiwyg/homepage-three-column-promo-
02.png" alt="Shop Private Sales - Members Only">
</a>
</li>
<li>
<a href="http://www.9999.com/accessories/
bags-luggage.html">
<img src="/media/wysiwyg/homepage-three-columnpromo-
03.png" alt="Travel Gear for Every Occasion">
</a>
</li>
</ul>
具體代碼如下:
def test_vip_promo(self):
# get vip promo image
vip_promo = self.driver.\
find_element_by_xpath("http://img[@alt='Shop Private Sales - Members Only']")
# check vip promo logo is displayed on home page
self.assertTrue(vip_promo.is_displayed())
# click on vip promo images to open the page
vip_promo.click()
# check page title
self.assertEqual("VIP", self.driver.title)
當然,如果使用find_elements_by_xpath()
的方法,將會返回所有匹配了XPath查詢的元素。
依據(jù)CSS選擇器進行查找
CSS是一種設(shè)計師用來描繪HTML文檔的視覺的層疊樣式表。一般來說CSS用來定位多種多樣的風(fēng)格,同時可以用來是同樣的標簽使用同樣的風(fēng)格等。
<div class="minicart-wrapper">
<p class="block-subtitle">Recently added item(s)
<a class="close skip-link-close" href="#" title="Close">×</a>
</p>
<p class="empty">You have no items in your shopping cart.
</p>
</div>
我們來創(chuàng)建一個測試,驗證這些消息是否正確。
def test_shopping_cart_status(self):
# check content of My Shopping Cart block on Home page
# get the Shopping cart icon and click to open the
# Shopping Cart section
shopping_cart_icon = self.driver.\
find_element_by_css_selector("div.header-minicart
span.icon")
shopping_cart_icon.click()
# get the shopping cart status
shopping_cart_status = self.driver.\
find_element_by_css_selector("p.empty").text
self.assertEqual("You have no items in your shopping cart.",
shopping_cart_status)
# close the shopping cart section
close_button = self.driver.\
find_element_by_css_selector("div.minicart-wrapper
a.close")
close_button.click()
特殊 iframe 操作
iframe 元素會創(chuàng)建包含另外一個文檔的內(nèi)聯(lián)框架(即行內(nèi)框架)。
在一個<html>中,包含了另一個<html>
示例
<html>
<head>
<title>iframe示例</title>
</head>
<body>
<h1>
這里是H1,標記了標題
</h1>
<p>
這里是段落,標記一個段落,屬于外層
</p>
<div>
<iframe id="iframe-1">
<html>
<body>
<p>
這里是個段落,屬于內(nèi)層,內(nèi)聯(lián)框架中的
</p>
<div id="div-1">
<p class="hahahp">
這里是div中的段落,需要被定位
</p>
</div>
</body>
</html>
</iframe>
</div>
</body>
</html>
需要定位上面示例中的:
<p>這里是div中的段落,需要被定位</p>
如下是selenium WebDiriver的代碼
## 查找并定位 iframe
element_frame = driver.find_element_by_css_selector('#iframe-1')
## 切換到剛剛查找到的 iframe
driver.switch_to.frame(element_frame)
## 定位 <p>
driver.find_element_by_css_selector('#div-1 > p')
## TODO....
## 退出剛剛切換進去的 iframe
driver.switch_to.default_content()
特殊 Select 操作
<select>
是選擇列表Select 是個selenium的類
selenium.webdriver.support.select.Select
<select id="brand">
<option value ="volvo">Volvo</option>
<option value ="saab">Saab</option>
<option value="opel">Opel</option>
<option value="audi">Audi</option>
</select>
示例,選擇 Audi
## 查找并定位到 select
element_select = driver.find_element_by_css_selector('#brand')
## 用Select類的構(gòu)造方法,實例化一個對象 object_select
object_select = Select(element_select)
## 操作 object_select
object_select.select_by_index(3)
## 也可以這樣
object_select.select_by_value('audi')
## 還可以這樣
object_select.select_by_visible_text('Audi')
組合操作
自動化經(jīng)驗的積累,需要100%按照手工的步驟進行操作。
比如步驟如下:
- 點擊一個
<a id="customer_chosen">
- 自動產(chǎn)生了一個
<ul id="customer_list">
- 點擊
<ul>
的第五個<li>
代碼示例
driver.find_element_by_css_selector('#customer_chosen').click()
sleep(1)
driver.find_element_by_css_selector('#customer_list > li:nth-child(5)').click()
鼠標事件操作
Web測試中,有關(guān)鼠標的操作,不只是單擊,有時候還要做右擊、雙擊、拖動等操作。這些操作包含在ActionChains類中。
常用的鼠標方法:
- context_click() # 右擊
- double_click() # 雙擊
- drag_and_drop() # 拖拽
- move_to_element() # 鼠標停在一個元素上
- click_and_hold() # 按下鼠標左鍵在一個元素上
例子:
# 方法模擬鼠標右鍵,參考代碼如下:
# 引入ActionChains 類
from selenium.webdriver.common.action_chains import ActionChains
...
# 定位到要右擊的元素
right =driver.find_element_by_xpath("xx")
# 對定位到的元素執(zhí)行鼠標右鍵操作
ActionChains(driver).context_click(right).perform()
...
# 定位到要雙擊的元素
double = driver.find_element_by_xpath("xxx")
# 對定位到的元素執(zhí)行鼠標雙擊操作
ActionChains(driver).double_click(double).perform()
鍵盤事件操作
鍵盤操作經(jīng)常處理的如下:
代碼 | 描述 | ||
---|---|---|---|
send_keys(Keys.BACKSPACE) |
刪除鍵(BackSpace) | send_keys(Keys.SPACE) |
空格鍵(Space) |
send_keys(Keys.TAB) |
制表鍵(Tab) | ||
send_keys(Keys.ESCAPE) |
回退鍵(Esc) | ||
send_keys(Keys.ENTER) |
回車鍵(Enter) | ||
send_keys(Keys.CONTROL,'a') |
全選(Ctrl+A) | send_keys(Keys.CONTROL,'c') |
復(fù)制(Ctrl+C) |
代碼如下
from selenium import webdriver
# 引入Keys 類包
from selenium.webdriver.common.keys import Keys
import time
driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
# 輸入框輸入內(nèi)容
driver.find_element_by_id("kw").send_keys("selenium")
time.sleep(3)
# 刪除多輸入的一個m
driver.find_element_by_id("kw").send_keys(Keys.BACK_SPACE)
time.sleep(3)
# 輸入空格鍵+“教程”
driver.find_element_by_id("kw").send_keys(Keys.SPACE)
driver.find_element_by_id("kw").send_keys("教程")
time.sleep(3)
# ctrl+a 全選輸入框內(nèi)容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'a')
截圖操作
截圖的方法:save_screenshot(file)
1.5 unittest 單元測試框架
在上一節(jié),我們對 Selenium WebDriver 的使用,僅僅停留在讓網(wǎng)頁自動的進行操作的階段,并沒有對任何一個步驟進行“檢查”。當然,這樣沒有“檢查”的操作,實際上是沒有測試意義的。那么第一項,我們需要解決的便是“檢查”的問題。
所謂“檢查”,實際上就是斷言。對需要檢查的步驟操作,通過對預(yù)先設(shè)置的期望值,和執(zhí)行結(jié)果的實際值之間的對比,得到測試的結(jié)果。在這里,我們并不需要單獨的寫 if
語句進行各種判定,而是可以使用編程語言中對應(yīng)的單元測試框架,即可解決好此類問題。
目前 Java 語言主流的單元測試框架有 JUnit 和 TestNG。Python 語言主流的單元測試框架有 unittest 。本小節(jié)的內(nèi)容,主要介紹 unittest 的使用,探討單元測試框架如何幫助自動化測試。
接下來我們將會使用 Python 語言的unittest
框架展開“檢查”。unittest
框架的原本的名字是PyUnit。是從JUnit 這樣一個被廣泛使用的經(jīng)典的Java應(yīng)用開發(fā)的單元測試框架創(chuàng)造而來。類似的框架還有NUnit(.Net開發(fā)的單元測試框架)等。我們可以使用unittest框架為任意Python項目編寫可理解的單元測試集合。現(xiàn)在這個unittest已經(jīng)作為Python的標準庫模塊發(fā)布。我們安裝完P(guān)ython以后,便可以直接使用unittest。
使用unittest需要以下簡單的三步:
- 引入unittest模組
- 繼承unittest.TestCase基類
- 測試方法以
test
開頭
unittest 并未使用 Java 語言常見的注解方式,依舊停留在 比較早期的 Java 版本中依靠方法名稱進行識別的方式。主要有以下兩個固定名字的方法:
- setUp():在每個測試方法運行前,執(zhí)行。是測試前置條件。
- tearDown():在每個測試方法運行后執(zhí)行,是測試清理操作。
具體的代碼如下:
## 引入unittest模組
import unittest
## 定義測試類,名字為DemoTests
## 該類必須繼承unittest.TestCase基類
class DemoTests(unittest.TestCase):
## 使用'@'修飾符,注明該方法是類的方法
## setUpClass方法是在執(zhí)行測試之前需要先調(diào)用的方法
## 是開始測試前的初始化工作
@classmethod
def setUpClass(cls):
print("call setUpClass()")
## 每一個測試開始前的預(yù)置條件
def setUp(self):
print("call setUp()")
## 每一個測試結(jié)束以后的清理工作
def tearDown(self):
print("call tearDown()")
## 測試一(務(wù)必以test開頭)
def test_01(self):
print("call test_01()")
pass
## 測試三(務(wù)必以test開頭)
def test_02(self):
print("call test_02()")
pass
## 測試三(務(wù)必以test開頭)
def test_03(self):
print("call test_03()")
pass
## tearDownClass方法是執(zhí)行完所有測試后調(diào)用的方法
## 是測試結(jié)束后的清除工作
@classmethod
def tearDownClass(cls):
print("call tearDownClass()")
# 執(zhí)行測試主函數(shù)
if __name__ == '__main__':
## 執(zhí)行main全局方法,將會執(zhí)行上述所有以test開頭的測試方法
unittest.main(verbosity=2)
需要注意步驟:
- 引入 unittest 模組
- 繼承 unittest.TestCase 類
- 做測試用例的方法,方法以 test_ 開頭
- 附加 setUp(), tearDown(), 在每個 test_ 方法執(zhí)行前后 進行執(zhí)行
- 附加 setUpClass(), tearDownClass()
需要在 類實例化的對象,運行的開頭和結(jié)尾進行執(zhí)行。
加了星號(*)的步驟,可以不用。
上述代碼運行結(jié)果如下:
call setUpClass()
call setUp()
call test_01()
call tearDown()
call setUp()
call test_02()
call tearDown()
call setUp()
call test_06()
call tearDown()
call tearDownClass()
為什么選擇 unittest
清晰的單元測試框架,提供 TestCase, TestSuite, TextTestRunner 等基本類
unittest 是 原生 Python 的一部分
unittest 有第三方可用的 HTML 庫,可以輕松的生成 測試報告
-
unittest 的斷言配置使用
unittest 的斷言,屬于
TestCase
類,只要繼承了該類,均可以通過 self調(diào)用斷言方法 Method 檢查條件
assertEqual(a, b [, msg])
a == b,msg可選,用來解釋失敗的原因assertNotEqual(a, b [, msg]
a != b,msg可選,用來解釋失敗的原因assertTrue(x [, msg])
x 是真,msg可選,用來解釋失敗的原因assertFalse(x [, msg])
x 是假,msg可選,用來解釋失敗的原因assertIsNot(a, b [, msg])
a 不是 b,msg可選,用來解釋失敗的原因
1.6 為什么需要封裝 Selenium
-
什么是封裝
封裝是一個面向?qū)ο缶幊痰母拍睿敲嫦驅(qū)ο缶幊痰暮诵膶傩裕ㄟ^將代碼內(nèi)部實現(xiàn)進行密封和包裝,從而簡化編程。對Selenium進行封裝的好處主要有如下三個方面:
- 使用成本低
- 不需要要求所有的測試工程師會熟練使用Selenium,而只需要會使用封裝以后的代碼
- 不需要對所有的測試工程師進行完整培訓(xùn)。也避免工作交接的成本。
- 測試人員使用統(tǒng)一的代碼庫
- 維護成本低
- 通過封裝,在代碼發(fā)生大范圍變化和遷移的時候,不需要維護所有代碼,只需要變更封裝的部分即可
- 維護代碼不需要有大量的工程師,只需要有核心的工程師進行封裝的維護即可
- 代碼安全性
- 對作為第三方的Selenium進行封裝,是代碼安全的基礎(chǔ)。
- 對于任何的代碼的安全隱患,必須由封裝來解決,使得風(fēng)險可控。
- 使用者并不知道封裝內(nèi)部的代碼結(jié)構(gòu)。
1.7 如何封裝
這里面向的用戶是開發(fā)者,有一定基礎(chǔ)的,特別是面向?qū)ο箝_發(fā)基礎(chǔ)的開發(fā)者。封裝,最簡單直接就是函數(shù)、方法、類。這里不再復(fù)述。
1.8 Page-Object設(shè)計模式介紹
-
Page-Object設(shè)計模式的本質(zhì)
Page Object設(shè)計模式是Selenium自動化測試項目的最佳設(shè)計模式之一,強調(diào)測試、邏輯、數(shù)據(jù)和驅(qū)動相互分離。
Page Object模式是Selenium中的一種測試設(shè)計模式,主要是將每一個頁面設(shè)計為一個Class,其中包含頁面中需要測試的元素(按鈕,輸入框,標題等),這樣在Selenium測試頁面中可以通過調(diào)用頁面類來獲取頁面元素,這樣巧妙的避免了當頁面元素id或者位置變化時,需要改測試頁面代碼的情況。當頁面元素id變化時,只需要更改測試頁Class中頁面的屬性即可。
它的好處如下:
- 集中管理元素對象,便于應(yīng)對元素的變化
- 集中管理一個page內(nèi)的公共方法,便于測試用例的編寫
- 后期維護方便,不需要重復(fù)的復(fù)制和修改代碼
具體的做法如下:
- 創(chuàng)建一個頁面的類
- 在類的構(gòu)造方法中,傳遞 WebDriver 參數(shù)。
- 在測試用例的類中,實例化頁面的類,并且傳遞在測試用例中已經(jīng)實例化的WebDriver對象。
- 在頁面的類中,編寫該頁面的所有操作的方法
- 在測試用例的類中,調(diào)用這些方法
-
Page 如何劃分
一般通過繼承的方式,進行按照實際Web頁面進行劃分
-
Page-Object 類如何實現(xiàn)
實現(xiàn)的示例
-
Page 基類
- 設(shè)計了一個基本的 Page類,以便所有的頁面進行繼承,該類標明了一個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
- 私有的常量:存放元素的定位符
-
```python
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
```pypthon
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)
- 實例化Page
```python
self.main_page = MainPage(self.base_driver)
```
- 使用page的對象,調(diào)用成員方法
```python
self.main_page.open(self.base_url)
self.main_page.change_language(lang)
```
2. 構(gòu)建測試方案
2.1 數(shù)據(jù)驅(qū)動在自動化測試中的應(yīng)用
-
什么是數(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ū)動方式。當然數(shù)據(jù)驅(qū)動也可以結(jié)合單元測試框架的參數(shù)化測試進行編寫(此部分本文不做具體描述)。
無論使用了 哪一種(CSV 或者 MySQL),讀取數(shù)據(jù)后都要進行遍歷操作。
- 使用 csv
```python
import csv
csv_file = open("xxx.csv", "r", encoding="utf8")
csv_data = csv.reader(csv_file)
for row in csv_data:
# 進行測試
# 使用字典類型
data_to_test = {
"key1": row[0],
"key2": row[1]
}
csv_file.close()
```
- 使用 MySQL
```python
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:
# 進行測試
# 使用字典類型
data_to_test = {
"key1": row[0],
"key2": row[1]
}
cur.close()
connect.close()
```
-
需要掌握的知識點:
- python的字典類型
dict
類型 - python的讀寫文件
- python的讀寫數(shù)據(jù)庫
- for循環(huán)
- 注意資源的釋放
- 關(guān)閉數(shù)據(jù)庫游標和連接
- 關(guān)閉文件
- python的字典類型
2.2 測試方案的編碼實現(xiàn)
- main.py 測試入口
- runner.py 測試運行器
- cases 測試用例
- pages 測試頁面
- base 底層封裝與驅(qū)動
2.3 測試報告的生成
如何生成測試報告
測試報告的種類
-
HTML 測試報告的生成
HTML測試報告需要引入HTMLTestRunner
HTMLTestRunner是基于Python2.7的,我們的課程講義基于Python3.x,那么需要對這個文件做一定的修改。
測試的示例代碼如下
```python
# 聲明一個測試套件
suite = unittest.TestSuite()
# 添加測試用例到測試套件
suite.addTest(RanzhiTests("test_ranzhi_login"))
# 創(chuàng)建一個新的測試結(jié)果文件
buf = open("./result.html", "wb")
# 聲明測試運行的對象
runner = HTMLTestRunner.HTMLTestRunner(stream=buf,
title="Ranzhi Test Result",
description="Test Case Run Result")
# 運行測試,并且將結(jié)果生成為HTML
runner.run(suite)
# 關(guān)閉文件輸出
buf.close()
```