Selenium自動化測試工具的介紹與使用

Selenium自動化測試

什麼是自動化測試

自動化測試指軟件測試的自動化,在預設狀態下運行應用程序或者系統,預設條件包括正常和異常,最後評估運行
結果。總的概括即:將人爲驅動的測試行爲轉化爲機器執行的過程。
 
進入今天的主角:selenium 學習功能測試自動化首選工具就是selenium,它是一個web自動化測試工具。

selenium的特點

  • 支持多平臺:IE、Chrome、Firefox、edge、Safari
  • 支持多語言:Python、C、Java、C#、ruby、js
  • 免費小巧,支持分佈式測試用例的執行,可以把測試用例分佈到不同的測試機器執行,相當於分發機的功能。

自動化工具和自動化框架的區別

在學習selenium的過程中,我們寫測試腳本需要使用unittest框架,這個框架提供的類或函數可以實現我們想要實現的測試功能。自動化測試框架一般可以分爲兩個層次,上層是管理整個自動化測試的開發,執行以及維護,在比較龐大的項目中,它體現重要的作用,它可以管理整個自動測試,包括自動化測試用例執行的次序、測試腳本的維護、以及集中管理測試用例、測試報告和測試任務等。下層主要是測試腳本的開發,充分的使用相關的測試工具,構建測試驅動,並完成測試業務邏輯。

自動化工具本片博客主要說的是selenium,除此之外,還有QTPRational Robot jmeterappiumsoapuiLoadrunner等等,以後有時間再去學習一下。工具主要的特點就是每個工具都有自己獨特的操作界面供用戶使用,selenium中的Selenium IDE就是自動化測試工具。

 

自動化測試適合適用什麼項目,適合在什麼時機做這種自動化合適?

自動化測試分爲UI自動化測試和接口自動化測試。

UI自動化:適用於界面比較穩定的項目,前端開發完成之後,並且項目功能穩定。

接口自動化:適用於後端開發完成,並且項目功能穩定;後端完成之後,就可以進行接口測試。做接口自動化的工具:soupUI、jmeter。

實施自動化測試的前提條件:需求變動不頻繁、項目週期長、自動化測試腳本可以重複使用。

自動化適合的項目:產品型項目、機械並頻繁的測試(比如兼容性測試)

 自動化測試的優勢

降低大型系統的由於變更或者多期開發引起的大量的迴歸測試的人力投入,這可能是自動化測試最主要的任務,特
別是在程序修改比較頻繁時,效果是非常明顯的,自動化測試前期人力投入較多,但後期進入維護期後,可節省大
量人力,而手工測試後期需要增加大量人力用於迴歸測試。

  • 減少重複測試的時間,實現快速回歸測試
  • 創建優良可靠的測試過程,減少人爲錯誤
  • 可以運行更多更繁瑣的測試
  • 可以執行一些手工測試困難或不可能進行的測試
  • 更好的利用資源
  • 測試具有一致性和重複性
  • 測試腳本的重用性

 

selenium的實現原理

Selenium主要有三個版本,分別是Selenium1.0,Selenium2.0,Selenium3.0

Selenium1.0:包括selenium IDE、selenium RC、selenium Grid(支持分佈式)。Selenium1.0核心是selenium RC,所以Selenium1.0又稱爲Selenium RC,它是在瀏覽器中運用JavaScript應用,即用JavaScript代碼獲取頁面上的任何元素並執行各種操作

Selenium2.0:核心是WebDriver,Selenium+WebDriver

WebDriver的原理:

(1)啓動web瀏覽器,後臺會同時啓動基於Webdriver Wire協議的Web服務器作爲selenium的遠程服務器,並將其與瀏覽器綁定。綁定完成之後,服務器就開始監聽客戶端的操作請求。

(2)執行測試時,測試用例會作爲客戶端,將需要執行的頁面操作請求以HTTP請求的方式發送給遠程服務器。該HTTP請求的正文以Webdriver Wire協議規定的JSON格式來描述需要瀏覽器執行的具體操作。

(3)遠程服務器接收到請求後,會對請求進行解析,並將解析結果發送給Webdriver,由Webdriver實際執行瀏覽器的操作。

(4)Webdriver可以看作是直接操作瀏覽器的原生組件,所以搭建測試環境時,通常需要先下載瀏覽器對應的WebDriver。

業界有一個形象的比喻來理解:乘客和出租車的例子。乘客就是客戶端編寫的測試腳本,司機就是我們的WedDriver,而出租車就是瀏覽器。

Selenium3.0:增加了edge 、Safari的原生驅動

學習Selenium主要就是學習WebDriver常用的API

常見API詳解 

 #防止亂碼
#coding = utf-8
#想使用selenium的webdriver裏面的函數,首先需要把包導進來
from selenium import webdriver
import time
#我們需要操作的瀏覽器,這裏使用的是谷歌瀏覽器,也可以是IE、Firefox
driver =webdriver.Chrome()
#訪問百度首頁
driver.get('http://www.baidu.com')
 #停3秒鐘
