乐趣区

关于python:如何通过测试提升-Python-代码的健壮性

0x00 前言
本文的更多的是写给 Python 后端的程序员。来简略分享一下我对写测试的了解。

本期就聊聊测试这件小事件。

本文目录如下:

▼ 如何通过测试晋升 Python 代码的健壮性 : section
    0x00 前言 : section
  ▼ 0x01 测试的分类 : section
      后端次要关注哪些测试 : section
  ▼ 0x02 为什么要写测试 : section
      让老手更快的理解代码 : section
      让公布代码的时候更加有底气 : section
      让程序更容易重构 : section
      放慢团队的开发速度 : section
  ▼ 0x03 为什么不要写测试 : section
      测试不能解决的问题 : section
      不适当的测试为什么是累赘 : section
      并不是所有中央都容易测试的 : section
  ▼ 0x04 写 Python 测试的一些注意事项 : section
      我的项目的环境隔离 : section
      测试的根本环境 : section
      单测 / 功测 / 端对端 : section
      如何解决内部服务 : section
      其余 Pytest 小技巧 : section
    0xEE 参考 : section

0x01 测试的分类
测试有很多种,依照测试设计的办法能够分为:1. 黑盒 2. 白盒

依照测试目标:

  1. 功能测试
    单元测试
    功能测试
    集成测试
    场景测试
    A/B 测试
  2. 非功能测试
    压力测试
    安全性测试
    可拜访性测试
    其余
    回归测试
    易用性测试
    还有不少,懒得去整顿了 …..

代码覆盖率顾名思义,就是测试用例笼罩运行代码的比重。

后端次要关注哪些测试

  • 单元测试
  • 功能测试
  • 端对端测试
  • 性能测试

0x02 为什么要写测试
来讲讲测试的长处。

为什么要写测试来笼罩代码。

  1. 适当的测试能够让公布代码的时候更有底气。
  2. 适当的测试能够让老手更快的理解代码。
  3. 适当的测试能够让程序更容易重构。
  4. 适当的测试能够放慢团队的开发速度。

既不是不写,也不是狂写一气。看到这里你可能有些纳闷?写测试还加快速度?Are you kidding?

一个一个来解释吧。

举个简化版的例子,『用户下单』到『用户收货』。

  1. 用户『查问产品』
  2. 用户『应用优惠券』下单
  3. 用户『在线领取』。当然,用户也能够让不付款,让订单生效。或者间接勾销订单。
  4. 商家『确认发货』。
  5. 物流公司更新运单『发货中』。
  6. 用户『确认收货』。当然,用户也可发动退款。

让老手更快的理解代码
测试用例里的数据,往往是能跑通某段代码的最佳测试数据汇合。

如果,有个程序员写了『下单 - 在线领取 - 确认收货』的集成测试。作为刚接手这段代码的人。能够在最短的工夫内,通过浏览测试代码从而了解整个流程。

有 fixture, 老手能够在很短的工夫内晓得 setup 能让我的项目跑起来的根本数据

当然,如果过多的写了测试,也会导致浏览起来比拟艰难。

让公布代码的时候更加有底气
写测试,是为了验证代码运行正确。

一个流程,通常蕴含若干个子流程,子流程是对的,整个流程才是对的。

如果不写测试对一些要害的流程进行全面的笼罩,则会导致
批改或者新增了一个子流程,须要从新跑个流程进行人肉测试。
如果人肉测试太麻烦,则个别程序员就会跳过这个步骤导致线上出问题。

让程序更容易重构
当你晓得写测试代码有这么多长处的之后,你的第一反馈是,这我都晓得,然而,写测试还能放慢开发速度?

当然,你要晓得,一个须要去保护的有价值的产品,往往须要一直的批改流程。

一开始,PM 通知你只须要下单买个货色,起初,要加上满减券,再起初要加上各种类型的券,而后你要对接第三方服务,接下来你要凑合各种不依照你设定的流程出牌的用户….

写测试,则是通过一直的补充一些测试,实现整个流程的测试自动化。造成一套测试该项目标测试代码。流程长的令人发指,你指望全靠人肉来测试?

    1. 当我批改或者新增子流程的时候,在曾经构建进去的测试代码上,能够花大量的代码间接保障批改或者新增的子流程输出和输入被测试到位。
  1. 多人单干的时候,如果 A 原先保护了一套子流程,而 B 来改了一波 A 写的子流程。在有适当的测试的状况下,基本上改出问题来,都会跑不过测试的。

