通常在前期調試代碼的時候,我們會使用print在IDE控制台打印一些信息,判斷運行情況。但在運行整個自動化測試項目的過程中,通過print打印信息的方式獲取運行情況顯然行不通。
這時就需要收集日志,每次運行后通過查看日志來獲取項目運行情況。那么我們該如何獲取日志?
一,日志概述
1,日志作用
在項目開發或測試過程中,項目運行一旦出現問題,記錄日志信息就顯得尤為重要。主要通過日志來定位問題,就好比偵探人員要根據現場留下的線索來推斷案情。
2,日志級別
-
代碼在運行的過程中會出現不同的情況,如調試信息、警告信息、報錯等,那么采集日志時就需要對這些日志區分級別管理,這樣才能更精確地定位問題。日志級別一般分類如下(以嚴重程度遞增排序):
級別何時使用DEBUG 調試信息,也是最詳細的日志信息 INFO 證明事情按預期工作 WARNING 表明發生了一些意外,或不久的將來會發生問題(如 磁盤滿了),軟件還是正常工作 ERROR 由於更嚴重的問題,軟件已經不能執行一些工作了 CRITICAL 嚴重錯誤,表明軟件已經不能繼續運行了 -
日志級別排序為:CRITICAL > ERROR > WARNING > INFO > DEBUG
日志采集時設置低級別的日志,能采集到更高級別的日志,但不能采集到更低級別的日志。
例如:設置的日志級別為info級別,就只能采集到info、warning、error、critical級別的日志,不能采集到debug級別的日志。設置的日志級別為debug級別的話則能采集到所有級別的日志。默認設置級別為WARNING。
-
在自動化測試項目中,通常在一般情況時使用info日志,預計報錯則使用error日志。
3,日志格式
將日志格式化是為了提高日志的可閱讀性,比如:時間+模塊+行數+日志級別+日志具體信息 的日志格式。如果輸出的日志信息雜亂無章,就不利於問題的定位。如下所示就是日志格式化輸出,非常便於閱讀查看。
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.
4,日志位置
通常,在一個項目中會有很多的日志采集點,日志采集點的設置必須結合業務來確定。
比如在執行修改登錄密碼用例前插入“開始執行修改登錄密碼用例...”的日志信息。再比如在登錄代碼執行前可以插入“准備登錄...”日志信息。
如果在登錄完成后,再設置登錄的提示日志就會給人造成誤解,無法判斷到底是登錄之前的問題還是登錄之后的問題,因此日志采集點的位置很重要。
二,logging模塊
1,簡介
logging為python自帶的日志模塊,提供了通用的日志系統,包括不同的日志級別。logging可使用不同的方式記錄日志,如使用文件,HTTP GET/POST,SMTP,Socket等方式記錄。通常情況下,我們使用文件記錄日志信息,文件格式一般為.txt或.log文件。
2,文檔
詳細內容可查看logging模塊官方文檔,使用時需要導入:
import logging
三,logging第一種使用方法:簡單配置使用
1,使用方法
logging.basicConfig(**kwargs)
2,basicConfig()部分參數說明
filename 指定日志名稱或完整路徑,如:E:/app-ui-autotest/log/log.txt
filemode 指定打開文件的模式(如果文件打開模式未指定,則默認為'a')
常見的文件讀寫方式:
- w 以寫的方式打開
- W 清空后寫入(文件已存在)
- r 以讀的方式打開
- a 以追加模式打開(即在文件原有的數據后面添加)
format 指定日志輸出格式
level 將根記錄器級別設置為指定級別
3,示例1:日志打印至控制台
# -*- 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.
4,示例2:日志保存至文件
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的第二種使用方式:日志流處理流程
1,logging四大組件介紹
logging模塊包括Logger,Handler,Filter,Formatter四個部分。
- Logger 記錄器,用於設置日志采集。
- Handler 處理器,將日志記錄發送至合適的路徑。
- Filter 過濾器,提供了更好的粒度控制,它可以決定輸出哪些日志記錄。
- Formatter 格式化器,指明了最終輸出中日志的格式。
2,Logger 記錄器
使用日志流采集日志時,須先創建Logger實例,即創建一個記錄器(如果沒有顯式的進行創建,則默認創建一個root logger,並應用默認的日志級別WARNING,Handler和Formatter),然后做以下三件事:
- 為程序提供記錄日志的接口
- 根據過濾器設置的級別對日志進行過濾
- 將過濾后的日志根據級別分發給不同handler
3,Handler 處理器
Handler處理器作用是,將日志記錄發送至合適的路徑。如發送至文件或控制台,此時需要使用兩個處理器,用於輸出控制台的處理器,另一個是用於輸出文件的處理器。通過 addHandler() 方法添加處理器 。常用的處理器類型有以下兩種:
3.1,StreamHandler
-
將日志信息發送至sys.stdout、sys.stderr或任何類似文件流對象,如在Pycharm IDE上顯示的日志信息。
-
構造函數為:StreamHandler(strm)。參數strm是一個文件對象,默認是sys.stderr。
3.2,FileHandler
-
將日志記錄輸出發送至磁盤文件。 它繼承了StreamHandler的輸出功能,不過FileHandler會幫你打開這個文件,用於向一個文件輸出日志信息。
-
構造函數為:FileHandler(filename, mode)。參數filename為文件名(文件完整路徑),參數mode為文件打開方式,默認為'a'即在文末追加。
自動化測試使用這兩種類型就夠了,其他還有RotatingFileHandler、TimedRotatingFileHandler、NullHandler等處理器,有興趣可以查找資料了解。
4,Filter 過濾器
顧名思義是用於過濾,Handlers 與 Loggers 使用 Filters 可以完成比級別更復雜的過濾。不多做介紹,有興趣可以查找資料了解。
5,Formatter 格式化器
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 | 打印日志信息 |
6,使用示例:將日志輸出至控制台,同時保存至文件
根據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
五,logging 實戰
1,測試場景
給登錄今日頭條app的操作添加日志采集。
2,簡單配置代碼示例
# -*- 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 登陸成功...
3,拋出問題
上面示例代碼成功地獲取了日志信息,但這種寫法只能作用於當前模塊。而一個自動化測試項目往往有多個模塊,如果在每個需要獲取日志的模塊都使用這樣的方式,顯然是不方便維護的。那么我們需要怎么解決呢?
4,解決思路
使用日志流處理流程。提供以下兩種思路:
思路1:使用python代碼實現日志配置。先創建日志記錄器,並設置好Handler與日志格式,如上面的logging_test.py模塊構造logger,其他模塊采集日志時直接調用。
思路2:將日志的格式、輸出路徑等參數抽離出來放置在專門的配置文件里,如logging.conf,使用專門的模塊處理,使用時直接在模塊調用即可。
5,思路1:使用python代碼實現日志配置示例
目錄結構
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 登陸成功...
6,思路2:日志格式配置示例
6.1,logger.conf文件
[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。
6.2,讀取配置文件,創建日志記錄器logger
baseLog.py
# -*- coding:utf-8 -*-
# @author: 給你一頁白紙
import logging.config
CON_LOG='./logger.conf' # 配置文件路徑
logging.config.fileConfig(CON_LOG) # '讀取日志配置文件'
logger = logging.getLogger('exampleLogger') # 創建一個日志器logger
6.3,調用示例
目錄結構如下
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 登陸成功...
7,總結
在實際使用python做自動化測試過程中兩種解決思路都可以使用,且都挺方便。其中對於思路1,還可以將代碼進行更進一步的封裝。