当前位置: 首页 > 后端技术 > Python

如何通过测试提高Python代码的健壮性

时间:2023-03-26 13:03:56 Python

0x00前言本文更多是为Python后端程序员写的。我简单分享一下我对写测试的理解。本期我们就来说说测试这件小事。本文内容如下:▼如何通过测试提高Python代码的健壮性:section0x00前言:section▼0x01测试分类:section后端主要关注哪些测试:section▼0x02为什么要写测试:section让新手更快理解代码:section让发布代码时更有信心section让重构程序更容易section加速团队开发section▼0x03为什么不写测试section测试可以的问题'tsolve:sectionWhyunappropriatetestingisabundle:section到处都不容易测试:section▼0x04写Python测试的一些注意事项:section项目的环境隔离:section测试的基本环境:section单元测试/电源测试/结束-to-end:sectionHowtodealwithexternalservices:sectionOtherPytestTips:section0xEEReference:section0x01测试有很多种,根据测试设计的方法,可以分为:1.黑盒2.白盒按测试目的:功能测试单元测试功能测试集成测试场景测试A/B测试非功能测试压力测试安全测试可访问性测试其他回归测试还有很多其他的可用性测试,懒得整理了……代码覆盖率,顾名思义,就是测试用例覆盖运行代码的比例。后端应该关注哪些测试?单元测试,功能测试,端到端测试,性能测试,0x02为什么要写测试,说说测试的好处。为什么要编写测试来覆盖代码。适当的测试可以在发布代码时提供信心。适当的测试可以让新手更快地理解代码。适当的测试可以使程序更容易重构。适当的测试可以加快团队的开发速度。既不是不写,也不是乱写。看到这里你可能会有点疑惑?编写测试会加快速度吗?你在开玩笑吧?让我们一一解释。举个简单的例子,从“用户下单”到“用户收货”。用户“查询商品”,用户“使用优惠券”下单,用户“在线支付”。当然,用户也可以拒绝支付并使订单作废。或者直接取消订单。商家“确认发货”。物流公司更新运单“发货”。用户“确认收货”。当然,用户也可以发起退款。让新手更快的理解代码测试用例中的数据,往往是能够跑通某段代码的最佳测试数据集。比如某程序员写了一个“下单-在线支付-确认收货”的集成测试。作为刚刚接手这段代码的人。您可以在最短的时间内通过阅读测试代码了解整个过程。有了fixture,新手也能知道setup的基本数据,使项目在短时间内跑起来。当然,如果测试写得太多,也会造成阅读困难。发布代码的时候为了更加自信的写测试,就是验证代码是否正确运行。一个流程通常包含多个子流程。如果子流程正确,则整个流程正确。如果不写测试覆盖一些关键流程,会导致修改或增加子流程,需要跑新流程进行人肉测试。如果人肉测试太费力,一般程序员都会跳过这一步,导致上线问题。让程序更容易重构当你知道写测试代码有这么多优点时,你的第一反应是,这个我知道,但是写测试也能加快开发速度?当然,你要知道,一个需要维护的有价值的产品,往往需要不断地修改流程。一开始PM跟你说买东西只需要下单就可以了。后面还需要添加满减优惠券,然后需要添加各种类型的优惠券。然后你需要对接第三方服务,然后你要处理各种不同类型的优惠券。按照你设定的流程打牌的用户...写测试就是通过不断补充一些测试来实现整个流程的测试自动化。形成一套测试代码来测试项目。过程长的离谱,你指望完全靠人肉来测试?当我修改或增加新的子流程时,我可以在已经构建好的测试代码上花费少量的代码,直接保证修改或新增的子流程的输入输出测试到位。多人合作的时候,如果A原来维护了一套子流程,B改了一波A写的子流程,在测试得当的情况下,基本上如果问题改正了,就会测试不通过。当然,前提是A认真写测试,而不是写一个只让A的代码跑过去的测试。但是如果测试写的太多了,也会造成团队精力的分散。这个在下面说到测试的缺点时就知道了。加速团队发展虽然我写的是关于加速团队发展的文章,但它实际上也适用于个人。除非,你写的是渲染页面....所见即所得。Noneedforanytest0x03Whynotwriteatest根据软件界著名的“没有银弹”理论,说完了测试的优点,还要说说测试的局限性。主要有三点:什么问题测试解决不了?不适当的测试通常是一种负担。并非所有地方都容易测试。测试无法解决的问题测试可以保证代码运行的质量,但不能保证代码编写的质量,也不能保证产品设计的逻辑问题。也就是说,代码写的不好,测试代码只能保证写好的代码能正常运行。不会提高代码质量。顶多为坏代码的重构提供一个更好的运行保障。对于产品设计逻辑问题,测试代码只能保证设计逻辑落地。当您对编写测试代码感到不自在时,您应该考虑重构您的程序。为什么不适当的测试是一种负担人们总是不得不习惯的是:事物,学无止境。未知总是存在的。新事物不断涌现,旧事物不断演变。时间有限,精力有限。在测试方面,测试是无止境的。如果你写一个IFELSE,你需要测量两个组,如果你多写一个IFELSE,你需要测量四个组。如果是比较复杂的过程,基本上很难写出综合测试。我的想法是:选择重点地方进行测试,减少用户不必要的数据获取,不是所有的地方都好测,不是所有的地方都好测。特别依赖其他服务提供商的企业。比如支付宝/微信预付款。微信小程序的登录。跨终端业务。这种业务如果做的深入,需要Mock很多逻辑。0x04写Python测试的一些注意事项项目环境隔离从项目整体来看,代码运行环境应该分为四个环境:Local/Test/Stage/Prod。本地环境:开发者电脑上的环境。测试环境:开发者电脑上的环境/持续集成。我以前更喜欢GitlabCI。后来团队安装了jenkins,用起来没问题。预发布环境:预发布环境,对于后端而言,通常是前端可以调用API的环境。生产环境:生产环境。之所以有这样的区分,是因为不同的环境有不同的侧重点。Local环境是为开发者设置的,这个环境下的代码改动比较频繁。本地环境下的webapplication/Worker/Beat/Deamon一般报错比较多,一般我会禁用日志。Test环境用于执行makelint&&maketest检查lint相关代码并运行测试。Stage环境Prod环境比较接近Stage环境。但不完全一样。比如生产环境中的一些组织或者商家的开发资料。测试的基础环境一般都是从一个Docker-Compose文件开始,快速初始化测试环境。例如WebApp/CeleryWorker/CeleryBeats/Redis/RabbitMQ/MySQL可以直接用makestart启动这些服务。单机测试/功耗测试/端到端前面说到后端需要注意以下测试单元测试功能测试端到端测试性能测试性能测试一般可以监控系统哪里有瓶颈在进步。纵观现场,一般通过观察和监控更容易预测系统的瓶颈。这个比较偏向于调优,后面再说。框架假设我们使用的是Flask,然后假设有这样一个BBS(我知道你为什么要吐槽为什么拿blog/BBS为例,懒得解释太多业务场景的背景知识,escape...)组织发布了一个Thread用户User在ThisThread进行了回复「未注册的用户可以看到」AdministratorAdmin发现User似乎发布了不该发布的信息。删除回复。“未注册用户看不到/所有者可以看到。”最后User申诉了,Admin发现发布的内容其实还可以,就批准了。“未注册用户可以看到”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#可用于批量初始化数据。这个过程并不复杂,但是足够写测试了。数据的基本初始化在test_factory_helper中完成。端到端测试中的简单测试浏览。包括未注册的用户查看器访问,用户/管理员/组织访问,使用有效/无效/过期的登录凭证来测试单元中与业务不密切相关的一些逻辑。例如,计算时间是functional中的一个单独测试。有时会拉出几个功能进行测试。一个相对独立的测试是新建一个UserThread,删除Reply,拉上去测试1/2/3/4。一个测试结束了。前者比较简单,后者比较接近集成测试。优点和缺点。我通常会多做一些代码来启动和测试关键流程。但是上拉测试还需要解决一个问题,那就是用户登录认证。当您调用服务时,您将其称为匿名用户/用户身份/管理员/组织。即在调用不同服务解决问题时,可能需要快速切换身份。切换身份再切换回速度。于是,testauthhelper就出来了。助手中有一个开关作为功能。每次需要切换身份时,将g变量中的登录快照g.userg.adminhttp://g.orgpush到LocalStack栈中(fromwerkzeug.localimportLocalStack),调用Service后弹出再次。拉起测试的效果是这样的。deftest_complex_process(org,user,admin):withswitch_as_org(org)asorg:#1.组织发布一个Threadthread=publish_thread_by_org()withswitch_as_user(user)asuser:#2.用户User在这执行一个线程线程回复reply=reply_thread(thread)assertreplywithswitch_as_anonymous()asanonymous_user:_thread=see_thread(thread)assertreplyin_thread.replies#“未注册用户可以看到”withswitch_as_admin()asadmin:#3.AdministratorAdmin是发现User好像发了不该发的信息。删除回复。delete_reply(reply)assertreply.deleledwithswitch_as_anonymous()asanonymous_user:"Unregistereduserscannotsee"_thread=see_thread(thread)assertreplynotin_thread.replies#这里我的身份还是user_thread=see_thread(thread)assertreplyin_thread.replies#『Oweruserscanseeit』#4.最后User申诉,Admin发现发布的东西其实还可以,就批准了。“未注册用户可以看到”作为开发者,只要让这个测试跑通,开发就基本完成了。在此过程中,您还可以更好地清理代码。外部服务怎么处理拉起测试的时候,如果我们有额外的流程,用户可以通过微信支付赞赏回复,这就得依赖外部服务了。当我拉起来测试的时候,会遇到一个很尴尬的问题,因为上面的接口都是粒度比较大的。很欣赏这个过程中非常小的过程,一定要经过微信的http请求。解决方法也很简单。模拟请求微信的功能。手动调用支付回调函数即可。当然对于http请求,也可以使用response神器来快速模拟神器请求的响应。响应的一般用法如下defmock_success_pay():defrequest_callback(request):headers={}dispatch_callback(data=data)return200,headers,resp_bodyresponses.add_callback(responses.POST,PAY_URL,callback=request_callback,content_type="application/json",)@responses.activatedeftest_pay(user):mock_success_pay()switch_as_user(user)asu:order=pay_order(u)assertorder.status=="PAID"其他Pytest技巧有时ipdb效果更好比pdb少一点。如何在pytest中使用它?pytest-v--pdb--pdbcls=IPython.terminal.debugger:Pdb以上就是本次分享的全部内容。想了解更多python知识,请前往公众号:Python编程学习圈,送“J”即可免费获取,每日干货分享