当然,前提是

  1. A 用心写了测试,而不是写了仅仅能让 A 的代码跑的过来的测试。
  2. 然而测试如果写过多的话,也会造成团队精力的扩散。这上面谈到测试的毛病的时候就会晓得。

放慢团队的开发速度
尽管说,我写的是放慢团队的开发速度,但实际上,也实用于集体。
除非,你是写渲染页面的…. 所见即所得。无需任何测试

0x03 为什么不要写测试
按照软件界驰名的『没有银弹』实践,说完了测试的优越性,也要来说说测试的局限性,次要有三点:

  1. 测试不能解决什么问题?
  2. 不适当的测试,往往是累赘。
  3. 并不是所有中央都容易测试的。

测试不能解决的问题
测试能确保代码的运行品质,但无奈确保代码编写品质,也无奈保障产品设计逻辑上的问题。

也就是说

  1. 代码写的烂,测试代码只能确保编写代码是能够失常运行的。并不能改善代码品质。最多给烂代码的重构提供比拟好的运行保障。
  2. 产品设计逻辑上的问题,测试代码也只能保障这个设计逻辑落地。

当你感觉测试代码写起来比拟好受的时候,你应该思考重构一下你的程序了。

不适当的测试为什么是累赘
人总要习惯的是:

  1. 货色,学是学不完的。未知的货色永远存在。新的事物总是在呈现,老的事物也一直在演进。
  2. 工夫无限,精力有限

放到测试上来说,测试,也是测不完的。

写了一个 IF ELSE , 你须要测两组,多写了一个 IF ELSE, 你就要测四组。如果是一个比较复杂的流程的话,基本上全面测试就很难写完了。

我的想法是:

  1. 筛选要害的中央进行测试
  2. 缩小用户不必要的数据获取

并不是所有中央都容易测试的

并不是所有中央都容易测试的。

  1. 特地依赖其余服务商的业务。比方,支付宝 / 微信的预领取。微信小程序的登陆。
  2. 跨端的业务。

这类业务如果做的比拟深刻,须要 Mock 掉很多逻辑。

0x04 写 Python 测试的一些注意事项
我的项目的环境隔离
从整体我的项目角度,代码的运行环境应该辨别 Local/Test/Stage/Prod 四种环境。

  • 本地环境:开发者电脑上的环境
  • 测试环境:开发者电脑上 / 继续集成上的环境,之前比拟喜爱用 GitlabCI, 起初 团队上了 jenkins, 用起来也还行。
  • 预公布环境:预公布环境,对后端来说,通常状况下就是前端能够通过调用 API 的环境。
  • 生产环境:生产环境。

之所以要做这种辨别,是因为不同的环境侧重点不同。

  • Local 环境 针对开发者设置的,这个环境的代码变更比拟频繁。Web 利用 / Worker / Beat / Deamon 在本地环境中,个别报错比拟多,个别我会在禁掉日志。
  • Test 环境 用于执行 make lint && make test,用于查看 lint 相干代码并运行测试。
  • Stage 环境
  • Prod 环境 和 Stage 环境就比拟靠近了。但也不完全一致。比方生产环境的组织或商家的一些开发材料。

测试的根本环境
个别起一个 Docker-Compose 文件,来疾速初始化测试环境。

比方 WebApp / Celery Worker / Celery Beats / Redis / RabbitMQ / MySQL 能够 make start 间接起这些服务。

单测 / 功测 / 端对端
之前说,后端须要留神上面的测试

  • 单元测试
  • 功能测试
  • 端对端测试
  • 性能测试

性能测试个别能够通过监控来提前对系统在哪些地方有瓶颈。看场景,个别察看监控会更加容易预测零碎的瓶颈,这个更多偏差于调优,放到前面来说吧。

框架假如咱们应用 Flask , 再假如有这么一个 BBS(我晓得你想吐槽为什么又拿博客 /BBS 举例子,懒得交代过多的业务场景背景常识了,逃…)

  1. 组织 Organization 公布了一个 Thread
  2. 用户 User 在这个 Thread 进行了 Reply『未注册的用户能看见』
  3. 管理员 Admin 发现了 User 仿佛公布了不该公布的信息。删 Reply。『未注册的用户看不见 / 所有者是能看见的』
  4. 最初 User 进行申述,Admin 发现其实公布的货色挺 OK 的,给予通过。『未注册的用户能看见』
