本人在網上查找了很多做自動化的教程和實例,偶然的一個機會接觸到了selenium,覺得非常好用。后來就在網上查閱各種selenium的教程,但是網上的東西真的是太多了,以至於很多東西參考完后無法系統的學習和應用,有一次在網上隨意搜索,找到了-蟲師-寫的《Selenium2自動化測試實戰基於Python語言》,覺得真心不錯,內容也很調理,為了方便自己學習和知識的整理,就把其中蟲師編寫的自動化項目教程整理一下,有興趣的可以去參看蟲師的博客http://www.cnblogs.com/fnng/
以下整理的只是書中自動化項目的知識內容,介紹怎么搭建自動化測試框架、執行自動化測試用例、生成自動化測試報告、發送測試報告郵件....,具體的Selenium和python語言基礎不做介紹
一、項目結構介紹
下面逐級介紹此目錄與文件的作用
mztstpro/
|-----bbs/
| |-----data/
| |-----report/
| |------image/
| |-----test_case/
| |------models/
| |----driver.py
| |----function.py
| |----myunit.py
| |------page_obj/
| |----*Page.py
| |------*_sta.py
|-----driver/
|-----package/
|-----run_bbs_test.py
|-----startip.bat
|-----自動化測試項目說明文檔.docx
1.mztestpro測試項目
bbs:用於存放BBS項目的測試用例、測試報告和測試數據等。
driver:用於存放瀏覽器驅動。如selenium-server-standalone-2.47.0jar、chromedriver.exe、IEDriverServer.exe等。在執行測試前根據執行場景將瀏覽器驅動復制到系統環境path目錄下。
package:用於存放自動化所用到的擴展包。例如:HTMLTestRunner.py屬於一個單獨模塊
run_bbs_test.py:項目主程序。用來運行社區(BBS)自動化用例。
startup.bat:用於啟動selenium server,默認啟動driver目錄下的selenium-server-standalone-2.44.0.jar。
自動化測試項目說明文檔.docx:介紹當前項目的架構、配置和使用說明。
2.bbs目錄
data:該目錄用來存放測試相關數據。
report:用於存放HTML測試報告。其下面創建了image目錄用於存放測試過程中的截圖。
test_case:測試用例目錄,用於存放測試用例及相關模塊。
3.test_case
models:該目錄下存放了一些公共的配置函數及公共類。
page_obj:該目錄用於存放測試用例的頁面對象(Page Object)。根據自定義規則,以“*Page.py”命名的文件為封裝的頁面對象文件。
*_sta.py:測試用例文件。根據測試文件匹配規則,以“*_sta.py”命名的文件被當作自動化測試用例執行。
二、編寫公共模塊
首先定義驅動文件:
...\mztestpro\bbs\test_case\models\driver.py
driver.py
# __author__ = 'Ztiny' # -*-coding:utf-8-*- from selenium.webdriver import Remote from selenium import webdriver # 啟動瀏覽器驅動 def browser(): driver = webdriver.Firefox() # host = '192.168.0.132:5555' #運行主機 :端口號(默認本機:127.0.0.1:4444) # dc = {'browserName':'internet explorer','version':'','platfrom':'WINDOWS','javascriptEnabled':True} # # dc = {'browserName':'firefox','version':'','platfrom':'ANY','javascriptEnabled':True,'marionette':False,}#指定瀏覽器 ('chrome','firefox') # driver = Remote(command_executor='http://' + host + '/wd/hub', # desired_capabilities=dc) return driver if __name__ == '__main__': dr = browser() dr.get("http://www.mayi.com") dr.quit()
定義瀏覽器驅動函數browser(),該函數可以進行配置,根據我們的需要,配置測試用例在不同的主機及瀏覽器下運行。
自定義測試框架類:
...\mztestpro\bbs\test_case\models\myunit.py
myunit.py
# __author__ = 'Ztiny' #-*-coding:utf-8-*- from selenium import webdriver from driver import browser import unittest class MyTest(unittest.TestCase): def setUp(self): self.driver = browser() self.driver.implicitly_wait(10) self.driver.maximize_window() def tearDown(self): self.driver.quit() if __name__ == '__main__': unittest.main()
定義MyTest()類用於集成unittest.TestCase類,因為筆者創建的所有測試類中setUp()與tearDown()方法所做的事情相同,所以,將他們抽象為MyTest()類,好處就是在編寫測試用例時不再考慮這兩個方法的實現。
定義截圖函數:
...\mztestpro\bbs\test_case\models\function.py
function.py
# __author__ = 'Ztiny' #-*-coding:utf-8-*- from selenium import webdriver import os #截圖函數 def insert_img(driver, file_name): base_dir = os.path.dirname(os.path.dirname(__file__)) base_dir = str(base_dir) base_dir = base_dir.replace('\\','/') base = base_dir.split('test_case')[0] file_path = base + "report/image/" + file_name driver.get_screenshot_as_file(file_path) if __name__ == '__main__': driver = webdriver.Ie() driver.get("http://www.baidu.com") insert_img(driver,'baidu.jpg') driver.quit()
創建截圖函數insert_img(),為了保持自動化項目的移植性,采用相對路徑的方式將測試截圖保持到.\report\image目錄中。
三、編寫Page Object
首先創建基礎Page基礎類(百度主頁為例):
...\mztestpro\bbs\test_case\page_obj\base.py
base.py
# __author__ = 'Ztiny' #-*-coding:utf-8-*- class Page(object): ''' 頁面基礎類,用於所有頁面的繼承 ''' baidu_url = 'https://www.baidu.com' def __init__(self,selenium_driver,base_url = baidu_url,parent =None): self.base_url = base_url self.driver = selenium_driver self.timeout = 30 self.parent = parent def _open(self,url): url = self.base_url + url self.driver.get(url) assert self.on_page(),'Did not land on %s' % url def find_element(self,*loc): return self.driver.find_element(*loc) def find_elements(self,*loc): return self.driver.find_elements(*loc) def open(self): self._open(self.url) def on_page(self): return (self.driver.current_url).encode('utf-8') == (self.base_url + self.url)
def script(self,src): return self.driver.execute_script(src)
創建頁面基礎類,通過__init__()方法初始化參數:瀏覽器驅動、URL地址、超時時長等。定義基本方法:open()用於打開BBS地址:find_element()和find_elements()分別用來定位單個與多個元素;創建script()方法可以更簡便地調用JavaScript代碼。當然還可以對更多的WebDriver方法進行重定義。
創建BBS登錄對象類:
...\mztestpro\bbs\test_case\page_obj\loginPage.py
loginPage.py
# __author__ = 'Ztiny' # -*-coding:utf-8-*- from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from base import Page from time import sleep class login(Page): ''' 用戶登錄界面 ''' url = '/' #Action baidu_login_user_loc = (By.LINK_TEXT,u'登錄') #彈出登錄窗口 def baidu_login(self): self.find_element(*self.baidu_login_user_loc).click() login_username_loc = (By.ID,'TANGRAM__PSP_8__userName') login_password_loc = (By.ID,'TANGRAM__PSP_8__password') login_button_loc = (By.ID,'TANGRAM__PSP_8__submit') #登錄用戶名 def login_username(self, username): self.find_element(*self.login_username_loc).clear() self.find_element(*self.login_username_loc).send_keys(username) #登錄密碼 def login_password(self, password): self.find_element(*self.login_password_loc).clear() self.find_element(*self.login_password_loc).send_keys(password) #登錄按鈕 def login_button(self): self.find_element(*self.login_button_loc).click() #統一登錄入口 def user_login(self, username="**********@qq.com", password="*********"): '''獲取用戶名和面登錄''' self.open() self.baidu_login() self.login_username(username) self.login_password(password) self.login_button() sleep(2)
user_error_hint_loc = (By.LINK_TEXT,u"賬號不能為空") pawd_error_hint_loc = (By.LINK_TEXT,u"密碼不能為空") user_login_success_loc = (By.LINK_TEXT,u'Ztiny') #用戶名錯誤提示 def user_error_hint(self): return self.find_element(*self.user_error_hint_loc).text #密碼錯誤提示 def pawd_error_hint(self): return self.find_element(*self.pawd_error_hint_loc).text #登錄成功用戶名 def user_login_success(self):
return self.find_element(*self.user_login_success_loc).text
創建登錄頁面對象,對用戶登錄頁面上的用戶名/密碼輸入框、登錄按鈕和提示信息等元素的定位進行封裝。除此之外,還創建user_login()方法作為系統統一登錄的入口。關於對操作步驟的封裝可以放在Page Object當中,也可以放在測試用例當中,這個主要根據具體的需求來衡量。這里之所以要放在Page Object當中,主要考慮到還會有其他的測試用例調用到該登錄方法。為username 和 password 入參數設置了默認值是為了方便其他用例在調用user_login()時不用再傳遞登錄用戶信息,因為該系統大多用例的執行使用該賬號即可,同時也方便了在賬號失效時的修改。
四、編寫測試用例
現在開始編寫測試用程序,因為前面已經做好了基礎工作,此時測試用例的編寫將會簡單的許多,更能集中精力考慮用例的設計和事項。
創建BBS登錄類:
...\mztestpro\bbs\test_case\login_sta.py
此處需要注意文件名的創建。例如,假設登錄頁的對象命名為loginPage.py,那么關於測試登錄的用例文件應該命名為login_sta.py,這樣方便后期用例報錯時問題跟蹤。盡量把一個頁面上的元素定位封裝到一個“*Page.py”文件中,把針對這個頁面的測試用例集中到一個“*_sta.py”文件中
login_sta.py
# __author__ = 'Ztiny' #-*-coding:utf-8-*- from time import sleep import unittest, random ,sys sys.path.append("./models") sys.path.append("./page_obj") from models import myunit, function from page_obj.loginPage import login class loginTest(myunit.MyTest): '''測試用戶登錄''' def user_login_verify(self, username='',password=''): login(self.driver).user_login(username,password)
def test_login1(self): '''用戶名、密碼為空登錄''' self.user_login_verify() po = login(self.driver) self.assertEqual(po.user_error_hint(),"賬號不能為空") self.assertEqual(po.pawd_error_hint()."密碼不能為空") function.insert_img(self.driver,"user_pawd_empty.jpg") def test_login2(self): '''用戶名正確,密碼為空登錄''' self.user_login_verify(username="*******") po = login(self.driver) self.assertEqual(po.pawd_error_hint(),"密碼不能為空") function.insert_img(self.driver,"paqd_empty.jpg") def test_login3(self): '''用戶名為空,密碼正確''' self.user_login_verify(password="*******") po = login(self.driver) self.assertEqual(po.user_error_hint(),"賬號不能為空") function.insert_img(self.driver,"user_empty.jpg") def test_login4(self): '''用戶名與密碼不匹配''' character = random.choice('abcdefghijklmnopqrstuvwxyz') username = "zhangsan" + character self.user_login_verify(username=username,password="123456") po = login(self.driver) self.assertEqual(po.pawd_error_hint(),"密碼與賬號不匹配") function.insert_img(self.driver,"user_pwad_error.jpg") def test_login5(self): '''用戶名、密碼正確''' self.user_login_verify(username='********@qq.com',password='********') sleep(2) po = login(self.driver) self.assertEqual(po.user_login_success(), u'Ztiny') function.insert_img(self.driver ,"user_pwd_ture.jpg") if __name__ == '__main__': unittest.main()
首先創建loginTest()類,繼承myunit.Mytest()類,關於Mytest()類的實現,請翻看前面代碼。這樣就省去了在每一個測試類中實現一遍setUp()和tearDown()方法。
創建user_login_verify()方法,並調用loginPage.py中定義的user_login()方法。為什么不直接調用呢?因為user_login()的入參已經設置了默認值,原因前面已經解釋,這里需要重新將其入參的默認值設置為空即可。
前三條測試用例很好理解,分別驗證:
- 用戶名密碼為空,點擊登錄
- 用戶名正確,密碼為空,點擊登錄
- 用戶名為空,密碼正確,點擊登錄
第四條用例驗證錯誤用戶名和密碼登錄。在當前系統中如果反復使用固定錯誤的用戶名和密碼,系統會彈出驗證碼輸入框。為了避免這種情況的發生,就需要用戶名進行隨機變化,此處的做法用固定前綴“zhangsan”,末尾字符從a~z中隨機一個字符與前綴進行拼接。
第五條用例驗證正確的用戶名和密碼登錄,通過獲取用戶名作為斷言信息
在上面的測試用例中,每條測試用例結束時都調用function.py文件中的insert_img函數進行截圖。當用例運行完成后,打開...\report\image\目錄將會看到用例執行的截圖文件,如圖:
五、執行測試用例
為了在測試用例運行過程中不影響做其他事,筆者選擇調用遠程主機或虛擬機來運行測試用例,那么這里就需要使用Selenium Grid(其包含Selenium Server)來調用遠程節點。
創建...\mztestpro\startup.bat文件,用於啟動...\mztestpro\driver\目錄下的Selenium Server。
startup.bat
@echo off echo 啟動hub java -jar .\mztestpro\driver\selenium-server-standalone-2.40.0.jar -role hub -host 192.168.0.102 -port 4444 echo 啟動node java -jar .\mztestpro\driver\selenium-server-standalone-2.40.0.jar -role node -port 5555 -hub http://192.168.0.102:4444/grid/register
雙擊startup.bat文件,啟動Selenium Server創建主hub節點。在遠程主機或虛擬機中通樣需要啟動Selenium Server創建node節點。
創建用例執行程序:...\mztestpro\run_bbs_test.py
run_bbs_test.py
# __author__ = 'Ztiny' #-*-coding:utf-8-*- from HTMLTestRunner import HTMLTestRunner from email.mime.text import MIMEText import smtplib import unittest import time import os # =========================郵件接收者============================ mailto_list=["********@qq.com"] #============= 設置服務器,用戶名、口令以及郵箱的后綴=============== mail_host="smtp.163.com" mail_user="******@163.com" mail_pass="*******" #===========================發送郵件============================ def send_mail(to_list,file_new): ''''' to_list:發給誰 sub:主題 content:內容 send_mail("aaa@126.com","sub","content") ''' f = open(file_new, 'rb') mail_body = f.read() f.close() me=mail_user msg = MIMEText(mail_body,'html','utf-8') msg['Subject'] = u'自動化測試報告' msg['From'] = me msg['To'] = ";".join(to_list) try: s = smtplib.SMTP() s.connect(mail_host,25) s.login(mail_user,mail_pass) s.sendmail(me, to_list, msg.as_string()) s.close() return True except Exception, e: print str(e) return False #==============查找測試報告目錄,找到最新生成的測試報告文件========== def new_report(testreport): lists = os.listdir(testreport) lists.sort(key=lambda fn:os.path.getatime(testreport + "\\" + fn)) file_new = os.path.join(testreport,lists[-1]) print(file_new) return file_new if __name__ == '__main__': now = time.strftime("%Y-%m-%d %H_%M_%S") filename = './bbs/report/' + now +'result.html' fp = open(filename,'wb') runner = HTMLTestRunner(stream=fp, title=u'百度登錄自動化測試報告', description=u'環境 :window 7 瀏覽器:firefox') discover = unittest.defaultTestLoader.discover('./bbs/test_case', pattern='*_sta.py') runner.run(discover) fp.close() file_path = new_report('./bbs/report/') if send_mail(mailto_list,file_path): print u"發送成功" else: print u"發送失敗"
執行過程中並沒有任何改動,集成HTMLTestRunner生成的HTML測試報告,以及集成自動發郵件功能等。唯一需要注意的是,腳本中的路徑建議使用相對路徑,以便於項目被移動到任意目錄下執行。
打開...\mztestpro\driver.py 文件,修改腳本運行的節點及瀏覽器。現在可以通過運行run_bbs_test.py來執行測試項目了。
小結:
如果你完成了前面的操作,那么這只是自動化項目的開始,不過,我們已經把基本架構設計完成,后面大部分工作就是編寫各個頁面的*Page.py以及測試用*_sta.py。在這個過程中會遇到各種各樣的問題,如元素定位、架構的擴展,需要讀者自己去克服這些問題。
PS:對自動化感興趣的同學可以加QQ群:539725853,歡迎加入交流!
