pytest插件开发笔记

上一篇博客解决了写爬虫测试时候的一个痛点:复制粘贴太多的重复代码。可还有一个比较烦人的地方,即使test的代码可以自动生成,但还是需要人工去找到底什么地方出现了外部请求,如果 检查外部请求->下载对应的资源并生成mock的代码这部分能够自动完成就好了。今天下手搞了下。

我们的CI是通过pytest的conftest强制必须mock掉HTTP请求的。所以我想这个也可以通过pytest的插件机制来解决。于是就写了一个pytest插件来搞。

Github地址:https://github.com/laixintao/pytest-mock-helper

Pypi: https://pypi.org/project/pytest-mock-helper/

写这个插件花了大约两个小时,原理非常简单。在pytest启动的时候将requests库的requests.adapters.Adapter.send方法替换掉,加入我们要做的逻辑。这么做的灵感来自于另外一个pytest插件:pytest-blockage。这个插件的作用是让pytest强制block掉HTTP请求。和我们在conftest中做的事情一样,但是插件更合理一些,因为我们在不同的项目中就要复制conftest中的代码。不过要注意的是这个插件很久不更新了,不支持Python3。所以如果有类似的需求(在CI中强制Mock HTTP请求)建议使用我的插件。

能如此快速写出一个插件要得益于pytest超赞的文档啊。writing_plugins这一页写的很详细。有一些需要注意的地方,在这篇博客中我记一下:

插件载入的顺序:

  1. 内置的_pytest文件夹中的插件
  2. 外部安装的插件。通过搜索setuptools提供的entry_points查找pytest11,这个很有意思的,不知道为什么会是pytest11,看起来像是开发者随手写的。如果我们自己写插件,就要在setup.py中提供一个pytest11的entry_points
  3. 载入conftest.py

快速参考一些插件:

  1. 文档提供了一个非常简单的Yaml文件测试的插件代码,源代码一看就能懂
  2. 默认插件的源代码
  3. 这里有一个搭建在Heroku上面的pytest兼容性列表,列出了第三方写的插件。这个我猜是监控的pypi,因为我自己写的插件昨天上传到了pypi,今天就在这个列表看见了。不过它判断是否兼容应该是判断的setup.py中的classifiers。我啥也没写就被无情地判断成了不兼容Pyhton27也不兼容Python3。
  4. 一个工具cookiecutter-pytest-plugin,用来生成一个插件的模板。(我不是很推荐这种东西,刚开始用个最小的能跑起来的东西就可以了)

另外发现往pypi.python.org上传东西已经过时了,Api会返回Upload failed (410) Gone。现在正处于迁移的过程,新的包应该向pypi.org提交。而且应该用twine上传。

新的pypi酷炫多了,Python社区在迭代方面真是勇敢啊。都是好事儿。

 

Vim补全代码块神器Ultisnips

爬虫项目中的testcase有很多重复的代码,一般情况都是:mock掉发向服务器的请求 -> 初始化数据库 -> 模拟抓取过程 -> 检查副作用,比如对比存进来的数据等。这些代码不被调用,只是保证之前的代码正常工作,所以我觉得这种“复制、粘贴”编程是可以接受的。而且这种复制粘贴让每一个testcase更直观,抽象度不高。当CI炸了的时候可以很快定位到问题。

以前的做法是复制之前的一个testcase然后修改。这样很不爽。

后来写了一个代码生成器,每次运行命令都自动生成一段代码,然后去修改它。这样的好处是可以在代码生成器做一些事情,比如下载要处理的网页,自动生成运行目标测试的命令等。缺点是每次运行这个生成器都需要一些参数,而且没有和编辑器结合。

所以我觉得解决这个问题最好的办法还是编辑器原生的功能,Vim(一般的代码编辑器都会有这个功能)有Snippet代码片段的功能。自动插入一段代码,然后你可以在placeholder之间跳转修改,非常方便。

SirVer/ultisnips 效果

我根据参考[1]的推荐选择了Ultisnips。安装过程如下:

