今天花了点时间看了一下Python自带的pdb库,发现用pdb来调试程序还是很方便的,当然了,什么远程调试,多线程之类,pdb是搞不定的。

用pdb调试有多种方式可选:

1. 命令行启动目标程序,加上-m参数,这样调用myscript.py的话断点就是程序的执行第一行之前
python -m pdb myscript.py

2. 在Python交互环境中启用调试
>>> import pdb
>>> import mymodule
>>> pdb.run(‘mymodule.test()’)

3. 比较常用的,就是在程序中间插入一段程序,相对于在一般IDE里面打上断点然后启动debug,不过这种方式是hardcode的

if __name__ == "__main__":
    a = 1
    import pdb
    pdb.set_trace()
    b = 2
    c = a + b
    print (c)

然后正常运行脚本,到了pdb.set_trace()那就会定下来,就可以看到调试的提示符(Pdb)了

常用的调试命令

  • h(elp),会打印当前版本Pdb可用的命令,如果要查询某个命令,可以输入 h [command],例如:“h l” — 查看list命令
  • l(ist),可以列出当前将要运行的代码块

(Pdb) l
497 pdb.set_trace()
498 base_data = {}
499 new_data = {}
500 try:
501 execfile(base_file_name,{},base_data)
502 -> execfile(new_file_name,{},new_data)
503 except:
504 logger.writeLog(“error! load result log error!”)
505 print “load cmp logs error!”
506 raise Exception, “load cmp logs error!”
507

  • b(reak), 设置断点,例如 “b 77″,就是在当前脚本的77行打上断点,还能输入函数名作为参数,断点就打到具体的函数入口,如果只敲b,会显示现有的全部断点

(Pdb) b 504
Breakpoint 4 at /home/jchen/regression/regressionLogCMP.py:504

  • condition bpnumber [condition],设置条件断点,下面语句就是对第4个断点加上条件“a==3”

(Pdb) condition 4 a==3
(Pdb) b
Num Type Disp Enb Where
4 breakpoint keep yes at /home/jchen/regression/regressionLogCMP.py:504
stop only if a==3

  • cl(ear),如果后面带有参数,就是清除指定的断点(我在Python2.4上从来没成功过!!!);如果不带参数就是清除所有的断点

(Pdb) cl
Clear all breaks? y

  • disable/enable,禁用/激活断点

(Pdb) disable 3
(Pdb) b
Num Type Disp Enb Where
3 breakpoint keep no at /home/jchen/regression/regressionLogCMP.py:505

  • n(ext),让程序运行下一行,如果当前语句有一个函数调用,用n是不会进入被调用的函数体中的

  • s(tep),跟n相似,但是如果当前有一个函数调用,那么s会进入被调用的函数体中
  • c(ont(inue)),让程序正常运行,直到遇到断点
  • j(ump),让程序跳转到指定的行数

(Pdb) j 497
> /home/jchen/regression/regressionLogCMP.py(497)compareLog()
-> pdb.set_trace()

  • a(rgs),打印当前函数的参数

(Pdb) a
_logger =
_base = ./base/MRM-8137.log
_new = ./new/MRM-8137.log
_caseid = 5550001
_toStepNum = 10
_cmpMap = {‘_bcmpbinarylog’: ‘True’, ‘_bcmpLog’: ‘True’, ‘_bcmpresp’: ‘True’}

  • p,最有用的命令之一,打印某个变量

(Pdb) p _new
u’./new/MRM-8137.log’

  • !,感叹号后面跟着语句,可以直接改变某个变量
  • q(uit),退出调试

发现在命令行下调试程序也是一件挺有意思的事情,接下来分享一个测试用例,由于python的单元测试与java的junit是同一作者,所以学过junit的同学应该很熟悉了:

下面是一个简单的数学库,实现了简单的加减函数。代码如下:

1 def add(op1, op2):
2     return op1+op2;
3  
4 def sub(op1, op2):
5     return op1-op2;

主程序会调用数学库中的函数,代码如下:

01 #!/usr/bin/env python
02  
03 import math_utils
04  
05 = 3;
06 = 4;
07  
08 = math_utils.add(a, b);
09  
10 print c;

下面演示了几条最基本的调试命令的使用方法,包括”list”,”continue”,”break”,”run”,”backtrace”等等。打断点的时候不要打在函数的def处。如果这样做,import的时候会运行到这个位置,真正调用函数时却不会停住。impor的时候会生成Python内部的运行指令,因此会运行到每个函数定义的地方。

01 henshao@henshao-desktop:~/source$ python -m pdb main.py
02 > /home/henshao/source/main.py(3)<module>()
03 -> import math_utils
04 (Pdb) h
05  
06 Documented commands (type help <topic>):
07 ========================================
08 EOF    bt         cont      enable  jump  pp       run      unt
09 a      c          continue  exit    l     q        s        until
10 alias  cl         d         h       list  quit     step     up
11 args   clear      debug     help    n     r        tbreak   w
12 b      commands   disable   ignore  next  restart  u        whatis
13 break  condition  down      j       p     return   unalias  where
14  
15 Miscellaneous help topics:
16 ==========================
17 exec  pdb
18  
19 Undocumented commands:
20 ======================
21 retval  rv
22  
23 (Pdb) b math_utils.py:4
24 Breakpoint 1 at /home/henshao/source/math_utils.py:4
25 (Pdb) h cont
26 c(ont(inue))
27 Continue execution, only stop when a breakpoint is encountered.
28 (Pdb) run
29 Restarting main.py with arguments:
30  
31 > /home/henshao/source/main.py(3)<module>()
32 -> import math_utils
33 (Pdb) cont
34 > /home/henshao/source/math_utils.py(4)add()
35 -> return op1+op2;
36 (Pdb) bt
37   /usr/lib/python2.6/bdb.py(368)run()
38 -> exec cmd in globals, locals
39   <string>(1)<module>()
40   /home/henshao/source/main.py(8)<module>()
41 -> c = math_utils.add(a, b);
42 > /home/henshao/source/math_utils.py(4)add()
43 -> return op1+op2;
44 (Pdb) args
45 op1 = 3
46 op2 = 4

Python的单元测试框架同JUnit差不多,因为它们的开发者都是Kent Beck,接口都是一样的。使用setUp和tearDown做准备和收尾工作。每个要测试的接口都以”test”开始。在math_utils.py添加下面这段代码便可。

01 import unittest
02  
03 class MathTestCase(unittest.TestCase):
04         def setUp(self):
05                 pass;
06  
07         def tearDown(self):
08                 pass;
09  
10         def testAdd(self):
11                 self.assertEqual(add(45), 9)
12  
13         def testSub(self):
14                 self.assertEqual(sub(85), 3);
15  
16         def suite():
17                 suite = unittest.TestSuite()
18                 suite.addTest(MathTestCase())
19                 return suite
20  
21 if __name__ == "__main__":
22         unittest.main()

直接运行程序,得到以下输出:

1 henshao@henshao-desktop:~/source$ python math_utils.py
2 ..
3 ----------------------------------------------------------------------
4 Ran 2 tests in 0.000s
5  
6 OK

上面这种方式将测试代码同源代码混杂在一起,并不是一种很好的方式。可以将测试用例的代码放到一个单独的unittest目录中。对每个用例设计一个test case,然后使用下面这种方式添加测试用例。

view source
print ?
1 if __name__ == "__main__":
2         suite = unittest.TestSuite()
3  
4         suite.addTest(MathTestCase("testAdd"))
5         suite.addTest(MathTestCase("testSub"))