前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python: 调试代码和单元测试

Python: 调试代码和单元测试

作者头像
Exploring
发布2022-09-20 14:16:55
7820
发布2022-09-20 14:16:55
举报

文章背景: 最近在学习华为云在线课程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文件所在的文件夹,运行如下代码:

代码语言:javascript
复制
python -O err.py

会得到错误提示:

代码语言:javascript
复制
ZeroDivisionError: division by zero

在程序上线时一般会禁用断言。

3 断点调试

断点(Break point)是指在代码中指定位置,当程序运行到此位置时中断下来,开发者可查看此时各个变量的值。因断点中断的程序并没有结束,可以选择继续执行。

断点调试需要借助于IDE(如pycharm, VS code等);Python本身提供工具pdb,但在使用上不如IDE中的工具简单。

4 单元测试

单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。

比如对函数abs(),我们可以编写出以下几个测试用例:

  1. 输入正数,比如11.20.99,期待返回值与输入相同;
  2. 输入负数,比如-1-1.2-0.99,期待返回值与输入相反;
  3. 输入0,期待返回0
  4. 输入非数值类型,比如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的代码如下:

代码语言:javascript
复制
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的代码如下:

代码语言:javascript
复制
import unittest
from student import Student


class TestStudent(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        print("setUpClass\n")

    @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,得到的结果如下:

代码语言:javascript
复制
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)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据处理与编程实践 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 print语句
  • 2 assert(断言)
  • 3 断点调试
  • 4 单元测试
    • 4.1 单元测试的特殊方法
      • 4.2 单元测试内置的条件判断
        • 4.3 测试用例
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档