python中debug怎么测试循环_python拾遗9 - 测试、debug、性能分析

单元测试

测试是非常重要的。但是,如果你不是负责测试的人员,一般你做好单元测试就已经足够了

什么是单元测试

单元测试,通俗易懂地讲,就是编写测试来验证某一个模块的功能正确性,一般会指定输入,验证输出是否符合预期。

单元测试一定需要用到 unittest 模块,下面是一个使用示例:

import unittest

# 将要被测试的排序函数

def sort(arr):

l = len(arr)

for i in range(0, l):

for j in range(i + 1, l):

if arr[i] >= arr[j]:

tmp = arr[i]

arr[i] = arr[j]

arr[j] = tmp

# 编写子类继承unittest.TestCase

class TestSort(unittest.TestCase):

# 以test开头的函数将会被测试

def test_sort(self):

arr = [3, 4, 1, 5, 6]

sort(arr)

# assert 结果跟我们期待的一样

self.assertEqual(arr, [1, 3, 4, 5, 6])

if __name__ == '__main__':

## 如果在Jupyter下,请用如下方式运行单元测试

unittest.main(argv=['first-arg-is-ignored'], exit=False)

## 如果是命令行下运行,则:

## unittest.main()

## 输出

..

----------------------------------------------------------------------

Ran 2 tests in 0.002s

OK

创建一个用于测试的类,这个类要继承 unittest.TestCase

所有以 “test” 开头的函数都会被测试

在测试的内部,通常使用 “assert” 开头的属性进行结果验证。如代码中是判定:arr 是否和 [1,3,4,5,6] 相等

使用 unittest.main( ) 启动测试

运行输出 OK,代表测试通过

单元测试的技巧

我们仔细想一下单元测试的核心:测试某一小块代码的功能。但是在实际生产中,该模块会有非常多的依赖项,搞定这些依赖项是非常麻烦的。所以单元测试的技巧就在这里:用虚假的实现,替换被测试函数的一些依赖项,让我们可以更加专注于需要被测试的功能上。

实现这种虚假实现有三种方法:mock、side_effect、patch

1.mock

import unittest

from unittest.mock import MagicMock

class A(unittest.TestCase):

def m1(self):

val = self.m2()

self.m3(val)

def m2(self):

pass

def m3(self, val):

pass

def test_m1(self):

a = A()

a.m2 = MagicMock(return_value="custom_val")

a.m3 = MagicMock()

a.m1()

self.assertTrue(a.m2.called) # 验证m2被call过

a.m3.assert_called_with("custom_val") # 验证m3被指定参数call过

if __name__ == '__main__':

unittest.main(argv=['first-arg-is-ignored'], exit=False)

## 输出

# ..

# ----------------------------------------------------------------------

# Ran

# 2

# tests in 0.002

# s

#

# OK

unittest.mock.MagicMock 可以创建一个虚假函数,虚假函数的功能一般来说比较单一

这个虚假函数可以替代其它函数

你可以验证某个 mock 实例的调用信息

2.Mock Side Effect

from unittest.mock import MagicMock

def side_effect(arg):

if arg < 0:

return 1

else:

return 2

mock = MagicMock()

mock.side_effect = side_effect

print(mock(-1))

# 1

print(mock(1))

# 2

print(mock.called)

# True

mock.side_effect 可以设置函数的行为,这让虚假函数有了更多功能

3.patch

patch 给开发者提供了遍历的函数 mock 方法。可以利用 装饰器 或 上下文管理环境 实现mock。

# patc.py

def func_1():

return 1

def func_call():

x = func_1()

return x

if __name__ == '__main__':

f = func_call()

print(f)

# 测试.py

from unittest.mock import patch

import unittest

import patc

res = 100

class GetPatchTest(unittest.TestCase):

@patch('patc.func_1') # 我要使用 patch功能 替换掉 patc.func_1 函数

def test_patc(self, mock_1): # 使用 mock_1 替换 func_1,mock_1 已经是一个虚假函数

mock_1.return_value = res # 为虚假函数设置返回值

