文章背景: 最近在学习华为云在线课程Python应用篇
,其中有个章节是程序调试。在代码编写过程中,需要不断地调试代码,使其满足我们的开发要求。下面首先介绍程序调试的几种方法,然后介绍单元测试。
1 print语句
2 assert(断言)
3 断点调试
4 单元测试
4.1 单元测试的特殊方法
4.2 单元测试内置的条件判断
4.3 测试用例
1 print语句
用print语句调试代码是最简单的一种方法。在代码中合适的地方插入print语句,可以输出某些变量,方便查看。
代码示例:
代码语言:javascript复制def foo(s):
n = int(s)
print('>>> n = %d' % n)
return 10 / n
def main():
foo('0')
main()
代码语言:javascript复制>>> n = 0
ZeroDivisionError: division by zero
此方法的缺点是在程序上线时需要删除多余的print语句。
2 assert(断言)
assert(断言),是Python中用于调试的工具,依赖于内置变量__debug__
,当其取值为True时assert才会执行。
assert expression [, arguments]
代码示例:
代码语言:javascript复制def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')
main()
代码语言:javascript复制AssertionError: n is zero!
assert
断言,表达式n != 0
应该是True。如果断言失败,assert
语句就会抛出AssertionError
。
启动Python解释器时可以用-O
参数来关闭assert
。
将上述代码存入err.py
文件中。在命令提示符
中,进入err.py
文件所在的文件夹,运行如下代码:
python -O err.py
会得到错误提示:
代码语言:javascript复制ZeroDivisionError: division by zero
在程序上线时一般会禁用断言。
3 断点调试
断点(Break point)是指在代码中指定位置,当程序运行到此位置时中断下来,开发者可查看此时各个变量的值。因断点中断的程序并没有结束,可以选择继续执行。
断点调试需要借助于IDE(如pycharm
, VS code
等);Python本身提供工具pdb
,但在使用上不如IDE中的工具简单。
4 单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
比如对函数abs()
,我们可以编写出以下几个测试用例:
- 输入正数,比如
1
、1.2
、0.99
,期待返回值与输入相同; - 输入负数,比如
-1
、-1.2
、-0.99
,期待返回值与输入相反; - 输入
0
,期待返回0
; - 输入非数值类型,比如
None
、[]
、{}
,期待抛出TypeError
。
把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。
(1) 如果单元测试通过,说明我们测试的这个函数能够正常工作;如果单元测试不通过,要么函数有bug,要么测试条件输入不正确。总之,需要修复使单元测试能够通过。
(2) 使用单元测试的好处是,如果我们后续对abs()
函数代码做了修改,只需要再跑一遍单元测试。如果通过,说明我们的修改不会对abs()
函数原有的行为造成影响;如果测试不通过,说明我们的修改与原有行为不一致,要么修改函数代码,要么修改测试代码。
4.1 单元测试的特殊方法
为了编写单元测试,我们需要引入Python自带的unittest
模块。在unittest
模块中,有以下几个常用的方法。
(1)unittest.main()
: 执行测试用例;
(2)setUp()
: 在每个测试方法执行之前执行。
若setUp()方法引发异常,测试框架会认为测试发生了错误,因此,测试方法不会被执行。
(3)tearDown()
: 在每个测试方法执行之后执行。
若setUp()
方法成功运行,无论测试方法是否成功,都会运行tearDown()
。
(4)unittest.skip(reason)
: 无条件跳过(装饰器形式)。
(5)setUpClass()
、tearDownClass()
: 在所有测试方法开始或结束的前后执行(类方法)。
4.2 单元测试内置的条件判断
下表总结了几种断言方法。
4.3 测试用例
(1)代码文件student.py
的代码如下:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
if self.score > 100 or self.score < 0:
raise ValueError('value must in 0 ~ 100')
if self.score >= 80:
return 'A'
if self.score >= 60:
return 'B'
return 'C'
(2)测试代码文件student_test.py
的代码如下:
import unittest
from student import Student
class TestStudent(unittest.TestCase):
@classmethod
def setUpClass(cls):
print("setUpClassn")
@classmethod
def tearDownClass(cls):
print("tearDownClass")
def setUp(self):
print('setUp...')
def tearDown(self):
print('tearDown...n')
def test_80_to_100(self):
s1 = Student('Bart', 80)
s2 = Student('Lisa', 100)
self.assertEqual(s1.get_grade(), 'A')
self.assertEqual(s2.get_grade(), 'A')
def test_60_to_80(self):
s1 = Student('Bart', 60)
s2 = Student('Lisa', 79)
self.assertEqual(s1.get_grade(), 'B')
self.assertEqual(s2.get_grade(), 'B')
def test_0_to_60(self):
s1 = Student('Bart', 0)
s2 = Student('Lisa', 59)
self.assertEqual(s1.get_grade(), 'C')
self.assertEqual(s2.get_grade(), 'C')
def test_invalid(self):
s1 = Student('Bart', -1)
s2 = Student('Lisa', 101)
with self.assertRaises(ValueError):
s1.get_grade()
with self.assertRaises(ValueError):
s2.get_grade()
if __name__ == '__main__':
unittest.main()
运行测试代码student_test.py
,得到的结果如下:
setUpClass
setUp...
tearDown...
setUp...
tearDown...
setUp...
tearDown...
setUp...
tearDown...
tearDownClass
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
(1) 编写单元测试时,我们需要编写一个测试类,从unittest.TestCase
继承。
(2) 以test
开头的方法就是测试方法,不以test
开头的方法不被认为是测试方法,测试的时候不会被执行。
(3) 对每一类测试都需要编写一个test_xxx()
方法。由于unittest.TestCase
提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。
(4) assertRaises
方法可以用来确保一个特定的函数调用引发特定的异常,它可以通过上下文管理器(with语句)来包装内嵌代码。如果with语句中的代码引发了正确的异常,则测试通过;否则,测试失败。
参考资料:
[1] Python应用篇(https://education.huaweicloud.com/courses/course-v1:HuaweiX CBUCNXX123 Self-paced/about?isAuth=0&cfrom=hwc)
[2] Python调试代码的4种方法:print、log、pdb、PyCharm的debug(https://blog.csdn.net/xiemanR/article/details/72775737)
[3] 调试(https://www.liaoxuefeng.com/wiki/1016959663602400/1017602696742912)
[4] 单元测试(https://www.liaoxuefeng.com/wiki/1016959663602400/1017604210683936)
[5] unittest 中用于 skip 跳过 test method, test class,的相关装饰器(https://blog.csdn.net/HeatDeath/article/details/72633303)
[6] 测试面向对象程序(https://www.kancloud.cn/k12edu/python3_object_oriented/1161666)