美文网首页
python拾遗9 - 测试、debug、性能分析

python拾遗9 - 测试、debug、性能分析

作者: 天命_风流 | 来源:发表于2020-05-08 11:01 被阅读0次

单元测试

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

什么是单元测试

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

单元测试一定需要用到 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)<module>()
-> 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)<module>()
-> 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)<module>()
-> 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)

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


优化后性能

相关文章

  • python拾遗9 - 测试、debug、性能分析

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

  • 玩转电商应用性能调优

    第1章 入门篇—性能测试基础知识 什么性能测试 性能测试类型 性能测试基本流程 性能测试需求分析 性能测试指标

  • python 获取debug模式

    python获取debug模式,给debug运行添加参数,经过测试,可以通过下面代码来判断当前是否debug运行:...

  • 性能测试流程与调优

    性能测试流程与调优 概述 分析性能需求 制定性能测试计划 设计场景 编写脚本和程序初始化配置 执行性能测试 分析结...

  • [JPT_02]性能测试-性能指标的分析 & 定义

    目录结构 一、性能测试需求分析与定义 通过前文[JPT_01]性能测试需求分析对性能测试的必要性评估之后,敏捷开发...

  • 吐血推荐:性能测试方案编写(一)

    前言 性能测试方案需包含测试测试需求分析、测试对象分析、测试重点分析、测试环境分析、测试场景构建几个关键点,其中:...

  • Golang Notes

    测试 Test 代码测试 Benchmark 性能测试 性能数据分析 测试代码 命令行操作 工具 Graphviz...

  • 性能瓶颈分析案例

    性能分析案例一 在性能测试过程中,瓶颈犹如功能测试的bug,瓶颈的分析犹如bug的定位。性能测试工程师好比医生,...

  • 性能测试-相关术语理解(一)

    1.性能测试分类 性能测试不是单纯割裂的分类,应该通过分析性能测试所包含元素之间的关系,高效设计性能测试,重点关注...

  • 性能测试策略

    性能测试策略 1. 测试阶段 (1)性能测试需求分析阶段 1根据用户使用习惯和实际业务的性能需求,生成性能测试需...

网友评论

      本文标题:python拾遗9 - 测试、debug、性能分析

      本文链接:https://www.haomeiwen.com/subject/yxytnhtx.html