time.sleep(3)
#百度輸入框的id爲kw,我們需要在輸入框中輸入Selenium,用send_keys進行輸入
driver.find_element_by_id("kw").send_keys("Selenium")
time.sleep(3)
#百度搜索框按鈕id叫su,找到後調用click函數模擬點擊操作
#和click有相同效果的是submit(),都可以用來點擊按鈕,submit主要是用於提交表單
driver.find_element_by_id("su").click()
time.sleep(3)
#退出並關閉窗口的每一個相關的驅動程序
driver.quit()

注意:關閉窗口主要有兩種方法,分別是close和quit。close是關閉當前瀏覽器窗口,quit不僅關閉窗口,還會徹底的退出webdriver,釋放driver server之間的連接,所以quit的關閉比close更徹底,它會更好的釋放資源。 

元素的定位 

對頁面元素進行定位是自動化測試的核心,我們要想操作一個對象,首先應該識別這個對象。在一個頁面中,每個對象屬性是唯一的我們需要用一系列的方式去定位我們要操作的對象。WebDriver提供了以下幾種方法定位元素:

  • id
  • name
  • class name
  • link text
  • partial link text
  • tag name
  • xpath
  • css selector

下面我將對每種定位方法進行舉例。

#coding = utf-8
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
######################百度輸入框的定位方式#################
#通過id定位
driver.find_element_by_id("kw").send_keys("selenium")
#通過name定位
driver.find_element_by_name("wd").send_keys(u"CSDN博客") #u表示以utf-8的格式輸入
#通過tag name定位
driver.find_element_by_tag_name("input").send_keys("Python")
#通過class name定位
driver.find_element_by_class_name("s_ipt").send_keys("Java")
#通過CSS定位
driver.find_element_by_css_selector("#kw").send_keys("C++")
#通過xpath定位
driver.find_element_by_xpath("//*[@id=kw]").send_keys(u"C語言")
#通過link test定位
driver.find_element_by_link_text("hao123").click()
#通過partial link test定位
driver.find_element_by_partial_link_text("hao").click()
driver.find_element_by_id("su").click()
time.sleep(3)
driver.quit()

智能等待

前面說過等待可以引入time包,從而在腳本中自由的添加休眠時間 。但是有時候我們不想等待一個固定的時間,於是可以通過implicitly_wait()方法方便的實現智能等待,它在一個時間範圍內智能等待。

selenium.webdriver.remote.webdriver.implicitly_wait(time_to_wait)隱式等待一個元素被發現或一個命令完成,這個方法每次會話只需要調用一次time_to_wait

具體用法:

# coding = utf-8
from selenium import webdriver
import time #調入time 函數
browser = webdriver.Chrome()
browser.get("http://www.baidu.com")
browser.implicitly_wait(30) #智能等待30秒
browser.find_element_by_id("kw").send_keys("selenium")
browser.find_element_by_id("su").click()
browser.quit()

 打印信息

使用print打印title和URL

#coding = utf-8
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('http://www.baidu.com')
print driver.title # 把頁面title 打印出來
print driver.current_url #打印url
driver.quit()

瀏覽器的操作

瀏覽器的最大化

調用啓動的瀏覽器不是全屏的,這樣雖然不影響腳本的執行,但有時會影響我們觀看腳本執行的變化。使用maximize_window()

#coding=utf-8
from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get("http://www.baidu.com")
print "瀏覽器最大化"
browser.maximize_window() #將瀏覽器最大化顯示
time.sleep(2)
browser.find_element_by_id("kw").send_keys("selenium")
browser.find_element_by_id("su").click()
time.sleep(3)
browser.quit()

 設置瀏覽器的寬高

最大化不夠靈活,所以我們可以隨意的設置瀏覽頁面的寬高,使用set_window_size(寬,高)

#coding=utf-8
from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get("http://www.baidu.com")
time.sleep(2)
#參數數字爲像素點
print "設置瀏覽器寬480、高800顯示"
browser.set_window_size(480, 800) 
time.sleep(3)
browser.quit()

瀏覽器的前進、後退

我們也可以實現瀏覽器的前進和後退

#coding=utf-8
from selenium import webdriver
import time
browser = webdriver.Chrome()
#訪問百度首頁
first_url= 'http://www.baidu.com'
print "now access %s" %(first_url)
browser.get(first_url)
time.sleep(2)
#訪問新聞頁面
second_url='http://news.baidu.com'
print "now access %s" %(second_url)
browser.get(second_url)
time.sleep(2)
#返回(後退)到百度首頁
print "back to %s "%(first_url)
browser.back()
time.sleep(1)
#前進到新聞頁
print "forward to %s"%(second_url)
browser.forward()
time.sleep(2)
browser.quit()

控制瀏覽器滾動條

#coding=utf-8
from selenium import webdriver
import time
#訪問百度
driver=webdriver.Chrome()
driver.get("http://www.baidu.com")
#搜索
driver.find_element_by_id("kw").send_keys("selenium")
driver.find_element_by_id("su").click()
time.sleep(3)
#將頁面滾動條拖到底部
js="var q=document.documentElement.scrollTop=10000"
driver.execute_script(js)
time.sleep(3)
#將滾動條移動到頁面的頂部
js="var q=document.documentElement.scrollTop=0"
driver.execute_script(js)
time.sleep(3)
driver.quit()
#excute_script(script,*args),在當前窗口同步執行JavaScript

鍵盤事件

鍵盤鍵用法

