一般在前期调试代码的时候,咱们会使用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,还能够将代码进行更进一步的封装。