这是Github上官方的安装。我没有安装vim-snippets,这个插件是一些常用的片段,由网友贡献的。大体看了一下Python的,大都比较实用。不过我觉得用这些玩意儿很容易把Python写的跟Java似的,所以没弄这些。我只要自定义的想补全的片段就够了,其余的可以手写,毕竟写了这么长时间Python了没用这个也没感觉不适。

第三部分是对快捷键的设置。这三个let分别对应:触发展开片段的键、跳到下一个占位符的键,跳到上一个占位符的键。由于tab和YCM补全插件冲突,以及我认为Linux所有的编辑器和命令行都默认F是向前,B是向后的默契,所以我的设置这样的:

读取Ultisnips的路径是~/.vim/UltiSnips。但是也可以自定义。

写UltiSnips很简单,可以参考vim-snippets中的一个:

可以看到,大致的语法是:

这样在编辑器中打出<name>的时候,可选的snippets就会展开,然后用<tab>选择到该名字上(<shift> + <tab>可以向上选择),按你定义的“触发”快捷键就可以将<body>替换到编辑器中。

其中,placeholder在<body>中可以这样规定: ${2:var}。数字用来标志placeholder的id,用于跳转。另外很好用的一点多光标编辑:如果在一个 $2上面编辑,那么会改动所有的$2。

更高级的功能可以仔细阅读:help。

对了,在编辑器外也要经常用一些片段,比如mongo查询等,推荐一个剪切板管理工具:Clipmenu,可以将常用的Snippet放到里面,随时取出来粘贴。

参考:

  1. http://mednoter.com/UltiSnips.html
 

Twitter机器人:今年还要工作多少天?

今天突然想,去年Twitter将字数限制放开到了280,那么就可以模仿YearProgress做一个工作日的进度条了。每天用一个字符表示,一年的工作日有250天,可以放得下。于是就写了一个脚本,用cron每天发出一条twitter提醒工作日还剩下多少天。

Github: https://github.com/laixintao/workdays-bot

Twitter: https://twitter.com/workdays_bot

脚本很简单,就是计算一下一年工作日的天数。然后用tweepy调用一个接口就行了。但是脚本中我用了namedtuple list存放所有天数和类型,后来发现不是很好,应该用OrderedDict,会方便很多。但是这个脚本每天只跑一次,效率不是什么问题,就没去优化。

twitter,github,telegram这些公司接口设计的真好啊!要是国内微信淘宝这些公司的接口,半个小时写出来个能work的东西,我觉得是肯定不可能的……(Telegram和微信的比较)

还发现了两个好玩的网站:

http://china.workingdays.org 这个网站可以计算每个国家的工作天数。可以发现不看公司政策,每个国家一年的工作时间都是250±2的,惊人的一致…… 还以为基督教国家有复活节和圣诞节会多很多呢……而且神奇的是,这个网站有N个域名……每个国家都是一个域名后缀。如果不好买(比如我猜.cn就不好买)才会用二级域名。

 

谈谈Python for循环的作用域

对于从其他语言转到Python的人来说,下面这段代码显得很诡异:

你期望的是i变量不存在报错,而实际上打印结果是:

这是因为,在Pyhton中,是没有block这个概念的。

Python中的作用域只有四种,即LEGB规则:

L, local – 在lambda函数内或者def函数内部的变量

E, Enclosing-function – 闭包的作用域(了解Python的闭包可以看《闭包初探》)

G,Global – 全局作用域

B, Build-in – 内建作用域

举个例子:

由此看来,for循环的作用域会污染局部作用域,Python2的列表生成式也会有这个副作用,但是已经在Python3中得到了修复。

曾经Python邮件列表中有人想“如果在for-loop中有函数引用变量,就将此变量变成for-loop局部变量”,但是造成这个的问题并不是for循环的问题,而是闭包的迟绑定。

 

2017年鉴

瞄了一眼2016年写的总结,发现定的目标果然没有完成,哈哈,当成2018年的目标好了。

年初的时候买了《最后的守护者:食人的大鹫》,上天人文的作品,传说是游戏里面最智能的一条狗,可惜买来之后发现自己并不是很喜欢。纯粹的解谜游戏,没有什么提示,游戏的过程比较无聊,到现在也没有通关。