#coding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys #需要引入keys 包
import os,time
driver = webdriver.Chrome()
driver.get("http://demo.zentao.net/user-login-Lw==.html")
time.sleep(3)
driver.maximize_window() # 瀏覽器全屏顯示
driver.find_element_by_id("account").clear()
time.sleep(3)
driver.find_element_by_id("account").send_keys("demo")
time.sleep(3)
#tab 的定位相當於清除了密碼框的默認提示信息,等同上面的clear()
driver.find_element_by_id("account").send_keys(Keys.TAB)
time.sleep(3)
#通過定位密碼框,enter(回車)來代替登陸按鈕
driver.find_element_by_name("password").send_keys(Keys.ENTER)
time.sleep(3)
driver.quit()

鍵盤組合鍵用法

實現Ctrl+a,Ctrl+x

#coding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
#輸入框輸入內容
driver.find_element_by_id("kw").send_keys("selenium")
time.sleep(3)
#ctrl+a 全選輸入框內容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'a')
time.sleep(3)
#ctrl+x 剪切輸入框內容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'x')
time.sleep(3)
#輸入框重新輸入內容,搜索
driver.find_element_by_id("kw").send_keys("webdriver")
driver.find_element_by_id("su").click()
time.sleep(3)
driver.quit()

 鼠標事件

操作鼠標需要使用到ActionChains類

  • context_click()右擊
  • double_click()雙擊
  • drag_and_drop()拖動
  • move_to_element()移動
#coding=utf-8
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
import time
driver = webdriver.Chrome()
driver.get("http://news.baidu.com")
qqq =driver.find_element_by_xpath(".//*[@id='s_btn_wr']")
ActionChains(driver).context_click(qqq).perform() #右鍵
ActionChains(driver).double_click(qqq).perform() #雙擊
#定位元素的原位置
element = driver.find_element_by_id("s_btn_wr")
#定位元素要移動到的目標位置
target = driver.find_element_by_class_name("btn")
#執行元素的移動操作
ActionChains(driver).drag_and_drop(element, target).perform()
#ActionChains(driver)生成用戶的行爲。所有的行動都存儲在actionchains 對象。通過perform()存儲的行爲。
#move_to_element(menu)移動鼠標到一個元素中,menu 上面已經定義了他所指向的哪一個元素
#perform()執行所有存儲的行爲

 定位一組元素

前面我們定位元素時,只定位了某一個特定的對象,但有時我們需要定位一組對象,這就需要使用find_elements方法

#coding=utf-8
from selenium import webdriver
import time
import os
dr = webdriver.Chrome()
file_path = 'file:///' + os.path.abspath('D:\\Users\\320S-15\\seleniumTestHTML\\checkbox.html')
dr.get(file_path)
# 選擇頁面上所有的input,然後從中過濾出所有的checkbox 並勾選之
inputs = dr.find_elements_by_tag_name('input')
for input in inputs:
    if input.get_attribute('type') == 'checkbox':
        input.click()
time.sleep(2)
dr.quit()
#get_attribute獲取屬性值

多層框架/窗口定位

  • switch_to_frame()多層框架定位
  • switch_to_window() 多窗口定位

switch_to_frame()的功能是把當前定位的主體切換到frame裏,即frame中實際上嵌入了另一個頁面,而webdriver每次只能在一個頁面識別,因此才需要用switch_to_frame方法去獲取frame中嵌入的頁面,對那個頁面裏的元素進行定位。

switch_to _default_content:從frame中嵌入的頁面跳出,跳回到最外面的原始頁面中。

#coding=utf-8
from selenium import webdriver
import time
import os
browser = webdriver.Chrome()
file_path = 'file:///' + os.path.abspath('D:\\Users\\320S-15\\seleniumTestHTML\\frame.html')
browser.get(file_path)
browser.implicitly_wait(30)
#先找到到ifrome1(id = f1)
browser.switch_to_frame("f1")
#再找到其下面的ifrome2(id =f2)
browser.switch_to_frame("f2")
#下面就可以正常的操作元素了
browser.find_element_by_id("kw").send_keys("selenium")
browser.find_element_by_id("su").click()
time.sleep(3)
browser.quit()

 多層窗口定位:switch_to_window用法與switch_to_frame相同,如:driver.switch_to_window("windowname")

層級定位

對於這種定位的思路是:先點擊顯示出1個下拉菜單,然後再定位該下拉菜單所在的ul,再定位這個ul下的某個具體的link,如果要定位第一個下拉菜單中的Action選項:

#coding=utf-8
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
import time
import os
dr = webdriver.Chrome()
file_path = 'file:///' + os.path.abspath('D:\\Users\\320S-15\\seleniumTestHTML\\level_locate.html')
dr.get(file_path)
#點擊Link1鏈接(彈出下拉列表)
dr.find_element_by_link_text('Link1').click()
#找到id爲dropdown1的父元素
WebDriverWait(dr,10).until(lambda the_driver:#10秒內每隔500毫秒掃描1次頁面變化,當出現指定的元素結束
the_driver.find_element_by_id('dropdown1').is_displayed())#is_displayed()表示是否用戶可見
#在父親元件下找到link 爲Action 的子元素
menu = dr.find_element_by_id('dropdown1').find_element_by_link_text('Action')
#鼠標定位到子元素上
webdriver.ActionChains(dr).move_to_element(menu).perform()
time.sleep(2)
dr.quit()

 下拉框處理

