一般在前期調試代碼的時候,咱們會使用print在IDE控制檯打印一些信息,判斷運行狀況。但在運行整個自動化測試項目的過程當中,經過print打印信息的方式獲取運行狀況顯然行不通。
這時就須要收集日誌,每次運行後經過查看日誌來獲取項目運行狀況。那麼咱們該如何獲取日誌?python
在項目開發或測試過程當中,項目運行一旦出現問題,記錄日誌信息就顯得尤其重要。主要經過日誌來定位問題,就比如偵探人員要根據現場留下的線索來推斷案情。android
代碼在運行的過程當中會出現不一樣的狀況,如調試信息、警告信息、報錯等,那麼採集日誌時就須要對這些日誌區分級別管理,這樣才能更精確地定位問題。日誌級別通常分類以下(以嚴重程度遞增排序):web
級別
|
什麼時候使用
|
---|---|
DEBUG | 調試信息,也是最詳細的日誌信息 |
INFO | 證實事情按預期工做 |
WARNING | 代表發生了一些意外,或不久的未來會發生問題(如 磁盤滿了),軟件仍是正常工做 |
ERROR | 因爲更嚴重的問題,軟件已經不能執行一些工做了 |
CRITICAL | 嚴重錯誤,代表軟件已經不能繼續運行了 |
日誌級別排序爲:CRITICAL > ERROR > WARNING > INFO > DEBUGapp
日誌採集時設置低級別的日誌,能採集到更高級別的日誌,但不能採集到更低級別的日誌。模塊化
例如:設置的日誌級別爲info級別,就只能採集到info、warning、error、critical級別的日誌,不能採集到debug級別的日誌。設置的日誌級別爲debug級別的話則能採集到全部級別的日誌。默認設置級別爲WARNING。函數
在自動化測試項目中,一般在通常狀況時使用info日誌,預計報錯則使用error日誌。測試
將日誌格式化是爲了提升日誌的可閱讀性,好比:時間+模塊+行數+日誌級別+日誌具體信息 的日誌格式。若是輸出的日誌信息雜亂無章,就不利於問題的定位。以下所示就是日誌格式化輸出,很是便於閱讀查看。ui
2020-09-30 10:45:05,119 logging_test.py[line:7] DEBUG this is debug message. 2020-09-30 10:45:05,119 logging_test.py[line:9] INFO this is info message. 2020-09-30 10:45:05,119 logging_test.py[line:11] WARNING this is warning message. 2020-09-30 10:45:05,120 logging_test.py[line:13] ERROR this is error message. 2020-09-30 10:45:05,120 logging_test.py[line:15] CRITICAL this is critical message.
一般,在一個項目中會有不少的日誌採集點,日誌採集點的設置必須結合業務來肯定。this
好比在執行修改登陸密碼用例前插入「開始執行修改登陸密碼用例...」的日誌信息。再好比在登陸代碼執行前能夠插入「準備登陸...」日誌信息。編碼
若是在登陸完成後,再設置登陸的提示日誌就會給人形成誤解,沒法判斷究竟是登陸以前的問題仍是登陸以後的問題,所以日誌採集點的位置很重要。
logging爲python自帶的日誌模塊,提供了通用的日誌系統,包括不一樣的日誌級別。logging可以使用不一樣的方式記錄日誌,如使用文件,HTTP GET/POST,SMTP,Socket等方式記錄。一般狀況下,咱們使用文件記錄日誌信息,文件格式通常爲.txt或.log文件。
詳細內容可查看logging模塊官方文檔,使用時須要導入:
import logging
logging.basicConfig(**kwargs)
filename 指定日誌名稱或完整路徑,如:E:/app-ui-autotest/log/log.txt
filemode 指定打開文件的模式(若是文件打開模式未指定,則默認爲'a')
常見的文件讀寫方式:
format 指定日誌輸出格式
level 將根記錄器級別設置爲指定級別
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 import logging logging.basicConfig(filename='./log.txt', level=logging.DEBUG, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s') logging.debug('This is debug message') logging.info('This is info message') logging.warning('This is warning message') logging.error('This is error message') logging.critical('This is critical message')
控制檯輸出結果:
2020-09-30 10:45:05,119 logging_test.py[line:7] DEBUG This is debug message. 2020-09-30 10:45:05,119 logging_test.py[line:9] INFO This is info message. 2020-09-30 10:45:05,119 logging_test.py[line:11] WARNING This is warning message. 2020-09-30 10:45:05,120 logging_test.py[line:13] ERROR This is error message. 2020-09-30 10:45:05,120 logging_test.py[line:15] CRITICAL This is critical message.
logging.basicConfig(filename='log.txt', level=logging.INFO, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s') logging.debug('This is debug message') logging.info('This is info message') logging.warning('This is warning message') logging.error('This is error message') logging.critical('This is critical message')
輸出格式:
2020-09-30 10:45:05,119 logging_test.py[line:9] INFO This is info message. 2020-09-30 10:45:05,119 logging_test.py[line:11] WARNING This is warning message. 2020-09-30 10:45:05,120 logging_test.py[line:13] ERROR This is error message. 2020-09-30 10:45:05,120 logging_test.py[line:15] CRITICAL This is critical message.
注意:
相較於控制檯打印日誌,文件保存日誌的區別在於basicConfig()方法中加入了filename參數(即文件的完整路徑)。
保存日誌至文件示例中,由於參數level=logging.INFO,因此DEBUG級別的日誌未輸出
logging模塊包括Logger,Handler,Filter,Formatter四個部分。
使用日誌流採集日誌時,須先建立Logger實例,即建立一個記錄器(若是沒有顯式的進行建立,則默認建立一個root logger,並應用默認的日誌級別WARNING,Handler和Formatter),而後作如下三件事:
Handler處理器做用是,將日誌記錄發送至合適的路徑。如發送至文件或控制檯,此時須要使用兩個處理器,用於輸出控制檯的處理器,另外一個是用於輸出文件的處理器。經過 addHandler() 方法添加處理器 。經常使用的處理器類型有如下兩種:
將日誌信息發送至sys.stdout、sys.stderr或任何相似文件流對象,如在Pycharm IDE上顯示的日誌信息。
構造函數爲:StreamHandler(strm)。參數strm是一個文件對象,默認是sys.stderr。
將日誌記錄輸出發送至磁盤文件。 它繼承了StreamHandler的輸出功能,不過FileHandler會幫你打開這個文件,用於向一個文件輸出日誌信息。
構造函數爲:FileHandler(filename, mode)。參數filename爲文件名(文件完整路徑),參數mode爲文件打開方式,默認爲'a'即在文末追加。
自動化測試使用這兩種類型就夠了,其餘還有RotatingFileHandler、TimedRotatingFileHandler、NullHandler等處理器,有興趣能夠查找資料瞭解。
顧名思義是用於過濾,Handlers 與 Loggers 使用 Filters 能夠完成比級別更復雜的過濾。很少作介紹,有興趣能夠查找資料瞭解。
Formatter用於設置日誌的格式與內容,默認的時間格式爲%Y-%m-%d %H:%M:%S,更多格式以下:
格式
|
描述
|
---|---|
%(levelno)s | 打印日誌級別的數值 |
%(levelname)s | 打印日誌級別的名稱 |
%(pathname)s | 打印當前執行程序的路徑 |
%(filename)s | 打印當前執行程序的名稱 |
%(funcName)s | 打印日誌的當前函數 |
%(lineno)d | 打印日誌的當前行號 |
%(asctime)s | 打印日誌的時間 |
%(thread)d | 打印線程ID |
%(threadName)s | 打印線程名稱 |
%(process)d | 打印進程ID |
%(message)s | 打印日誌信息 |
根據logging的模塊化來編寫代碼,思路參考以下。
目錄結構
logging_test.py
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 import logging # 第一步,建立日誌記錄器 # 1,建立一個日誌記錄器logger logger = logging.getLogger() # 2,設置日誌記錄器的日誌級別,這裏的日誌級別是日誌記錄器能記錄到的最低級別,區別於後面Handler裏setLevel的日誌級別 logger.setLevel(logging.DEBUG) # 第二步,建立日誌處理器Handler。這裏建立一個Handler,用於將日誌寫入文件 # 3,建立一個Handler,用於寫入日誌文件,日誌文件的路徑自行定義 logFile = './log.txt' fh = logging.FileHandler(logFile, mode='a', encoding='utf-8') # 4,設置保存至文件的日誌等級 fh.setLevel(logging.INFO) # 第三步,定義Handler的輸出格式 # 5,日誌輸出格式定義以下 format= logging.Formatter('%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s') # 6,設置 寫入日誌文件的Handler 的日誌格式 fh.setFormatter(format) # 第四步,將Handler添加至日誌記錄器logger裏 logger.addHandler(fh) # 一樣的,建立一個Handler用於控制檯輸出日誌 ch = logging.StreamHandler() ch.setLevel(logging.INFO) ch.setFormatter(format) logger.addHandler(ch) # 輸出日誌 logger.info("This is info message") logger.warning("This is warning message") logger.error("This is error message") logger.critical("This is critical message")
Pycharm運行logging_test.py模塊,log.txt以及Pycharm控制檯獲得以下結果:
2020-10-07 15:54:04,752 test.py[line:3] INFO This is info message 2020-10-07 15:54:04,752 test.py[line:4] WARNING This is warning message 2020-10-07 15:54:04,752 test.py[line:5] ERROR This is error message 2020-10-07 15:54:04,752 test.py[line:6] CRITICAL This is critical message
給登陸今日頭條app的操做添加日誌採集。
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 import logging from appium import webdriver logging.basicConfig(filename='./testLog.log', level=logging.INFO, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s') def android_driver(): desired_caps = { "platformName": "Android", "platformVersion": "10", "deviceName": "PCT_AL10", "appPackage": "com.ss.android.article.news", "appActivity": ".activity.MainActivity", "unicodeKeyboard": True, "resetKeyboard": True, "noReset": True, } logging.info("啓動今日頭條APP...") driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps) return driver def login_opera(driver): '''登陸今日頭條操做''' logging.info("開始登錄今日頭條APP...") try: driver.find_element_by_id("com.ss.android.article.news:id/cji").click() # 點擊【我知道了】 driver.find_element_by_id("android:id/button1").click() # 點擊權限管理-肯定按鈕 driver.find_element_by_xpath("//android.widget.TabWidget/android.widget.RelativeLayout[@index=3]").click() # 點擊未登陸 driver.find_element_by_id("com.ss.android.article.news:id/a10").click() # 未登陸頁點擊登陸按鈕 driver.find_element_by_id("com.ss.android.article.news:id/bgh").click() # 登陸頁點擊「。。。」 driver.find_element_by_xpath("//android.widget.LinearLayout[@index=4]").click() # 選擇密碼登陸 driver.find_element_by_id("com.ss.android.article.news:id/bu").send_keys("18768124236") # 輸入帳號 driver.find_element_by_id("com.ss.android.article.news:id/c5").send_keys("xiaoqq3915172") # 輸入密碼 driver.find_element_by_id("com.ss.android.article.news:id/a2o").click() # 點擊登陸 except Exception as e: logging.error("登陸錯誤,緣由爲:{}".format(e)) else: logging.info("登錄成功...") driver = android_driver() login_opera(driver)
登陸成功則日誌輸出以下:
2020-09-30 18:20:05,119 logging_test.py[line:21] INFO 啓動今日頭條APP... 2020-09-30 18:20:10,119 logging_test.py[line:27] INFO 開始登錄今日頭條APP... 2020-09-30 18:21:07,120 logging_test.py[line:41] INFO 登錄成功...
上面示例代碼成功地獲取了日誌信息,但這種寫法只能做用於當前模塊。而一個自動化測試項目每每有多個模塊,若是在每一個須要獲取日誌的模塊都使用這樣的方式,顯然是不方便維護的。那麼咱們須要怎麼解決呢?
使用日誌流處理流程。提供如下兩種思路:
思路1:使用python代碼實現日誌配置。先建立日誌記錄器,並設置好Handler與日誌格式,如上面的logging_test.py模塊構造logger,其餘模塊採集日誌時直接調用。
思路2:將日誌的格式、輸出路徑等參數抽離出來放置在專門的配置文件裏,如logging.conf,使用專門的模塊處理,使用時直接在模塊調用便可。
目錄結構
test.py中須要採集日誌時,從logging_test.py導入logger便可。也能夠將logging_test.py裏的代碼進行進一步的封裝,再調用,這裏僅僅只是示例。
logging_test.py
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 import logging # 建立日誌記錄器 logger = logging.getLogger() logger.setLevel(logging.DEBUG) # 設置日誌輸出格式 format= logging.Formatter('%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s') # 建立一個Handler用於將日誌寫入文件 logFile = './log.txt' fh = logging.FileHandler(logFile, mode='a', encoding='utf-8') fh.setLevel(logging.INFO) fh.setFormatter(format) logger.addHandler(fh) # 一樣的,建立一個Handler用於控制檯輸出日誌 ch = logging.StreamHandler() ch.setLevel(logging.INFO) ch.setFormatter(format) logger.addHandler(ch)
test.py
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 from appium import webdriver from log.logging_test import logger def android_driver(): desired_caps = { "platformName": "Android", "platformVersion": "10", "deviceName": "PCT_AL10", "appPackage": "com.ss.android.article.news", "appActivity": ".activity.MainActivity", "unicodeKeyboard": True, "resetKeyboard": True, "noReset": True, } logger.info("啓動今日頭條APP...") driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps) return driver def login_opera(driver): '''登陸今日頭條操做''' logger.info("開始登錄今日頭條APP...") try: driver.find_element_by_id("com.ss.android.article.news:id/cji").click() # 點擊【我知道了】 driver.find_element_by_id("android:id/button1").click() # 點擊權限管理-肯定按鈕 driver.find_element_by_xpath("//android.widget.TabWidget/android.widget.RelativeLayout[@index=3]").click() # 點擊未登陸 driver.find_element_by_id("com.ss.android.article.news:id/a10").click() # 未登陸頁點擊登陸按鈕 driver.find_element_by_id("com.ss.android.article.news:id/bgh").click() # 登陸頁點擊「。。。」 driver.find_element_by_xpath("//android.widget.LinearLayout[@index=4]").click() # 選擇密碼登陸 driver.find_element_by_id("com.ss.android.article.news:id/bu").send_keys("18768124236") # 輸入帳號 driver.find_element_by_id("com.ss.android.article.news:id/c5").send_keys("xiaoqq3915172") # 輸入密碼 driver.find_element_by_id("com.ss.android.article.news:id/a2o").click() # 點擊登陸 except Exception as e: logger.error("登陸錯誤,緣由爲:{}".format(e)) else: logger.info("登錄成功...") driver = android_driver() login_opera(driver)
運行test.py,結果以下:
2020-10-07 18:45:05,119 logging_test.py[line:21] INFO 啓動今日頭條APP... 2020-10-07 18:45:11,119 logging_test.py[line:27] INFO 開始登錄今日頭條APP... 2020-10-07 18:45:20,120 logging_test.py[line:41] INFO 登錄成功...
[loggers] # loggers日誌器對象列表,必須包含 keys=root, exampleLogger # 必定要包含root這個值,當使用無參函數logging.getLogger()時,默認返回root這個logger,其餘自定義logger能夠經過logging.getLogger("exampleLogger")方式進行調用 [handlers] # handlers處理器對象列表,必須包含 keys=consoleHandler, fileHandler # 定義聲明handlers信息 [formatters] # formatters格式對象列表,必須包含 keys=form01,form02 [logger_root] # 對loggers中聲明的logger進行逐個配置,且要一一對應,在全部的logger中,必須制定lebel和handlers這兩個選項。對於非roothandler,還須要添加一些額外的option,如qualname、propagate等。handlers能夠指定多個,中間用逗號隔開,好比handlers=fileHandler,consoleHandler,同時制定使用控制檯和文件輸出日誌 level=DEBUG handlers=consoleHandler, fileHandler [logger_exampleLogger] # 配置日誌處理器exampleLogger:設置日誌級別、日誌輸出指定的處理器配置文件,如consoleHandler,fileHandler level=DEBUG handlers=consoleHandler, fileHandler qualname=exampleLogger # qualname 表示它在logger層級中的名字,在應用代碼中經過這個名字制定所使用的handler propagate=0 # 可選項,其默認是爲1,表示消息將會傳遞給高層次logger的handler [handler_consoleHandler] # 日誌處理器consoleHandler的配置文件 class=StreamHandler # 定控制檯輸出。將日誌消息發送到輸出到Stream,如std.out, std.err或任何file-like對象 level=DEBUG # 日誌級別 formatter=form01 # 輸出格式 args=(sys.stdout,) [handler_fileHandler] # 日誌處理器fileHandler的配置文件 class=FileHandler # 將日誌輸出至磁盤文件 level=DEBUG # 日誌級別 formatter=form02 # 輸出格式 args=('./log.txt', 'a', 'UTF-8') # 參數如未設置絕對路徑,則默認生成在執行文件log.py的工做目錄。指定日誌文件的打開模式,默認爲’a’ [formatter_form01] # 格式配置1 format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s [formatter_form02] # 格式配置2 format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
注意:
爲了說明配置結構,這裏的配置文件 logger.conf 里加了中文註釋,實際使用時須要將註釋去掉或改寫成英文註釋,不然會報編碼錯誤。
配置文件中包含三大主要模塊:loggers,handlers,formatters。這三個主要模塊包含的內容都是經過keys進行指定,而後經過logger_key、handler_key、formatter_key對裏面的key進行具體的設置。
配置handlers中的handler_consoleHandler的參數:指定日誌輸出到控制檯、級別、輸出格式、參數。
配置handlers中的handler_fileHandlers的參數:指定將日誌輸出至磁盤文件、設置日誌級別、輸出格式、參數等。
配置日誌輸出格式formatter_xxx,可配置多個,如:form01,form02。
baseLog.py
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 import logging.config CON_LOG='./logger.conf' # 配置文件路徑 logging.config.fileConfig(CON_LOG) # '讀取日誌配置文件' logger = logging.getLogger('exampleLogger') # 建立一個日誌器logger
目錄結構以下
test.py
# -*- coding:utf-8 -*- # @author: 給你一頁白紙 from appium import webdriver from log.baseLog import logger def android_driver(): desired_caps = { "platformName": "Android", "platformVersion": "10", "deviceName": "PCT_AL10", "appPackage": "com.ss.android.article.news", "appActivity": ".activity.MainActivity", "unicodeKeyboard": True, "resetKeyboard": True, "noReset": True, } logger.info("啓動今日頭條APP...") driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps) return driver def login_opera(driver): '''登陸今日頭條操做''' logger.info("開始登錄今日頭條APP...") try: driver.find_element_by_id("com.ss.android.article.news:id/cji").click() # 點擊【我知道了】 driver.find_element_by_id("android:id/button1").click() # 點擊權限管理-肯定按鈕 driver.find_element_by_xpath("//android.widget.TabWidget/android.widget.RelativeLayout[@index=3]").click() # 點擊未登陸 driver.find_element_by_id("com.ss.android.article.news:id/a10").click() # 未登陸頁點擊登陸按鈕 driver.find_element_by_id("com.ss.android.article.news:id/bgh").click() # 登陸頁點擊「。。。」 driver.find_element_by_xpath("//android.widget.LinearLayout[@index=4]").click() # 選擇密碼登陸 driver.find_element_by_id("com.ss.android.article.news:id/bu").send_keys("18768124236") # 輸入帳號 driver.find_element_by_id("com.ss.android.article.news:id/c5").send_keys("xiaoqq3915172") # 輸入密碼 driver.find_element_by_id("com.ss.android.article.news:id/a2o").click() # 點擊登陸 except Exception as e: logger.error("登陸錯誤,緣由爲:{}".format(e)) else: logger.info("登錄成功...") driver = android_driver() login_opera(driver)
控制檯、log.txt輸出結果以下:
2020-10-07 19:30:35,119 logging_test.py[line:21] INFO 啓動今日頭條APP... 2020-10-07 19:30:40,119 logging_test.py[line:27] INFO 開始登錄今日頭條APP... 2020-10-07 19:31:12,120 logging_test.py[line:41] INFO 登錄成功...
在實際使用python作自動化測試過程當中兩種解決思路均可以使用,且都挺方便。其中對於思路1,還能夠將代碼進行更進一步的封裝。