self.assertEqual(patc.func_call(), 100) # 判断是否符合预期结果

if __import__ == '__main__':

unittest.main()

在这个测试中,我们使用 mock_1替代了 patc.func_1 函数

提高质量的关键

测试用例对测试代码的覆盖率,使用 coverage 模块

将代码进行模块化分解,这样既有利于阅读,又利于测试

debug

通常我们可以使用 ide 中自带的断点进行 debug ,但是有时候我们的工作环境可能没有现成的工具。在这种情况下我们可以使用 pdb模块 进行 debug。

a = 1

b = 2

import pdb

pdb.set_trace()

c = 3

print(a + b + c)

在程序运行到 pdb.set_trace( ) 之后,会停下:

> /Users/jingxiao/test.py(5)()

-> c = 3

这时你可以使用命令进行一些操作:

p x:打印变量 x

n :执行下一行代码

l :查看上下 11 行代码,让程序员了解代状态

s :进入相应代码块(比如一个函数或一个模块),进入会显示 --call-- ,退出代码块会出现 --return-- 字样。

def func():

print('enter func()')

a = 1

b = 2

import pdb

pdb.set_trace()

func()

c = 3

print(a + b + c)

# pdb

> /Users/jingxiao/test.py(9)()

-> func()

(pdb) s

--Call--

> /Users/jingxiao/test.py(1)func()

-> def func():

(Pdb) l

1 -> def func():

2 print('enter func()')

3

4

5 a = 1

6 b = 2

7 import pdb

8 pdb.set_trace()

9 func()

10 c = 3

11 print(a + b + c)

(Pdb) n

> /Users/jingxiao/test.py(2)func()

-> print('enter func()')

(Pdb) n

enter func()

--Return--

> /Users/jingxiao/test.py(2)func()->None

-> print('enter func()')

(Pdb) n

> /Users/jingxiao/test.py(10)()

-> c = 3

r :跳出代码块,当前代码块的代码会继续执行

b :设置断点,例如,b 11 可以在代码中的第 10 行添加一个断点

c :继续执行程序,直到遇到下一个断点

其它命令可以查看官方文档

性能

程序运行时,性能的瓶颈可能只受限于某一个模块,如果知道了哪个模块拉低了程序性能,我们就可以对症下药了。

在 python 中,使用 cProfile 可以实现对每个模块的性能分析,例如,下面编写一个计算斐波那契数列的程序:

def fib(n):

if n == 0:

return 0

elif n == 1:

return 1

else:

return fib(n-1) + fib(n-2)

def fib_seq(n):

res = []

if n > 0:

res.extend(fib_seq(n-1))

res.append(fib(n))

return res

fib_seq(30)

我们发现这个程序有点慢,我们对代码做如下改动:

import cProfile

# def fib(n)

# def fib_seq(n):

cProfile.run('fib_seq(30)')

或者,你可以在命令行运行时使用特定的参数:

python3 -m cProfile xxx.py

你会获得一个性能分析表:

性能分析

各项参数含义如下:

ncalls :相应代码、函数被调用的次数

tottime :对应代码、函数执行的时间(不包含调用其它代码的时间)

tottime percall :每次代码执行的时间,即 tottime / ncalls

cumtime : 对应代码执行的时间(包含调用)

cumtime percall :每次代码执行时间,即 cumtime / ncalls

你会发现,性能瓶颈出现在第二行的 fib( ) 函数,它被调用了 700w+ 次。

我们使用一个 装饰器 + 字典 设置一个调用备忘录,这样可以减小调用次数:

def memoize(f):

memo = {}

def helper(x):

if x not in memo:

memo[x] = f(x)

return memo[x]

return helper

@memoize

def fib(n):

if n == 0:

return 0

elif n == 1:

return 1

else:

return fib(n-1) + fib(n-2)

def fib_seq(n):

res = []

if n > 0:

res.extend(fib_seq(n-1))

res.append(fib(n))

return res

fib_seq(30)

再分析其性能,结果如下:

优化后性能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值