后来玩了《使命召唤13》,没啥感觉……大约花了8个小时通关了,去线上老是被虐,就封箱了。不顾最近出了《使命召唤二战》,看着挺想玩的。不是很喜欢未来向的游戏,喜欢基于历史的,枪械也比较老的那种风格。等降价入一下《二战》吧。

有一段时间在公司和同事一起玩《BROFORCE》,4人同屏横版闯关游戏,有点像魂斗罗,非常欢乐。后来打到最后的BOSS,打了几次都过不了,渐渐的就不玩了。然后大家基本上不一块玩游戏了。

中间去尝试了一些PS PLUS赠送的游戏,比如《HUE》《刺客信条 黑旗·自由的呐喊》《合金装备V幻痛》,但是都没有深入玩下去。

后来认真通关了《恶名昭彰2 次子》,这个游戏设计的画面很好,游戏性一般,UI也很一般,界面上没有用的画面占了很多地方(菜单)。趁着打折买了心仪已久的《辐射4》,也不错,只不过游戏系统太复杂了,很多操作需要重复做,比如整理道具之类的,不太友好。世界观设定蛮好的,缺点就是太不人性化了。年底会员免费了《拉结特与克拉克》,太喜欢这个游戏了!画面,游戏性,音乐,剧情都可以给满分了!而且非常低龄化,游戏性非常高,可以玩很久!

工作之后玩游戏的时间少了,不过这并不是抱怨,以前在学校很无聊,也不知道做什么,学东西也不知道用在什么地方,学校又不教啥有用的东西,所以很多时间想打游戏。工作之后比较充实,研究一些技术问题、写代码很多时候都感觉比打游戏有意思!今年也没在游戏上花什么钱,倒是Switch出了很多好游戏(年度游戏《塞尔达·荒野之息》),想买,明年看看吧。

7月份延迟1个月顺利拿到了毕业证,在公司入职,一切都很顺利。工作之后真的爽很多啊,比起在学校,做的东西都更有意义了,有了更多的学习目标,学到了更多有有意义的东西,生活也感觉更幸福了。花了很多时间补充Python的知识,用Linux更熟练了。

10月份搬家了,在市区,新家有冰箱和厨房可以用, 感觉生活质量上升了一个档次。之前租的房子打算凑活一下,没想也整整住了一年。做饭这种事情吧,确实费时间,但这就是生活吧,可以学做不一样的东西。

这一年的生活大多数时候还是挺开心的。明年想多花点时间锻炼身体。

很喜欢现在工作的一点是:工作上的事情大家都直来直去,review代码,讨论意见可以就事论事,没有太多顾虑。新方案的提出所有人都可以参与讨论,决定事情比较快,实施也快。现在我们基本上都迁移到Python3了,用的数据库,Elasticsearch基本也都更新很及时,跟着官方的新版本。每天上班心情都很好。

不足的是,有一些项目因为不断添加新的东西,添加的时候一直在复制粘贴,导致线上API那边几百行的函数是很正常的事情。以及前端的代码,同样的逻辑要写在很多地方很多次。现在添加展示一种新的数据类型,经常要grep一下之前的数据是怎么展示的,然后对照这些文件复制粘贴来添加新的。很不好。明年希望能重构一下。首先得熟练用pytest保证不挂掉东西。

去年定的目标,看完《SICP》和《CSAPP》,到年底了,两本书都没有动。今年系统性的认真看完的书很好,学习的东西比较浅,基本就是感兴趣某个方面或者某个问题,去网上翻翻资料,整理一下。豆瓣上“想读”的书还是那么多,今年看的书好像就《永恒的边缘》。

明年少花点时间逛论坛了,纯粹就是浪费时间。很多东西看了还让自己不爽。多花点时间读代码、文档、书,尝试一下流行的技术。多写点博客。

今年7月20日,林肯公园主唱查斯特·贝宁顿在家中自杀身亡。

最后,汇总一下2018年的目标:

  1. 读完《SICP》和《CSAPP》(这好像连续3年被列为目标了)
  2. 在LeetCode做完500道题目
  3. 学习Rust(能直接编译成程序码很酷,打算实现一个HTTPS over DNS)
  4. 买switch

过去四年:

  1. 2013年
  2. 2014年
  3. 2015年
  4. 2016年