.测试stdout输出
写个测试来证明标准输出,会将文本打印到屏幕上面
使用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) #通过断言判断二者是否一致
2.在单元测试中给对象打补丁
写的单元测试中需要给指定的对象打补丁, 用来断言它们在测试中的期望行为(比如,断言被调用时的参数个数,访问指定的属性等)
(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()起到 限制测试作用域的作用
3.在单元测试中测试异常情况
写个测试用例来准确的判断某个异常是否被抛出。
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) #需要注意 没有任何异常抛出情况下需要设置处理措施
4.将测试输出用日志记录到文件中
将单元测试的输出写到到某个文件中去,而不是打印到标准输出
(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)
5.忽略或期望测试失败
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()
6.捕获异常
(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:
...
7.输出警告信息
程序能生成警告信息(比如废弃特性或使用问题)
要输出一个警告消息,可使用 warning.warn() 函数。
import warnings
def func(x, y, logfile=None, debug=False):
if logfile is not None:
warnings.warn(logfile argument deprecated, DeprecationWarning)
...
warn() 的参数是一个警告消息和一个警告类
警告类有如下几种:
- UserWarning, DeprecationWarning, SyntaxWarning, RuntimeWarning, ResourceWarning, FutureWarning.
对警告的处理取决于你如何运行解释器以及一些其他配置
- -W 选项能控制警告消息的输出。
- -W all 会输出所有警告消息,
- -W ignore 忽略掉所有警告,
- -W error 将警告转换成异常
8.调试程序崩溃错误
例:
# 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)
20
>>>
部分编译环境下,可以在程序崩溃后打开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中执行语句
9.性能测试
程序运行所花费的时间并做性能测试
(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
859647 function calls in 16.016 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
263169 0.080 0.000 0.080 0.000 someprogram.py:16(frange)
513 0.001 0.000 0.002 0.000 someprogram.py:30(generate_mandel)
262656 0.194 0.000 15.295 0.000 someprogram.py:32(<genexpr>)
1 0.036 0.036 16.077 16.077 someprogram.py:4(<module>)
262144 15.021 0.000 15.021 0.000 someprogram.py:4(in_mandelbrot)
1 0.000 0.000 0.000 0.000 os.py:746(urandom)
1 0.000 0.000 0.000 0.000 png.py:1056(_readable)
1 0.000 0.000 0.000 0.000 png.py:1073(Reader)
1 0.227 0.227 0.438 0.438 png.py:163(<module>)
512 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() 函数会在给定平台上获取最高精度的计时值。 不过,它仍然还是基于时钟时间,很多因素会影响到它的精确度,比如机器负载。
10.优化程序运行效能
(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来实现的,运行起来非常快。 如果你想自己实现新的数据结构(比如链接列表、平衡树等), 那么要想在性能上达到内置的速度几乎不可能,
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/290284.html