寫在前面的:
最近在學習寫UI自動化腳本钮呀,并且正在自己的項目中實踐删掀。
在斷言的時候使用了try語句塊患膛,其實自己只是對python對try語句有基本的語法認識凡蚜,根本不知道怎么去用人断。結果出了個bug,老半天才找出原因來朝蜘。是因為自己把好多代碼都try了恶迈,結果有錯誤也沒曝露出來,很是憂桑芹务。當時有點懷疑人生了蝉绷,為什么沒報錯,但是卻沒有得到自己預想的結果呢枣抱?
有問題的代碼類似下面這樣(當時的代碼找不到了熔吗,下面代碼只是說明下錯誤):
#coding=utf-8
def div(a,b):
try:
result=a/b
return result
except Exception as e:
print("不知道發(fā)生了什么。佳晶。")
if __name__=="__main__":
print(div(1,0))
第一個禁忌:避免捕捉所有異常并吞噬它們桅狠。
看上去好像在處理異常的時候也打印了一句“不知道發(fā)生了什么”,但其實這里是把0作為除數(shù)的這種錯誤給吞噬了轿秧,也就是說中跌,代碼沒有對這種錯誤情況給出一個原因或者針對性對解決方案。
這種情況的解決辦法:
def div1(a,b):
"""
:param a: int或者float類型
:param b: int或者float類型
:return: 如果a菇篡,b不是int或者foat類型漩符,返回None;如果b=0,返回None;否則返回a/b的結果
"""
if not isinstance(a,(int,float)) :
return None
if not isinstance(b,(int,float)):
return None
if 0==b:
return None
result=a/b
return result
這種方法就是對于一個傳進來的參數(shù)做這種校驗驱还,因為你不知道別人會傳什么樣的參數(shù)嗜暴,所以要想到各種情況。
如果有的時候议蟆,代碼中很多地方都可能會發(fā)生異常闷沥,類型可預見,但是什么時候發(fā)生不知道咐容,這就要用TryExcept語句舆逃。比如webdriver的元素操作,有時候是元素的獲取有問題戳粒,有時候是操作元素的時候有問題路狮,常見都就是報錯ElementNotVisibleException、NoSuchElementException蔚约,還有一些其他的異常(如下圖)览祖。
你在做頁面操作的時候可能是一系列操作,那每個元素都要判斷是否正常返回嘛炊琉?那估計是要累死寶寶了展蒂。這時候用TryExcept就很方便。我是這樣處理的:
1.所有元素獲取和操作的部分try
2.如果except到了異常苔咪,根據異常的種類做不同的處理锰悼。ElementNotVisibleException、NoSuchElementException這兩種異常是我最常遇見的团赏,原因就是腳本不太穩(wěn)定箕般,大多數(shù)情況下是可以通過用例的,少數(shù)情況會報這兩個異常舔清,那么我希望遇到這兩種異常的時候重新跑用例腳本丝里,代碼不會因此而中斷曲初,增強了代碼的健壯性。遇到未知的Exception杯聚,此時一定要raise出來臼婆,raise之前也可以自己打點日志。
3.對于webdriver的assert方法也要try一下幌绍。因為assert方法只要失敗颁褂,就會拋AssertionError(感覺這種處理方式不是很好,但是又必須用selenium傀广,通常情況應該是正確或者錯誤都需要返回一個值颁独,而不是拋出一個異常呀)。except語句塊主要是記錄下測試用例失敗的數(shù)據伪冰,然后也要raise掉誓酒。如果不raise掉,那么這個用例結果會是成功(但實際是失敗了贮聂,只不過你但try讓它看起來成功了)丰捷。代碼如下:
def testToolButton(self):
logger.info("開始執(zhí)行工具按鈕的測試腳本...")
try:
# 設置瀏覽器為iphone模式打開
mobile_emulation = {'deviceName': 'Galaxy S5'}
options = webdriver.ChromeOptions()
options.add_experimental_option("mobileEmulation", mobile_emulation)
browser = webdriver.Chrome(chrome_options=options)
browser.get("https://plogin.m.jd.com/user/login.action")
logger.info('啟動瀏覽器,訪問"登錄"頁面...')
LoginAction.login('13180314708','TGB6yhn',browser,'http://test-jdread.jd.com/h5/m/')
logger.info('登錄完成...')
time.sleep(3)
logger.info('創(chuàng)建書城頁面寂汇,點擊工具按鈕病往,點擊"我的"按鈕...')
bookCityPage = BookCityPage(browser)
bookCityPage.toolButtonObj().click()
bookCityPage.minePageButtonObj().click()
time.sleep(3)
minePage=MinePage(browser)
try:
minePage.ExitButtonObj()#如果在"我的"頁面找到退出按鈕,則通過測試用例骄瓣,如果沒找到該按鈕則測試用例未通過
self.assertTrue(1==1)
logger.info('在"我的"頁面找【退出】按鈕停巷,成功,用例通過')
except AssertionError as e:
logger.debug('在"我的"頁面找到【退出】按鈕榕栏,失敗畔勤,用例不通過')
raise e
except ElementNotVisibleException as e:
logger.error("元素不可見..")
except NoSuchElementException as e:
logger.error("元素沒有找到..")
except Exception as e:
logger.error(e)
raise e
另外python允許程序員自己寫一些異常類,這時候要注意的就是禁忌二:拋出的異常應該解釋為什么扒磁,不能讓別人來猜測庆揪,你可能注意到,selenium定義類很多異常妨托,但是人家還是把異常信息的原因返回的很清楚缸榛。同理,我們自己在寫異常的時候兰伤,拋出時也要寫清楚為什么内颗,方便別人在用的時候不知道發(fā)生了什么就拋出了異常。以下代碼是一個自己定義異常的例子敦腔,ShortInputException類里面有兩個成員變量length和atleast均澳,分別代表輸入的長度和要求最短的長度,一旦length小于atleast就應該拋出異常。這兩個成員變量就能說明異常的原因:
#coding=utf-8
import sys
class ShortInputException(Exception):
def __init__(self,length,atleast):
Exception.__init__(self)
self.length=length
self.atleast=atleast
def assertInputLength(atleastLen):
s=input("請輸入一個長度大于%s的字符串:"%str(atleastLen))
if len(s)<atleastLen:
raise ShortInputException(len(s),atleastLen)
else:
return True
try:
assertInputLength(3)
except ShortInputException as e:
sys.stderr.write("%s is not enough long ,atleast %s \n"%(e.length,e.atleast))
禁忌三:不要使用異常來控制進程找前,這樣您的程序就很難理解和維護
雖然python是一種很“自由”的語言糟袁,tryExcept能做很多事情,筆者自己就寫過這樣的代碼:
#coding=utf-8
import time
def AIclick(element):
for i in range(60): # 循環(huán)60次躺盛,從0至59
if i >= 59: # 當i大于等于59時项戴,打印提示時間超時
print("timeout")
break
try: # try代碼塊中出現(xiàn)找不到特定元素的異常會執(zhí)行except中的代碼
element.click()
except: # 上面try代碼塊中出現(xiàn)異常,except中的代碼會執(zhí)行打印提示會繼續(xù)嘗試查找特定的元素id
print("wait for find element")
else:
break
time.sleep(1)
以上代碼是為了穩(wěn)定的獲取頁面元素颗品。但不得不說肯尺,這樣的代碼很難讓人理解沃缘。代碼邏輯簡單還好躯枢,一旦復雜,就會跳進自己挖的坑里槐臀。
禁忌四:如果有需要锄蹂,記得使用最后釋放資源
這里使用java來說明下,最有名的就是數(shù)據庫的操作:
package com.runoob.test;
import java.sql.*;
public class MySQLDemo {
// MySQL 8.0 以下版本 - JDBC 驅動名及數(shù)據庫 URL
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/RUNOOB";
// MySQL 8.0 以上版本 - JDBC 驅動名及數(shù)據庫 URL
//static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
//static final String DB_URL = "jdbc:mysql://localhost:3306/RUNOOB?useSSL=false&serverTimezone=UTC";
// 數(shù)據庫的用戶名與密碼水慨,需要根據自己的設置
static final String USER = "root";
static final String PASS = "123456";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
// 注冊 JDBC 驅動
Class.forName(JDBC_DRIVER);
// 打開鏈接
System.out.println("連接數(shù)據庫...");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
// 執(zhí)行查詢
System.out.println(" 實例化Statement對象...");
stmt = conn.createStatement();
String sql;
sql = "SELECT id, name, url FROM websites";
ResultSet rs = stmt.executeQuery(sql);
// 展開結果集數(shù)據庫
while(rs.next()){
// 通過字段檢索
int id = rs.getInt("id");
String name = rs.getString("name");
String url = rs.getString("url");
// 輸出數(shù)據
System.out.print("ID: " + id);
System.out.print(", 站點名稱: " + name);
System.out.print(", 站點 URL: " + url);
System.out.print("\n");
}
// 完成后關閉
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
// 處理 JDBC 錯誤
se.printStackTrace();
}catch(Exception e){
// 處理 Class.forName 錯誤
e.printStackTrace();
}finally{
// 關閉資源
try{
if(stmt!=null) stmt.close();
}catch(SQLException se2){
}// 什么都不做
try{
if(conn!=null) conn.close();
}catch(SQLException se){
se.printStackTrace();
}
}
System.out.println("Goodbye!");
}
}
禁忌五:處理異常后不要忘記清理或回滾
這里還是用java的數(shù)據庫事務操作來說明下:
public boolean DeleteSeatInfo(Seat seat){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String sql="UPDATE info_seat seat,info_classroom room SET seat.seat_student_name = ? ,seat.seat_empty = ? WHERE seat.classroom_id = room.classroom_id AND seat_id = ?";
try{
conn = DbUtils.getConnection();
conn.setAutoCommit(false);
ps = conn.prepareStatement(sql);
System.out.println(sql);
ps.setString(1,seat.getSeatName());
ps.setInt(2, seat.getIsEmpty());
ps.setString(3, seat.getSeatId());
//ps.addBatch();
//ps.executeBatch(); //批量執(zhí)行
ps.executeUpdate();
conn.commit();//提交事務
return true;
}catch(SQLException e){
try {
conn.rollback(); //進行事務回滾
} catch (SQLException ex) {
}
}finally {
DbUtils.close(conn, ps, rs);
}
return false;
}
文章參考以下大牛的:
http://news.51cto.com/art/201801/565741.htm
https://www.cnblogs.com/cnkemi/p/8985654.html
http://www.runoob.com/java/java-mysql-connect.html
https://blog.csdn.net/yy763496668/article/details/51488882