對於下拉框裏的內容我們需要兩次定位,先定位到下拉框,再定位到下拉框內的選項

#coding=utf-8
from selenium import webdriver
import os,time
driver= webdriver.Chrome()
file_path = 'file:///' + os.path.abspath('D:\\Users\\320S-15\\seleniumTestHTML\\drop_down.html')
driver.get(file_path)
time.sleep(2)
#先定位到下拉框
m=driver.find_element_by_id("ShippingMethod")
#再點擊下拉框下的選項
m.find_element_by_xpath("//option[@value='10.69']").click()
time.sleep(3)
driver.quit()

 alert、confirm、prompt的處理

  • text返回alert、confirm、prompt中的文字信息
  • accept點擊確認按鈕
  • dismiss點擊取消按鈕

# -*- coding: utf-8 -*-
from selenium import webdriver
from time import sleep
import os
dr = webdriver.Chrome()
file_path = 'file:///' + os.path.abspath('D:\\Users\\320S-15\\seleniumTestHTML\\alert.html')
dr.get(file_path)
# 點擊鏈接彈出alert
dr.find_element_by_id('tooltip').click()
sleep(2)
alert = dr.switch_to.alert
alert.accept()
sleep(2)
dr.quit()

#coding:utf-8
from selenium import webdriver
from time import sleep
import os
driver=webdriver.Chrome()
driver.implicitly_wait(30)
file_path = 'file:///' + os.path.abspath('D:/Users/320S-15/seleniumTestHTML/send.html')
driver.get(file_path)
#點擊“請點擊”
driver.find_element_by_xpath("html/body/input").click()
#輸入內容
driver.switch_to.alert.send_keys('webdriver')
driver.switch_to.alert.accept()
sleep(5)
driver.quit()

div對話框的處理

更多的時候我們在實際應用中碰到的並不是簡單的警告框,而是提供更多功能的會話框

# -*- coding: utf-8 -*-
from selenium import webdriver
from time import sleep
import os
import selenium.webdriver.support.ui as ui
dr = webdriver.Chrome()
file_path = 'file:///' + os.path.abspath('D:/Users/320S-15/seleniumTestHTML/modal.html')
dr.get(file_path)
# 打開對話框
dr.find_element_by_id('show_modal').click()
sleep(3)
# 點擊對話框中的鏈接
link = dr.find_element_by_id('myModal').find_element_by_id('click')
link.click()
#dr.execute_script('$(arguments[0]).click()', link)
sleep(4)
# 關閉對話框
buttons =dr.find_element_by_class_name('modal-footer').find_elements_by_tag_name('button')
buttons[0].click()
sleep(2)
dr.quit()

上傳文件操作

上傳文件過程一般要打開一個本地窗口,從窗口選擇本地文件添加。所以selenium webdriver實現的方法是:只要定位上傳按鈕,通過send_keys添加本地文件路徑就可以了,絕對路徑和相對路徑都可以,關鍵是上傳的文件存在。

#coding=utf-8
from selenium import webdriver
import os,time
driver = webdriver.Chrome()
#腳本要與upload_file.html 同一目錄
file_path = 'file:///' + os.path.abspath('D:/Users/320S-15/seleniumTestHTML/upload.html')
driver.get(file_path)
#定位上傳按鈕,添加本地文件
driver.find_element_by_name("file").send_keys('E:\\320S-15\\test\\baidu.py')
time.sleep(2)
driver.quit()

unittest框架解析

unittest是Python的單元測試,它提供了創建測試用例、測試套件以及批量執行的方案。

作爲單元測試的框架,unittest也可以對程序最小模塊的一種敏捷化的測試。在自動化測試中,我們雖然不需要做白盒測試,但是必須知道所使用語言的單元測試框架。利用單元測試框架,創建一個類,該類繼承了unittest的TestCase,這樣可以把每個case看成是一個最小的單元,有測試容器組織起來,到時候直接執行,同時引入測試報告。

unittest各組件的關係如圖:

  • Test Fixture:測試固件,初始化和清理測試環境,比如創建臨時的數據庫、文件和目錄等,其中setUp和tearDown是最常用的
  • TestCase:單元測試用例,是編寫單元測試用例常用的類
  • TestSuite:單元測試用例的集合,TestSuite也是常用的類
  • TestRunner:執行單元測試
  • TestReport:生成測試報告

這裏我們簡單的創建一個測試用例:

# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException
import unittest, time
class Baidu1(unittest.TestCase):
    def setUp(self):
        print(u"setUp方法")
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(30)
        self.base_url = "http://www.baidu.com/"
        self.verificationErrors = []
        self.accept_next_alert = True
        #test fixture,清除環境
    def tearDown(self):
        print(u"tearDown方法")
        self.driver.quit()
        self.assertEqual([],self.verificationErrors)

    def test_hao(self):
         driver = self.driver
         driver.get(self.base_url + "/")
         driver.find_element_by_link_text("hao123").click()
         time.sleep(6)
         self.assertEqual(u"hao123_上網從這裏開始", driver.title)
   # @unittest.skip("skipping")
    def test_baidusearch(self):
         driver = self.driver
         driver.get(self.base_url + "/")
         driver.find_element_by_id("kw").click()
         driver.find_element_by_id("kw").clear()
         driver.find_element_by_id("kw").send_keys(u"雪之城")
         driver.find_element_by_id("su").click()
    #判斷element是否存在,可刪除
    def is_element_present(self, how, what):
         try:
             self.driver.find_element(by=how, value=what)
         except NoSuchElementException as e:
             return False
         return True
     #關閉alert,可刪除
    def is_alert_present(self):
        try: self.driver.switch_to.alert
        except NoAlertPresentException as e:
            return False
        return True

    def close_alert_and_get_its_text(self):
         try:
             alert = self.driver.switch_to.alert
             alert_text = alert.text
             if self.accept_next_alert:
                alert.accept()
             else:
                alert.dismiss()
             return alert_text
         finally: self.accept_next_alert = True

    if  __name__ == "__main__":
        unittest.main(verbosity=2)

可以增加verbosity參數,例如unittest.main(verbosity=2)。在主函數中,直接調用main() ,在main中加入verbosity=2 ,這樣測試的結果就會顯示的更加詳細。這裏的verbosity 是一個選項, 表示測試結果的信息複雜度,有三個值:

  • 0 ( 靜默模式): 你只能獲得總的測試用例數和總的結果比如總共100個,失敗20 成功80。
  • 1 ( 默認模式): 非常類似靜默模式只是在每個成功的用例前面有個“ . ” 每個失敗的用例前面有個“F”。
  • 2 ( 詳細模式): 測試結果會顯示每個測試用例的所有相關的信息。

批量執行腳本

構建測試套件

完整的單元測試需要執行很多個測試用例,開發人員通常需要編寫多個測試用例才能對某一軟件的功能進行比較完整的測試,這些相關的測試用例稱爲一個測試用例集,在unittest中是用TestSuite類表示的。

如果我們編寫了testbaidu1.py和testbaidu2.py兩個文件,那我們怎麼同時執行這兩個文件呢?

# encoding: utf-8
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException
import unittest, time, re
class Baidu2(unittest.TestCase):
#test fixture,初始化環境
 def setUp(self):
     print(u"setUp方法")
     self.driver = webdriver.Chrome()
     self.driver.implicitly_wait(30)
     self.base_url = "http://www.baidu.com/"
     self.verificationErrors = []
     self.accept_next_alert = True
 def tearDown(self):
    print(u"tearDown方法")
    self.driver.quit()
    self.assertEqual([], self.verificationErrors)
 #測試用例,必須以test開頭
 def test_baidusearch(self):
     driver = self.driver
     driver.get(self.base_url + "/")
     driver.find_element_by_id("kw").click()
     driver.find_element_by_id("kw").clear()
     driver.find_element_by_id("kw").send_keys(u"selenium")
     driver.find_element_by_id("su").click()
     driver.find_element_by_id("su").click()

 #判斷element是否存在,可刪除
 def is_element_present(self, how, what):
     try: self.driver.find_element(by=how, value=what)
     except NoSuchElementException as e:
        return False
     return True
 #判斷alert是否存在,可刪除
 def is_alert_present(self):
     try: self.driver.switch_to_alert()
     except NoAlertPresentException as e:
         return False
     return True
 #關閉alert,可刪除
 def close_alert_and_get_its_text(self):
    try:
        alert = self.driver.switch_to.alert
        alert_text = alert.text
        if self.accept_next_alert:
            alert.accept()
        else:
            alert.dismiss()
        return alert_text
    finally: self.accept_next_alert = True
 #test fixture,清除環境

if __name__ == "__main__":
#執行用例
 unittest.main(verbosity=2)
# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException
import unittest, time
class Baidu1(unittest.TestCase):
    def setUp(self):
        print(u"setUp方法")
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(30)
        self.base_url = "http://www.baidu.com/"
        self.verificationErrors = []
        self.accept_next_alert = True
        #test fixture,清除環境
    def tearDown(self):
        print(u"tearDown方法")
        self.driver.quit()
        self.assertEqual([],self.verificationErrors)

    def test_hao(self):
         driver = self.driver
         driver.get(self.base_url + "/")
         driver.find_element_by_link_text("hao123").click()
         time.sleep(6)
         self.assertEqual(u"hao123_上網從這裏開始", driver.title)
   # @unittest.skip("skipping")
    def test_baidusearch(self):
         driver = self.driver
         driver.get(self.base_url + "/")
         driver.find_element_by_id("kw").click()
         driver.find_element_by_id("kw").clear()
         driver.find_element_by_id("kw").send_keys(u"雪之城")
         driver.find_element_by_id("su").click()
    #判斷element是否存在,可刪除
    def is_element_present(self, how, what):
         try:
             self.driver.find_element(by=how, value=what)
         except NoSuchElementException as e:
             return False
         return True
     #關閉alert,可刪除
    def is_alert_present(self):
        try: self.driver.switch_to.alert
        except NoAlertPresentException as e:
            return False
        return True

    def close_alert_and_get_its_text(self):
         try:
             alert = self.driver.switch_to.alert
             alert_text = alert.text
             if self.accept_next_alert:
                alert.accept()
             else:
                alert.dismiss()
             return alert_text
         finally: self.accept_next_alert = True

    if  __name__ == "__main__":
        unittest.main(verbosity=2)

 這裏就需要構建測試套件,構建測試套件有三種方法,不同的情景下用不同的方法:

  • 直接加入測試方法:addTest()
  • makeSuit()/TestLoader()
  • discover()