tests # 测试文件目录
├── __init__.py
├── conftest.py # 这里寄存可能被子目录援用到的汇合
├── e2e #『端对端测试』│   ├── __init__.py
│   ├── test_viewer.py
│   ├── test_user.py
│   ├── test_admin.py
│   └── test_organization.py
├── functional #『功能测试』│   ├── __init__.py
│   ├── test_do_simple_reply.py
│   ├── test_do_complex_reply.py
│   └── test_helper.py
├── unit #『单元测试』|   ├── __init__.py
|   ├── test_auth.py
|   └── test_calc_some_thing.py
├── test_auth_helper.py # 寄存根本的用于切换身份的代码
├── test_const.py
└── test_factory_helper.py # 能够用来批量初始化数据

这个流程并不算简单,但足以写测试了。

  1. 在 test_factory_helper 实现数据的根本初始化。
  2. 在端对端测试中简略测试浏览。蕴含未注册用户 viewer 的拜访,user/admin/org 的带无效 / 有效 / 过期登陆凭据拜访
  3. 在 unit 中测试一些和业务联系不严密的逻辑。比方,计算工夫
  4. 在 functional 进行比拟独立的测试。有的时候也会把几个性能拉起来做测试。绝对独立的测试,就是新建一个 User 的 Thread, 删除 Reply, 拉起来测试就是 1/2/3/4 一个测试就完了。

前者比较简单,后者相对而言更加凑近集成测试。各有利弊。我个别在要害流程上多做几个拉起来测试的代码。

但拉起来测试要解决的问题就多了一个,即,用户登陆认证。你调用某个 Service 的时候,是以匿名用户 / 用户身份 / Admin / Org 调用的。

即在调用不同的 Service 解决问题的时候,你可能须要疾速的切换身份。切换完身份再速度切换回来。于是,test auth helper 进去了。helper 外面有个 switch as 函数,每次须要切换身份的时候,把 g 变量外面的登陆快照 g.user g.admin http://g.org push 到 LocalStack 栈里 (from werkzeug.local import LocalStack), 调用完 Service 再 Pop 进去。

拉起来测试的成果是这样子的。

def test_complex_process(org, user, admin):
    with switch_as_org(org) as org:     # 1. 组织 Organization 公布了一个 Thread
        thread = publish_thread_by_org()
        with switch_as_user(user) as user: # 2. 用户 User 在这个 Thread 进行了 Reply
            reply = reply_thread(thread)
            assert reply
            with switch_as_anonymous() as anonymous_user:
                _thread = see_thread(thread)
                assert reply in _thread.replies #『未注册的用户能看见』with switch_as_admin() as admin: # 3. 管理员 Admin 发现了 User 仿佛公布了不该公布的信息。删 Reply。delete_reply(reply)
                assert reply.deleled
            with switch_as_anonymous() as anonymous_user:『未注册的用户看不见』_thread = see_thread(thread)
                assert reply not in _thread.replies
            # 在这里,我的身份还是 user
            _thread = see_thread(thread)
            assert reply in _thread.replies #『Ower 用户能看见』# 4. 最初 User 进行申述,Admin 发现其实公布的货色挺 OK 的,给予通过。『未注册的用户能看见』

作为开发者,你只须要让这个测试跑通就根本开发结束了。在这个过程中,你也能够更好的梳理你的代码。

如何解决内部服务
在拉起来做测试的时候,如果咱们多了一个流程,用户能够通过微信领取赞叹 reply, 这就不得不依赖于内部的服务。

而拉起来做测试的时候,就会遇到一个十分难堪的问题,因为我下面的接口都粒度都比拟大,是赞叹这个流程外面的十分小的流程,必须要走微信的 http 申请。

解决形式也很简略。mock 掉申请微信的函数。手动调用一下领取回调函数,即可。

当然,对于 http 申请,也能够应用 responses 这个神器来疾速 mock 神器 requests 的 response

大抵的用法如下

def mock_success_pay():
    def request_callback(request):
        headers = {}
        dispatch_callback(data=data)
        return 200, headers, resp_body

    responses.add_callback(
        responses.POST,
        PAY_URL,
        callback=request_callback,
        content_type="application/json",
    )

@responses.activate
def test_pay(user):
    mock_success_pay()
        switch_as_user(user) as u:
            order = pay_order(u)
        assert order.status == "PAID"

其余 Pytest 小技巧
有的时候 ipdb 比 pdb 用起来不止好了一点点。如何在 pytest 里用上呢?

pytest -v --pdb --pdbcls=IPython.terminal.debugger:Pdb

以上就是本次分享的所有内容,想要理解更多 python 常识欢送返回公众号:Python 编程学习圈,发送“J”即可收费获取,每日干货分享

退出移动版