Python Revisited Day 09 (调试、测试与Profiling)
9.1 调试
定期地进行备份是程序设计中地一个关键环节——不管我们的机器,操作系统多么可靠以及发生失败的概率多么微乎其微——因为失败仍然是可能发生的。备份一般都是粗粒度的——备份文件是几小时之前的,甚至是几天之前的。
9.1.1 处理语法错误
if True
print("stupid!!!")
else:
print("You will never see me...")
File "C:/Py/modeltest.py", line 5
if True
^
SyntaxError: invalid syntax
上面的例子中,if后面忘记加了“:”,所以报错。
try:
s = "Tomorrow is a new day, {0}"
s2 = "gone with the wind..."
print(s.format(s2)
except ValueError as err:
print(err)
File "C:/Py/modeltest.py", line 10
except ValueError as err:
^
SyntaxError: invalid syntax
看上面的例子,实际上,报错的位置并没有错误,真正的错误在于print后少了半边括号,但是Python在运行到此处的时候并没有意识到错误, 因为可能通过括号分行,所以显示错误在了下一行。
9.1.2 处理运行时错误
pass
9.1.3 科学的调试
如果程序可以运行,但程序行为和期待的或需要的不一致,就说明程序中存在一个bug——必须清除的逻辑错误。清楚这类错误的最好方法是首先使用TDD(测试驱动的开发)来防止发生这一类错误,然而,总会有些bug没有避免,因此,即便使用TDD,调试也仍然是必须学习和掌握的技能。
为清楚一个bug, 我们必须采取如下一个步骤:
- 再现bug
- 定位bug
- 修复bug
- 对修复进行测试
9.2 单元测试
单元测试——对单独的函数、类与方法进行测试,确保其符合预期的行为。
就像我们之前那样做的:
if __name__ == "__main__":
import doctest
doctest.testmod()
另一种执行doctest的方法是使用uniitest模块创建单独的测试程序。unittest模块可以基于doctests创建测试用例,而不需要指导程序或模块包含的任何事物——只要指导其包含doctest即可。
我们创建了一个docunit.py的程序:
def test(x):
"""
>>> test(-1)
'hahahaha'
>>> test(1)
'lalalala'
>>> test('1')
'wuwuwuwuwuwu'
"""
s1 = "hahahahha"
s2 = "lalalalala"
s3 = "wuwuwuwuwuwu"
try:
if x <= 0:
return s1
else:
return s2
except:
return s3
注意,如果运行测试,前俩条会出错,因为不匹配。
再创建一个新的程序:
import doctest
import unittest
import docunit
suite = unittest.TestSuite()
suite.addTest(doctest.DocTestSuite(docunit))
runner = unittest.TextTestRunner()
print(runner.run(suite))
注意,第三个import的是自己的程序,输出为:
<unittest.runner.TextTestResult run=1 errors=0 failures=1>
F
======================================================================
FAIL: test (docunit)
Doctest: docunit.test
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Ana\lib\doctest.py", line 2198, in runTest
raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for docunit.test
File "C:\Py\docunit.py", line 7, in test
----------------------------------------------------------------------
File "C:\Py\docunit.py", line 9, in docunit.test
Failed example:
test(-1)
Expected:
'hahahaha'
Got:
'hahahahha'
----------------------------------------------------------------------
File "C:\Py\docunit.py", line 11, in docunit.test
Failed example:
test(1)
Expected:
'lalalala'
Got:
'lalalalala'
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
Process finished with exit code 0
只是,这个时候,我们写的程序的程序名,必须为有效的模块名。
unittest 模块定义了4个关键概念。测试夹具是一个用于描述创建测试(以及用完之后将其清理)所必须的代码的术语,典型实例是创建测试所用的一个输入文件,最后删除输入文件与结果输出文件。测试套件是一组测试用例的组合。测试用例是测试的基本单元。测试运行着是执行一个或多个测试套件的对象。
典型情况下,测试套件是通过创建unittest.TestCase的子类实现的,其中每个名称以“test”开头的方法都是一个测试用例。如果我们需要完成任何创建操作,就可以在一个名为setUp()的方法中实现;类似地,对任何清理操作,也可以实现一个名为tearDown()的方法。在测试内部,有大量可供我们使用的unittest.TestCase方法,包括assertTrue(), assertEqual(), assertAlmostEqual()(对于测试浮点数很有用)、assertRaises()以及更多,还包括对应的逆方法,比如assertFalse(), assertNotEqual()、faillfEqual()、failUnlessEqual()等。
下面是一个例子,因为不知道该编一个啥,就用一个最简单的,只是为了说明这个unittest该怎么用。
import unittest
class List(list):
def plus(self, other):
return list(set(self + other))
class TestList(unittest.TestCase):
def setUp(self):
self.list1 = List(range(3))
self.list2 = list(range(2, 5))
def test_list_add(self):
addlist = self.list1 + self.list2
self.assertEqual(
addlist, [0, 1, 2, 2, 3, 4]
)
def test_list_plus(self):
pluslist = self.list1.plus(self.list2)
self.assertNotEqual(
pluslist, [0, 1, 2, 2, 3, 4]
)
def process():
self.list2.plus(self.list1)
self.assertRaises(
AttributeError, process #注意assertRaises的第二项必须callable Obj
)
def tearDown(self):
"""
我不知道这么做有没有用
:return:
"""
del self
if __name__ == "__main__":
suite = unittest.TestLoader().loadTestsFromTestCase(
TestList
)
runner = unittest.TextTestRunner()
print(runner.run(suite))
更多的函数,在博客,还蛮详细的:
python的unittest单元测试框架断言整理汇总-黑面狐
9.3 Profiling
一些合理的Python程序设计风格,对提高程序性能不无裨益:
- 在需要只读序列是,最好使用元组而非列表;
- 使用生成器,而不是创建大的元组和列表并在其上进行迭代处理
- 尽量使用Python内置的数据结构——dicts, list, tuples——而不实现自己的自定义结构
- 从小字符串中产生大字符串时,不要对小字符串进行连接,而是在列表中累积,最后将字符串列表结合为一个单独的字符串
- 最后一点,如果某个对象需要多次使用属性进行访问,或从某个数据结构中进行访问,那么较好的做法时创建并使用一个局部变量来访问该对象。
在jupiter notebook里面用%%time输出cell单次运行的时间,%%timeit 输出运行10万次?的平均之间.
使用timeit模块:
import timeit
def function_a(x, y):
for i in range(10000):
x + y
def function_b(x, y):
for i in range(10000):
x * y
def function_c(x, y):
for i in range(10000):
x / y
if __name__ == "__main__":
repeats = 1000
X = 123.123
Y = 43.432
for function in ("function_a", "function_b",
"function_c"):
t = timeit.Timer("{0}(X, Y)".format(function),
"from __main__ import {0}, X, Y".format(function))
sec = t.timeit(repeats) / repeats
print("{function}() {sec:.6f} sec".format(**locals()))
其中timeit.Timer()函数的第一个参数,是我们需要执行的字符串,第二个参数也是可执行的字符串,是用以提供参数的。
function_a() 0.000386 sec
function_b() 0.000384 sec
function_c() 0.000392 sec
利用cProfile模块,会更加方便且详细地给出运行时间地指示:
import cProfile
import time
def function_a(x, y):
for i in range(10000):
function_f(x, y)
function_d()
def function_b(x, y):
for i in range(10000):
function_f(x, y)
function_d()
function_d()
def function_c(x, y):
for i in range(10000):
function_f(x, y)
function_d()
function_d()
function_d()
def function_d():
time.sleep(0.01)
def function_f(x, y):
x * y
if __name__ == "__main__":
repeats = 1000
X = 123.123
Y = 43.432
for function in ("function_a", "function_b",
"function_c"):
cProfile.run("for i in range(1000): {0}(X, Y)"
.format(function))
10003003 function calls in 16.040 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.007 0.007 16.040 16.040 <string>:1(<module>)
1000 3.878 0.004 16.033 0.016 modeltest.py:13(function_a)
1000 0.006 0.000 10.241 0.010 modeltest.py:31(function_d)
10000000 1.915 0.000 1.915 0.000 modeltest.py:34(function_f)
1 0.000 0.000 16.040 16.040 {built-in method builtins.exec}
1000 10.235 0.010 10.235 0.010 {built-in method time.sleep}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
10005003 function calls in 28.183 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.008 0.008 28.183 28.183 <string>:1(<module>)
1000 4.873 0.005 28.175 0.028 modeltest.py:18(function_b)
2000 0.015 0.000 20.903 0.010 modeltest.py:31(function_d)
10000000 2.399 0.000 2.399 0.000 modeltest.py:34(function_f)
1 0.000 0.000 28.183 28.183 {built-in method builtins.exec}
2000 20.887 0.010 20.887 0.010 {built-in method time.sleep}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
10007003 function calls in 38.968 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.008 0.008 38.968 38.968 <string>:1(<module>)
1000 5.004 0.005 38.959 0.039 modeltest.py:24(function_c)
3000 0.024 0.000 31.498 0.010 modeltest.py:31(function_d)
10000000 2.457 0.000 2.457 0.000 modeltest.py:34(function_f)
1 0.000 0.000 38.968 38.968 {built-in method builtins.exec}
3000 31.474 0.010 31.474 0.010 {built-in method time.sleep}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
ncalls: 调用地次数
tottime: 在某个函数中耗费的总时间,但是派出了函数调用的其他函数内部花费的时间
percall: 对函数的每次调用的平均时间 tottime / ncalls
cumtime: 累计时间,列出了在函数中耗费的时间,并且包含了函数调用其他函数内部花费的时间
percall(第二个): 列出了对函数的每次调用的平均时间,包裹其调用的函数耗费的时间
Python Revisited Day 09 (调试、测试与Profiling)的更多相关文章
- 转 Python3 错误和异常/ Python学习之错误调试和测试
########sample 0 https://www.cnblogs.com/Simon-xm/p/4073028.html except: #捕获所有异常 except: <异常名> ...
- python-8错误调试测试
1-错误处理 import logging try: print('try.......') r = 10/0 except ValueError as e: print('result:', e) ...
- 详细介绍windows下使用python pylot进行网站压力测试
windows下使用python进行网站压力测试,有两个必不可少的程序需要安装,一个是python,另一个是pylot.python是一个安装软件,用来运行python程序,而pylot则是pytho ...
- Python Web 性能和压力测试 multi-mechanize
http://www.aikaiyuan.com/5318.html 对Web服务做Performance & Load测试,最常见的工具有Apache Benchmark俗称ab和商用工具L ...
- Python标准库09 当前进程信息 (部分os包)
原文:Python标准库09 当前进程信息 (部分os包) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 我们在Linux的概念 ...
- Python如何实现单步调试
遇到大型python项目,如何定位问题和监控程序的运行状态是一个程序员必须掌握的技能,今天小编为你带来python程序的单步调试方法,方便易用,简单易记! 首先你需要在所调试程序的开头中:import ...
- Python网络数据采集7-单元测试与Selenium自动化测试
Python网络数据采集7-单元测试与Selenium自动化测试 单元测试 Python中使用内置库unittest可完成单元测试.只要继承unittest.TestCase类,就可以实现下面的功能. ...
- 基于NIOS-II的示波器:PART4 系统调试&测试
本文记录了在NIOS II上实现示波器的第四部分. 本文主要包括:修改部分BUG,以及测试 本文所有的硬件以及工程参考来自魏坤示波仪,重新实现驱动并重构工程. version 1.0 界面修改& ...
- 【转】python模块分析之unittest测试(五)
[转]python模块分析之unittest测试(五) 系列文章 python模块分析之random(一) python模块分析之hashlib加密(二) python模块分析之typing(三) p ...
随机推荐
- [C++] vptr, where are you?
Search(c++在线运行). 有的网站很慢--不是下面的程序有问题. #include <string.h> #include <stdio.h> #include < ...
- TLSv1.3 Support:主流 Web 客户端和服务端对 TLSv1.3 的支持情况
TLSv1.3 Support:主流 Web 客户端和服务端对 TLSv1.3 的支持情况 请访问原文链接:https://sysin.org/blog/tlsv1-3-support/,查看最新版. ...
- Linux FTP的主动模式与被动模式
Linux FTP的主动模式与被动模式 一.FTP主被动模式 FTP是文件传输协议的简称,ftp传输协议有着众多的优点所以传输文件时使用ftp协议的软件很多,ftp协议使用的端口是21( ...
- Linux系统根目录下各文件夹介绍
参考自:[1]Linux 系统根目录下各个文件夹的作用 https://www.cnblogs.com/jiangfeilong/p/10538795.html[2]了解Linux根目录"/ ...
- jdk1.6,1.7,1.8解压版无需安装(64位)
1.java SE 1.6各个版本 jdk http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads ...
- Output of C++ Program | Set 12
Predict the output of following C++ programs. Question 1 1 #include <iostream> 2 using namespa ...
- 【Linux】【Services】【SaaS】Docker+kubernetes(13. 部署Jenkins/Maven实现代码自动化发布)
1. 简介 Jenkins: 官方网站:https://jenkins.io/ 下载地址:https://jenkins.io/download/ war包下载:http://mirrors.jenk ...
- AOP中环绕通知的书写和配置
package com.hope.utils;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotatio ...
- 【Word】自动化参考文献-交叉引用
第一步:设置参考文献标号 开始-定义新编号格式中,定义参考文献式的方框编号: 这里注意不要把他原来的数字去掉 第二步:选择交叉引用 插入-交叉引用: 第三步:更新标号 如果更新标号,使用右键-更新域. ...
- linux基本操作命令2
复制文件 格式: cp [参数] [ 被复制的文件路径] [ 复制的文件路径] -r :递归复制 (需要复制文件夹时使用) 案例:将/root目录下的test文件夹及其内部的文件复制到/tmp中 [ ...