写个测试来证明标准输出,会将文本打印到屏幕上面
使用unitest框架进行测试
# mymodule.py def urlprint(protocol, host, domain): url = {}://{}.{}.format(protocol, host, domain) print(url) from io import StringIO from unittest import TestCase from unittest.mock import patch import mymodule class TestURLPrint(TestCase): #单元测试类 def test_url_gets_to_stdout(self): protocol = http host = www domain = example.com expected_url = {}://{}.{} .format(protocol, host, domain) #给定标准输出 with patch(sys.stdout, new=StringIO()) as fake_out: #patch模拟一种输出方式 mymodule.urlprint(protocol, host, domain) self.assertEqual(fake_out.getvalue(), expected_url) #通过断言判断二者是否一致
写的单元测试中需要给指定的对象打补丁, 用来断言它们在测试中的期望行为(比如,断言被调用时的参数个数,访问指定的属性等)
(1)将它当做装饰器使用
from unittest.mock import patch import example @patch(example.func) def test1(x, mock_func): example.func(x) # Uses patched example.func mock_func.assert_called_with(x) #断言被调用时的参数
(2)被当做一个上下文管理器
with patch(example.func) as mock_func: example.func(x) # Uses patched example.func mock_func.assert_called_with(x) #断言被调用时的参数
(3)可以手动的使用它打补丁
p = patch(example.func) mock_func = p.start() #限制作用域 example.func(x) mock_func.assert_called_with(x) #断言被调用时的参数 p.stop() #限制作用域
注意:patch() 接受一个已存在对象的全路径名,将其替换为一个新的值。 原来的值会在装饰器函数或上下文管理器完成后自动恢复回来。因此patch()起到 限制测试作用域的作用
写个测试用例来准确的判断某个异常是否被抛出。
import unittest # A simple function to illustrate def parse_int(s): return int(s) class TestConversion(unittest.TestCase):#测试类 def test_bad_int(self): #测试用例 self.assertRaises(ValueError, parse_int, N/A) #N/A表示错误提示
对比手动异常检测:
class TestConversion(unittest.TestCase): def test_bad_int(self): try: r = parse_int(N/A) except ValueError as e: self.assertEqual(type(e), ValueError) else: self.fail(ValueError not raised) #需要注意 没有任何异常抛出情况下需要设置处理措施
将单元测试的输出写到到某个文件中去,而不是打印到标准输出
(1)将运行测试的结果打印到标准输出上
import unittest class MyTest(unittest.TestCase): pass if __name__ == __main__: unittest.main()
(2)重定向输出到文件
import sys def main(out=sys.stderr, verbosity=2): loader = unittest.TestLoader() #组装测试套件 suite = loader.loadTestsFromModule(sys.modules[__name__]) #从测试类中扫描收集测试用例 unittest.TextTestRunner(out,verbosity=verbosity).run(suite) #测试运行类 if __name__ == __main__: with open(testing.out, w) as f: main(f)
unittest 模块有装饰器可用来控制对指定测试方法的处理
import unittest import os import platform class Tests(unittest.TestCase): def test_0(self): self.assertTrue(True)# 输出结果:ok @unittest.skip(skipped test) #skip() 装饰器能被用来忽略某个你不想运行的测试 输出结果:skipped skipped test def test_1(self): self.fail(should have failed!) @unittest.skipIf(os.name==posix, Not supported on Unix) #指定测试系统为posix 不支持unix系统 输出结果:skipped Not supported on Unix def test_2(self): import winreg @unittest.skipUnless(platform.system() == Darwin, Mac specific test) #指定测试平台为mac 输出结果:ok(保证使用mac电脑测试) def test_3(self): self.assertTrue(True) @unittest.expectedFailure def test_4(self): self.assertEqual(2+2, 5) if __name__ == __main__: unittest.main()
(1)处理多个异常
不创建大量重复代码就能处理所有的可能异常
try: client_obj.get_url(url) except (URLError, ValueError): client_obj.remove_url(url) except SocketTimeout: client_obj.handle_url_timeout(url)
注意:异常会有层级关系,对于这种情况,你可能使用它们的一个基类来捕获所有的异常
同时except 语句是顺序检查的,第一个匹配的会执行 所以异常被第一个能识别的捕获
(2)捕获全部异常
想要捕获所有的异常,可以直接捕获 Exception 即可
try: ... except Exception as e: ... log(Reason:, e) # Important!
注意:这个将会捕获除了 SystemExit 、 KeyboardInterrupt 和 GeneratorExit 之外的所有异常。 如果你还想捕获这三个异常,将 Exception 改成 BaseException 即可。
(3)捕获自定义异常
自定义异常类应该总是继承自内置的 Exception 类, 或者是继承自那些本身就是从 Exception 继承而来的类
class NetworkError(Exception): pass class HostnameError(NetworkError): pass class TimeoutError(NetworkError): pass class ProtocolError(NetworkError): pass try: msg = s.recv() except TimeoutError as e: ... except ProtocolError as e: ...
程序能生成警告信息(比如废弃特性或使用问题)
要输出一个警告消息,可使用 warning.warn() 函数。
import warnings def func(x, y, logfile=None, debug=False): if logfile is not None: warnings.warn(logfile argument deprecated, DeprecationWarning) ...
warn() 的参数是一个警告消息和一个警告类
警告类有如下几种:
对警告的处理取决于你如何运行解释器以及一些其他配置
例:
# sample.py def func(n): return n + 10 func(Hello)
如果你的程序因为某个异常而崩溃,运行 python3 -i someprogram.py 可执行简单的调试。 -i 选项可让程序结束后打开一个交互式shell。
bash % python3 -i sample.py Traceback (most recent call last): File "sample.py", line 6, in <module> func(Hello) File "sample.py", line 4, in func return n + 10 TypeError: Cant convert int object to str implicitly >>> func(10) >>>
部分编译环境下,可以在程序崩溃后打开Python的调试器
>>> import pdb #启动python自带调试器 >>> pdb.pm() > sample.py(4)func() -> return n + 10 (Pdb) w sample.py(6)<module>() -> func(Hello) > sample.py(4)func() -> return n + 10 (Pdb) print n Hello (Pdb) q >>>完整命令 简写命令 描述 args a 打印当前函数的参数 break b 设置断点 clear cl 清除断点 condition 无 设置条件断点 continue c或者cont 继续运行,知道遇到断点或者脚本结束 disable 无 禁用断点 enable 无 启用断点 help h 查看pdb帮助 ignore 无 忽略断点 jump j 跳转到指定行数运行 list l 列出脚本清单 next n 执行下条语句,遇到函数不进入其内部 p p 打印变量值,也可以用print quit q 退出 pdb return r 一直运行到函数返回 tbreak 无 设置临时断点,断点只中断一次 step s 执行下一条语句,遇到函数进入其内部 where w 查看所在的位置 ! 无 在pdb中执行语句
程序运行所花费的时间并做性能测试
(1)简单的想测试下你的程序整体花费的时间, 通常使用Unix时间函数time就行
bash % time python3 someprogram.py real 0m13.937s user 0m12.162s sys 0m0.098s bash %
需要一个程序各个细节的详细报告,可以使用 cProfile 模块
bash % python3 -m cProfile someprogram.py function calls in 16.016 CPU seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 0.080 0.000 0.080 0.000 someprogram.py:16(frange) 0.001 0.000 0.002 0.000 someprogram.py:30(generate_mandel) 0.194 0.000 15.295 0.000 someprogram.py:32(<genexpr>) 0.036 0.036 16.077 16.077 someprogram.py:4(<module>) 15.021 0.000 15.021 0.000 someprogram.py:4(in_mandelbrot) 0.000 0.000 0.000 0.000 os.py:746(urandom) 0.000 0.000 0.000 0.000 png.py:1056(_readable) 0.000 0.000 0.000 0.000 png.py:1073(Reader) 0.227 0.227 0.438 0.438 png.py:163(<module>) 0.010 0.000 0.010 0.000 png.py:200(group) ... bash %
(2)测试个别函数消耗时间 可以采用装饰器方法
# timethis.py import time from functools import wraps def timethis(func): @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() r = func(*args, **kwargs) end = time.perf_counter() print({}.{} : {}.format(func.__module__, func.__name__, end - start)) return r return wrapper >>> @timethis #要使用这个装饰器,只需要将其放置在你要进行性能测试的函数定义前即可 ... def countdown(n): ... while n > 0: ... n -= 1 ... >>> countdown(10000000) __main__.countdown : 0.803001880645752 >>>
(3)要测试某个代码块运行时间,可以定义一个上下文管理器
from contextlib import contextmanager @contextmanager def timeblock(label): start = time.perf_counter() try: yield finally: end = time.perf_counter() print({} : {}.format(label, end - start)) >>> with timeblock(counting): ... n = 10000000 ... while n > 0: ... n -= 1 ... counting : 1.5551159381866455 >>>
注意:
当执行性能测试的时候,需要注意的是你获取的结果都是近似值。 time.perf_counter() 函数会在给定平台上获取最高精度的计时值。 不过,它仍然还是基于时钟时间,很多因素会影响到它的精确度,比如机器负载。
(1)使用函数
# somescript.py import sys import csv with open(sys.argv[1]) as f: for row in csv.reader(f): # Some kind of processing pass #定义在全局范围的代码运行起来要比定义在函数中运行慢的多 因此,如果你想让程序运行更快些,只需要将脚本语句放入函数中即可 # somescript.py import sys import csv def main(filename): with open(filename) as f: for row in csv.reader(f): # Some kind of processing pass main(sys.argv[1])
(2)尽可能去掉属性访问
每一次使用点(.)操作符来访问属性的时候会带来额外的开销。 它会触发特定的方法,比如 __getattribute__() 和 __getattr__() ,这些方法会进行字典操作操作。
import math def compute_roots(nums): result = [] for n in nums: result.append(math.sqrt(n)) return result # Test nums = range(1000000) for n in range(100): r = compute_roots(nums) from math import sqrt # 使用 from module import name 这样的导入形式,以及使用绑定的方法 用 sqrt() 代替了 math.sqrt() def compute_roots(nums): result = [] result_append = result.append for n in nums: result_append(sqrt(n))# The result.append() 方法被赋给一个局部变量 result_append ,然后在内部循环中使用它 这种方法只有在大量重复代码中(循环)有意义 return result
(3)使用局部变量
import math def compute_roots(nums): sqrt = math.sqrt #sqrt 从 math 模块被拿出并放入了一个局部变量中 加速原因是因为对于局部变量 sqrt 的查找要快于全局变量 sqrt result = [] result_append = result.append for n in nums: result_append(sqrt(n)) return result
(4)避免不必要的抽象
任何时候当你使用额外的处理层(比如装饰器、属性访问、描述器)去包装你的代码时,都会让程序运行变慢。
(5)使用内置的容器
内置的数据类型比如字符串、元组、列表、集合和字典都是使用C来实现的,运行起来非常快。 如果你想自己实现新的数据结构(比如链接列表、平衡树等), 那么要想在性能上达到内置的速度几乎不可能,