文章目录
第一节:断言与单元测试
断言
格式:
assert +【条件表达式】
如果表达式成立,则程序能够正常向下执行,没有输出任何内容,否则抛出**AssertionError
**
使用断言,可以简易迅速地对流程结果进行测试,预测结果是否相符;
例:
# 待测函数
def add(a, b):
return a + b
# 使用断言进行测试:
assert add(3, 4) == 7
执行结果:
系统没有报错,断言是正确的,使用add这个函数得到的结果与预期相符
例:
# 待测函数
def add(a, b):
return a * b
# 使用断言进行测试:
assert add(3, 4) == 7
这时系统抛出了AssertionError,说明断言是错误的,待测函数add的结果于预期并不一致
单元测试
所谓单元,指的是可测试对象的最小单位,通常指一个函数。单元测试只是对工程中的代码单元(通常细化到每个函数)进行正确性验证的工作;最常用的测试
对自己写的模块和类进行必要的单元测试,保证严谨正确,既是良好的开发习惯,也是一种规范;
Python标准库中单元测试的模块是unittest
import unittest
测试用例
【要点】
测试用例类必须继承unittest.TestCase
具体的测试项函数必须以testxxx
来命名
setUp()
方法会在每个测试项执行前调用,如有必要的初始化工作可以通过覆写该方法来实现
tearDown()
方法会在每个测试项结束后调用,如有善后工作可以通过覆写该方法来实现
在整个测试用例中,还有两个类,setUpClass(cls)
和tearDowwnClass(cls)
也会在测试用例的执行前后各调用一次
在具体的测试项函数中,使用TestCase的assertXXX
系列函数预言结果
如果程序执行结果与预测的一致,测该单项测试通过,否则不通过
【几个常用断言方法】
assertFalse(self,expr,msg)
—— 断言正确
assertTrue(self,expr,msg)
—— 断言错误
assertEqual(self,expr,msg)
—— 断言相等
【注意】
测试与光标有关,当光标在某函数内时,执行的是此函数的测试,当想测试所有,需把光标放在unittest.main()区域
例:
import unittest
# 待测的工具类
class MathUtil:
# a,b相加
def sum(self, a, b):
return a + b
# a,b相减
def sub(self, a, b):
return a - b
# 判断a是否大于b
def gt(self, a, b):
return a > b
# 测试用例类,必须继承于unittest.TestCase
class MathUtilTest(unittest.TestCase):
'''MathUtil工具类测试用例'''
# 测试项初始化方法
def setUp(self):
print("MathUtilTest setUp,测试项正在初始化...")
# 创建实例(用于准备数据)
self.mu = MathUtil()
# 测试项结束时调用
def tearDown(self):
del self.mu # 消毁数据
print("MathUtilTest tearDown,测试项已结束")
'''
一系列的测试方法,都必须以testxxx命名
'''
# 测试MathUtil的sum方法
def testSum(self):
print("正在测试Sum方法...")
# 断言相等
self.assertEqual(self.mu.sum(3, 4), 7,"Sum函数测试失败!!!")
# 测试MathUtil的sub方法
def testSub(self):
print("正在测试Sub方法...")
# 断言相等
self.assertEqual(self.mu.sub(3, 4), 1, "Sub函数测试失败!!!")
# 测试MathUtil的gt方法
def testGt(self):
print("正在测试Others方法...")
# 断言真假
self.assertTrue(self.mu.gt(5, 4))
self.assertFalse(self.mu.gt(4, 5))
# 断言抛出异常
with self.assertRaises(TypeError):
self.mu.sum(1, "2")
# 执行单元测试
if __name__ == '__main__':
# 运行当前模块中的所有测试用例
unittest.main()
正常执行结果:
非正常执行结果:
测试套件
测试套件,用于单独测试某个方法,而不必整个测式用例进行测试,相对灵活一些
unittest.main()
方法会执行当前模块中的所有测试用例类中的所有测试项,这显得不太灵活;
unittest.TestSuite
类是一个测试用例容器,可以按需添加测试用例于其中,使得单元测试既可以批量进行,又可以自主增减测试项目;
例:
import unittest
from unittest.runner import TextTestRunner
# 待测的工具类
class MathUtil:...
# 测试用例类,必须继承于unittest.TestCase
class MathUtilTest(unittest.TestCase):...
class MathUtilTest2(unittest.TestCase):...
class MathUtilTest2(unittest.TestCase):...
# 执行单元测试
if __name__ == '__main__':
# 运行当前模块中的所有测试用例
# unittest.main()
# 定义一个测试套件
suite = unittest.TestSuite()
# 往测试套件里新增用例类下的所有测试项
suite.addTest(unittest.makeSuite(MathUtilTest))
suite.addTest(unittest.makeSuite(MathUtilTest2))
# 执行测试套件
runner = TextTestRunner() # 执行器
ret = runner.run(suite)
print(ret)
第二节:文档测试与DEBUG
文档测试
文档指的就是Python模块,文档测试就是对一个py文件进行整体的测试,是一种简单粗暴的测试方式;
文档测试中的测试代码是以注释的形式
写在文档中;
通过标准库API来触发文档测试:doctest.testmod(target_module)
'''
文档测试脚本
#预测加法的结果
>>> add(3,4)
7
#预测减法的结果
>>> sub(3,4)
-1
#预测幂的结果
>>> power(3,4)
81
'''
# 正确的加法函数
def add(a,b):
return a + b
# 正确的加法函数
def sub(a,b):
return a - b
# 错误的幂函数
def power(a,b):
return a ** b - 1
开始对目标模块进行测试:
#引入文档测试模块
import doctest
# 引入要进行测试的目标模块uut
import uut
if __name__ == '__main__':
#对uut进行文档测试
doctest.testmod(uut)
测试结果:
DEBUG
【什么是DEBUG】
DEBUG是指对程序的执行过程进行逐行逐步调试
;
DEBUG时,程序从第一个断点处进入暂停状态
,然后根据用户的指令,一步一步地进行执行,每执行一步,都能够从控制台中查看到程序和数据的所有细节;
【主要操作步骤】
在需要中止的地方打断点;
在IDE中右键选择“Debug XXX”;
按需【下一步(Step Over)】或【进入方法(Step Into) 】直到流程结束;
在分步执行的过程中,可以:
将重点怀疑的变量右击添加到观察(Add to watches);
调试过程中人为修改可疑变量的值(Set Value);
PS:DEBUG是一种效率不高的调试手段,它应用作程序调试的辅助手段而非主要手段;
例:
第三节:关于日志
什么是日志
网络设备、系统及服务程序等,在运作时都会产生一个叫log的事件记录;每一行日志都记载着日期、时间、使用者及动作等相关操作的描述。在软件项目工程中调试,不应该是DEBUG来进行的,应为检测的效率不高,应该以日志+单元测试为主要的方式。而日志往往都是后来用于回看的,通过回看日志可以发现当时发生了什么异常。Web通常把日志写到一个文件中
日志常用API
【全局日志logging】
basicConfig(level,format)
—— 基本设置(级别,格式)
getLogger(name)
—— 创建局部日志对象
Fomatter('%(asctime)s,%(name)s,%(levelname)s,%(message)s')
—— 设置日志格式,name 指局部日志名
FileHandler("./logs/log.txt")
—— 文件处理器,把日志传向文件
StreamHandler()
—— 流处理器,把日志传向控制台
RotatingFileHandler("./logs/log3.txt",maxBytes=1*1024,backupCount=3)
—— 回滚的文件处理器,传向动态存储上限的文件
logging.config.dictConfig(configDict)
—— 字典设置,扩大了基本设置的局限性
【局部日志Logger】
info(msg)
—— 打印信息
debug(msg)
—— 打印调试信息
waming(msg)
—— 打印敬告信息
error(msg,exc_info=True)
—— 打印错误信息
setLevel(level)
—— 设置上限等级
addHandler(handler)
—— 设置处理器对象
【处理日志Handler】
setLevel(logging.INFO)
—— 设置等级
setFormatter(formatter)
—— 设置格式
日志的等级
Python源码对日志级别的是有定义的,数值越大,级别越高;
输出时,等于或高于配置级别的日志信息都会被输出;
【不同的级别的具体含义】
FATAL
—— 致命错误
CRITICAL
—— 特别糟糕的事情,如内存耗尽、磁盘空间为空,一般很少使用
ERROR
—— 发生错误时,如IO操作失败或者连接问题
WARNING
—— 发生很重要的事件,但是并不是错误时,如用户登录密码错误
INFO
—— 处理请求或者状态变化等日常事务
DEBUG
—— 调试过程中使用DEBUG等级,如算法中每个循环的中间状态
NOTSET
—— 未设置
例:Python源码
日志的格式
%(levelno)s
—— 打印日志级别的数值
%(levelname)s
—— 打印日志级别的名称
%(pathname)s
—— 打印当前执行程序的路径
%(filename)s
—— 打印当前执行程序名
%(funcName)s
—— 打印日志的当前函数
%(lineno)d
—— 打印日志的当前行号
%(asctime)s
—— 打印日志的时间
%(thread)d
—— 打印线程ID
%(threadName)s
—— 打印线程名称
%(process)d
—— 打印进程ID
%(message)s
—— 打印日志信息
向控制台输出日志
例1:输出的级别
import logging
# 基本配置
# level=logging.WARNING 输出WARNING以上的级别的日志内容
# format定义了日志输出格式:'输出时间 - 日志名称 - 日志级别 - 日志内容
logging.basicConfig(level=logging.WARNING, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 打印日志
logging.critical("大家好,我是critical,最高等级的错误信息")
logging.error("大家好,我是error,等级第二的严重错误信息")
logging.warning("大家好,warning,中等级的警告")
logging.info("大家好,我是INFO,初等级的提示错误")
logging.debug("大家好,我是debug,只是小人物,大家忽略我吧")
执行结果:
例2:局部日志
import logging
# 基本配置
#level=logging.DEBUG 输出DEBUG以上级别的日志内容
# format定义了日志输出格式:'输出时间 - 日志名称 - 日志级别 - 日志内容'
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 获取logger对象,命名为__name__,也可以命名为其他,如:mylog
logger = logging.getLogger(__name__)
#设置局部日志输出等级(输出所有等级)
logger.setLevel(logging.NOTSET) # 完全没用,受限于根日志的设置等级
# 打印局部日志
logger.critical("大家好,我是critical,最高等级的错误信息")
logger.error("大家好,我是error,等级第二的严重错误信息")
logger.warning("大家好,warning,中等级的警告")
logger.info("大家好,我是INFO,初等级的提示错误")
logger.debug("大家好,我是debug,只是小人物,大家忽略我吧")
执行结果:
向文件输出日志
向文件输出日志
【步骤】
1、创建logging.getLogger局部日志对象logger,设置其等级
2、创建一个logging.FileHandler对象handler处理器,设置其等级
3、定义日志格式,并赋于handler处理器的格式设置
4、局部日志logger添加处理器handler
5、打印日志
例:
import logging
# 获取logger对象,设置日志级别
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
# 获取文件处理器,并设置级别
handler = logging.FileHandler("./log.txt")
# handler = logging.FileHandler("./logs/log.csv")
handler.setLevel(logging.INFO)
# 获取并设置文件处理器的日志格式
formatter = logging.Formatter('%(asctime)s,%(name)s,%(levelname)s,%(message)s')
handler.setFormatter(formatter)
# 设置日志处理器
logger.addHandler(handler)
# 打印日志
logger.critical("critical:you are lose")
logger.error("error:you make a big error")
logger.warning("warning:something is warning")
logger.info("INFO:mistake is coming")
logger.debug("debug:just a debug")
执行结果:
同时向控制台和文件输出日志
【步骤】
1、创建logging.getLogger局部日志对象logger,设置其等级
2、创建一个logging.FileHandler对象handler处理器,设置其等级
3、定义日志格式,并赋于handler处理器的格式设置
4、创建一个logging.StreamHandler对象console流处理器,设置其等级
5、用刚才定义的日志格式,并赋于console流处理器的格式设置
6、局部日志logger添加处理器handler和流处理器console
7、打印日志
例:
import logging
# 创建logging.getLogger局部日志对象logger,设置其等级
logger = logging.getLogger("mylog")
logger.setLevel(level=logging.INFO)
# 创建一个logging.FileHandler对象handler处理器,设置其等级
handler = logging.FileHandler("./log2.txt")
handler.setLevel(logging.INFO)
# 定义日志格式,并赋于handler处理器的格式设置
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
# 创建一个logging.StreamHandler对象console流处理器,设置其等级
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# 局部日志logger添加处理器handler和流处理器console
logger.addHandler(handler)
logger.addHandler(console)
#用刚才定义的日志格式,并赋于console流处理器的格式设置(略)
pass
# 打印日志
logger.critical("critical:you are lose")
logger.error("error:you make a big error")
logger.warning("warning:something is warning")
logger.info("INFO:mistake is coming")
logger.debug("debug:just a debug")
执行结果: