第九章:调试与测试

第一节:断言与单元测试

断言

格式: 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)

例:有一个待测试的模块uut.py

'''
文档测试脚本

#预测加法的结果
>>> 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")

执行结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xiangchi7/article/details/84846186