addTest()的應用

當有多個或幾百個測試用例的時候,就需要一個測試容器(測試套件),把測試用例放在容器中進行執行,unittest模塊中提供了addSuite類來生成測試套件,使用該類的構造函數可以生成一個測試套件的實例,該類提供了addTest把每個測試用例加入到測試套件中。

將testbaidu1.py,testbaidu2.py,runall.py放在同一級目錄下,runall.py文件如下:

# -*- coding: utf-8 -*-
import unittest,csv
import os,sys
import time
#導入testbaidu1,testbaidu2
import testbaidu1
import testbaidu2
#手工添加案例到套件,
def createsuite():
     suite = unittest.TestSuite()
     #將測試用例加入到測試容器(套件)中
     suite.addTest(testbaidu1.Baidu1("test_baidusearch"))
     suite.addTest(testbaidu1.Baidu1("test_hao"))
     suite.addTest(testbaidu2.Baidu2("test_baidusearch"))
     return suite

if __name__=="__main__":
    suite = createsuite()
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

上述做法有兩個方便的地方,阻礙腳本的快速執行,必須每次修改runall.py:
1)需要導入所有的py文件,比如import testbaidu1,每新增一個需要導入一個
2)addTest需要增加所有的testcase,如果一個py文件中有10個case,就需要增加10次

於是就引入了makeSuit()/TestLoader()


makeSuite()和TestLoader()


在unittest 框架中提供了makeSuite() 的方法,makeSuite可以實現把測試用例類內所有的測試case組成的測試套件TestSuite ,unittest 調用makeSuite的時候,只需要把測試類名稱傳入即可。TestLoader 用於創建類和模塊的測試套件,一般的情況下TestLoader().loadTestsFromTestCase(TestClass)來加載測試類。

修改runall.py

# -*- coding: utf-8 -*-
import unittest
import time
import testbaidu1
import testbaidu2
#手工添加案例到套件,
def createsuite():
    #suite = unittest.TestSuite()
    #將測試用例加入到測試容器(套件)中
    ######### makeSuite() ############### 
    # suite.addTest(unittest.makeSuite(testbaidu1.Baidu1))
    # suite.addTest(unittest.makeSuite(testbaidu2.Baidu2))
    # return suite
    ########## TestLoader() ##############
     suite1 = unittest.TestLoader().loadTestsFromTestCase(testbaidu1.Baidu1)
     suite2 = unittest.TestLoader().loadTestsFromTestCase(testbaidu2.Baidu2)
     suite = unittest.TestSuite([suite1, suite2])
     return suite

if __name__=="__main__":
     suite = createsuite()
     runner = unittest.TextTestRunner(verbosity=2)
     runner.run(suite)

經過makeSuite()和TestLoader()的引入,我們不用一個py文件測試類,只需要導入一次即可。
那麼能不能測試類也不用每次添加指定呢?

discover()的應用

discover 是通過遞歸的方式到其子目錄中從指定的目錄開始, 找到所有測試模塊並返回一個包含它們對象的TestSuite ,然後進行加載與模式匹配唯一的測試文件,discover 參數分別爲discover(dir,pattern,top_level_dir=None)

__author__ = '320S-15'
# -*- coding: utf-8 -*-
import unittest,csv
import os,sys
import time
#手工添加案例到套件,
def createsuite():
    discover=unittest.defaultTestLoader.discover('../test',pattern='testbaidu*.py',top_level_dir=None)
    print discover
    return discover

if __name__=="__main__":
     suite=createsuite()
     runner = unittest.TextTestRunner(verbosity = 2)
     runner.run(suite)
     

unittest 框架默認加載測試用例的順序是根據ASCII 碼的順序,數字與字母的順序爲: 0~9,A~Z,a~z 。所以, TestAdd 類會優先於TestBdd 類被發現, test_aaa() 方法會優先於test_ccc() 被執行。對於測試目錄與測試文件來說, unittest 框架同樣是按照這個規則來加載測試例。

如果想忽略某個用例的執行,可在方法前加@unittest.skip("skipping")

unittest斷言

自動化測試中,對於每個單獨的case來說,一個case執行結果中,必然會有期望結果與實際結果,來判斷該case是通過還是失敗。在unittest的庫中提供了大量實用方法來檢查預期值與實際值來驗證case的結果。一般來說,檢查條件大體分爲等價性、邏輯比較以及其他,如果給定的斷言通過,測試會繼續執行到下一行的代碼,如果斷言失敗,對應的case測試會立即停止或者生成錯誤信息(一般可以打印錯誤信息即可),但不要影響其他的case執行。

unittest的單元測試提供了標準的XUnit斷言方法。下面是一些常見的斷言:

斷言方法 斷言描述
assertEqual(arg1,arg2,msg=None) 驗證arg1 = arg2,不等則fail
assertNotEqual(arg1,arg2,msg=None) 驗證arg1 != arg2,不等則fail
assertTrue(exxpr,msg=None) 驗證expr是true,如果false,則fail
assertFalse(exxpr,msg=None) 驗證expr是false,如果true,則fail
assertIs(arg1, arg2, msg=None)  驗證arg1、arg2是同一個對象,不是則fail
assertIsNot(arg1, arg2, msg=None)  驗證arg1、arg2不是同一個對象,是則fail
assertIsNone(expr, msg=None)  驗證expr是None,不是則fail
assertIsNotNone(expr, msg=None)  驗證expr不是None,是則fail
assertIn(arg1, arg2, msg=None) 驗證arg1是arg2的子串,不是則fail
assertNotIn(arg1, arg2, msg=None) 驗證arg1不是arg2的子串,是則fail
assertIsInstance(obj, cls, msg=None)  驗證obj是cls的實例,不是則fail
 assertNotIsInstance(obj, cls, msg=None) 驗證obj不是cls的實例,是則fail

如前面的例子中:self.assertEqual(u"hao123_上網從這裏開始", driver.title)

HTML報告生成

執行完腳本之後,還需要看到HTML報告,可以通過HTMLTestRunner.py生成測試報告。因爲我本機安裝的是Python2.7,所以需要下載HTMLTestRunner.py文件,下載地址:http://tungwaiyip.info/software/HTMLTestRunner.html。下載後將其放在testcase目錄下或放入安裝路徑...\Python2.7\Lib目錄下。

將上面的runall.py改成:

# -*- coding: utf-8 -*-
import unittest,csv
import os,sys
import time
import HTMLTestRunner
#手工添加案例到套件,
def createsuite():
    discover=unittest.defaultTestLoader.discover('../test',pattern='testbaidu*.py',top_level_dir=None)
    print discover
    return discover
if __name__=="__main__":
    curpath=sys.path[0]
    print(sys.path)
    print("=================")
    print(sys.path[0])
    #取當前時間
    now=time.strftime("%Y-%m-%d-%H %M %S",time.localtime(time.time()))
    if not os.path.exists(curpath+'/resultreport'):
        os.makedirs(curpath+'/resultreport')
    print("=================")
    print(time.time())
    print("=================")
    print(time.localtime(time.time()))
    print("=================")
    print(now)
    #在當前目錄下創建一個子目錄resultreport,然後在該子目錄下生成一個resultreport.html文件
    filename=curpath+'/resultreport/'+now+'resultreport.html'
    with open(filename,'wb') as fp:
         #生成html報告
         runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title=u'測試報告',description=u'用例執行情況',verbosity=2)
         suite=createsuite()
         runner.run(suite)

測試報告用瀏覽器打開如圖所示:

異常捕捉和錯誤截圖

用例不可能每次都運行成功,也有運行不成功的時候,如果可以捕捉錯誤,這樣就能更方便我們定位錯誤,因此unittest中有一個函數get_screenshot_as_file可以實現錯誤截圖功能。

腳本實現實例:

# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException
import unittest, time, re
import os
class Baidu1(unittest.TestCase):
    #test fixture,初始化環境
    def setUp(self):
     self.driver = webdriver.Chrome()
     self.driver.implicitly_wait(30)
     self.base_url = "http://www.baidu.com/"
     self.verificationErrors = []
     self.accept_next_alert = True

    #測試用例,必須以test開頭
    def test_hao(self):
     driver = self.driver
     driver.get(self.base_url)
     driver.find_element_by_link_text("hao123").click()
     time.sleep(2)
     try:
        self.assertEqual(u'hao123_上網從這裏開始', driver.title)
     except:
        self.savescreenshot(driver,'hao.png')

    #判斷element是否存在,可刪除
    def is_element_present(self, how, what):
     try: self.driver.find_element(by=how, value=what)
     except NoSuchElementException as e:
         return False
     return True

    #判斷alert是否存在,可刪除
    def is_alert_present(self):
     try: self.driver.switch_to.alert
     except NoAlertPresentException as e:
         return False
     return True
    #關閉alert,可刪除
    def close_alert_and_get_its_text(self):
     try:
        alert = self.driver.switch_to.lert
        alert_text = alert.text
        if self.accept_next_alert:
            alert.accept()
        else:
            alert.dismiss()
        return alert_text
     finally: self.accept_next_alert = True
    #test fixture,清除環境
    def tearDown(self):
        self.driver.quit()
        self.assertEqual([], self.verificationErrors)
    #截圖
    def savescreenshot(self,driver,file_name):
        if not os.path.exists('./image'):#如果不存在image文件夾,我們就創建之
            os.makedirs('./image')
            now=time.strftime("%Y%m%d-%H%M%S",time.localtime(time.time()))
            print(time.time())
            print(time.localtime(time.time()))
            print(now)
            #截圖保存
            driver.get_screenshot_as_file('./image/'+now+'-'+file_name)
            time.sleep(1)

if __name__ == "__main__":
#執行用例
 unittest.main(verbosity=2)

如下就是捕捉到的錯誤截圖:

 

數據驅動

如果需要多次執行一個案例,比如百度搜索,分別輸入中文、英文、數字,或者同類型的多個數據,這時我們就想實現一次運行多個測試用例、unittest沒有自帶數據驅動功能,所以如果使用unittest,同時又要使用數據驅動,那麼就可以使用ddt來完成。

需要安裝ddt,可以在控制檯輸入如下命令:

pip install ddt

python set.up.py install

ddt的使用方法

dd.ddt

裝飾類,也就是繼承自TestCase的類。

ddt.data:
裝飾測試方法。參數是一系列的值。
ddt.file_data:
裝飾測試方法。參數是文件名。文件可以是json 或者 yaml類型。
注意,如果文件以”.yml”或者”.yaml”結尾,ddt會作爲yaml類型處理,其他所有文件都會作爲json文件處理。
如果文件中是列表,每個列表的值會作爲測試用例參數,同時作爲測試用例方法名後綴顯示。
如果文件中是字典,字典的key會作爲測試用例方法的後綴顯示,字典的值會作爲測試用例參數。
ddt.unpack:
傳遞的是複雜的數據結構時使用。比如使用元組或者列表,添加unpack之後,ddt會自動把元組或者列表對應到多個參數上。字典也可以這樣處理。

實現一個Testddt類,測試ddt.data

#-*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException
import unittest, time, re
import os,sys,csv
from ddt import ddt, data, unpack ,file_data
def getCsv(file_name):
     rows=[]
     path=sys.path[0].replace('\test','')
     print(path)
     with open(path+'/data/'+file_name,'rb') as f:
         readers=csv.reader(f,delimiter=',',quotechar='|')
         next(readers,None)
         for row in readers:
            temprows=[]
            for i in row:
                temprows.append(i.decode('gbk'))
            rows.append(temprows)
         return rows
#引入ddt
@ddt
class Testddt(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(30)
        self.base_url = "http://www.baidu.com"
        self.verificationErrors = []
        self.accept_next_alert = True

    @data(*getCsv('test_baidu_data.csv'))
    @unpack
    def test_hao(self,value,expected_value):
        driver = self.driver
        driver.get(self.base_url + "/")
        driver.find_element_by_id("kw").clear()
        driver.find_element_by_id("kw").send_keys(value)
        driver.find_element_by_id("su").click()
        time.sleep(2)
        print(value)
        self.assertEqual(expected_value, driver.title)
        print expected_value
        print driver.title
        #判斷element是否存在,可刪除
    def is_element_present(self, how, what):
         try: self.driver.find_element(by=how, value=what)
         except NoSuchElementException as e: return False
         return True
         #判斷alert是否存在,可刪除
    def is_alert_present(self):
        try: self.driver.switch_to.alert
        except NoAlertPresentException as e:return False
        return True
    #關閉alert,可刪除
    def close_alert_and_get_its_text(self):
        try:
            alert = self.driver.switch_to.alert
            alert_text = alert.text
            if self.accept_next_alert:
                alert.accept()
            else:
                alert.dismiss()
            return alert_text
        finally: self.accept_next_alert = True
    #test fixture,清除環境
    def tearDown(self):
        self.driver.quit()
        self.assertEqual([], self.verificationErrors)
    def savescreenshot(self,driver,file_name):
        if not os.path.exists('./image'):
            os.makedirs('./image')
            now=time.strftime("%Y%m%d-%H%M%S",time.localtime(time.time()))
            #截圖保存
            driver.get_screenshot_as_file('./image/'+now+'-'+file_name)
            time.sleep(1)
if __name__ == "__main__":
#執行用例
    unittest.main(verbosity=2)

 test_baidu_data.csv:

data
周迅,周迅_百度搜索
林允,林允_百度搜索

測試ddt.file_data

#-*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException
import unittest, time, re
import os,sys,csv
from ddt import ddt, data, unpack ,file_data
@ddt
class Testddt(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(30)
        self.base_url = "http://www.baidu.com"
        self.verificationErrors = []
        self.accept_next_alert = True

    
    @file_data('test_baidu_data.json')
    def test_hao(self,value):
        driver = self.driver
        driver.get(self.base_url + "/")
        driver.find_element_by_id("kw").clear()
        driver.find_element_by_id("kw").send_keys(value)
        driver.find_element_by_id("su").click()
        time.sleep(2)
        print(value)
        # self.assertEqual(expected_value, driver.title)
        # print expected_value
        print driver.title
        #判斷element是否存在,可刪除
    def is_element_present(self, how, what):
         try: self.driver.find_element(by=how, value=what)
         except NoSuchElementException as e: return False
         return True
         #判斷alert是否存在,可刪除
    def is_alert_present(self):
        try: self.driver.switch_to.alert
        except NoAlertPresentException as e:return False
        return True
    #關閉alert,可刪除
    def close_alert_and_get_its_text(self):
        try:
            alert = self.driver.switch_to.alert
            alert_text = alert.text
            if self.accept_next_alert:
                alert.accept()
            else:
                alert.dismiss()
            return alert_text
        finally: self.accept_next_alert = True
    #test fixture,清除環境
    def tearDown(self):
        self.driver.quit()
        self.assertEqual([], self.verificationErrors)
    def savescreenshot(self,driver,file_name):
        if not os.path.exists('./image'):
            os.makedirs('./image')
            now=time.strftime("%Y%m%d-%H%M%S",time.localtime(time.time()))
            #截圖保存
            driver.get_screenshot_as_file('./image/'+now+'-'+file_name)
            time.sleep(1)
if __name__ == "__main__":
#執行用例
    unittest.main(verbosity=2)

 test_baidu_data.json:

[
 "hello",
 "selenium"
]

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章