设为首页 收藏本站
查看: 883|回复: 0

[经验分享] TDD 与 CI 在 Python 中的实践

[复制链接]
发表于 2015-11-29 08:10:27 | 显示全部楼层 |阅读模式
  社区化产品的长久生存之道可能莫过于对迭代周期的控制。还记得以前采用老土的阶段开发的年代,将软件生命周期分为各个阶段,当到达每个阶段的里程碑则集中所有的资源、人力作全面冲刺。每次到了里程碑的检查点冲过了就可以集体庆功,冲爬下了就集体加班。而后者发生的机率总是比前者要多,现在回想起来真有种大浪淘沙,不堪回首之感。
  现在 敏捷开发 用顺溜了,回过头来看这种作坊式的开发甚是感触。阶段式的开发本身并无问题,而是迭代周期的控制很容易出错。往往都会将阶段周期拉得很长,尽量在每个阶段内将所有的工作完善之后再进入下一周期。然而,千里之堤,溃于蚁穴,过长的周期往往不会按我们预期的想法而进行,总是出现各种的问题,归结原因更多的是因为风险叠加的结果。优秀的PM会有N种处理风险的手段与经验,而且关于风险控制的理论层出不穷,这类的课程也是一扫一大堆。不过再强的PM再优秀的PM也架不住风险在里程碑的集中性爆发。
  这可能是也是 敏捷开发 最吸引人的地方,因为风险的集中性爆发的影响被 持续集中 CI 给最小化了。本文的主题并不是全面地讨论敏捷的理论,我相信有敏捷开发实践的人并不在少数,真正驱动我写下本文的动力是自从.net 移居到 linux 这个大世界后发现持续集成是如此的简单,执行的成本是如此之低,各种敏捷的工具可谓一应俱全,很想将这个过程记录下来以供分享。

测试驱动 TDD
  最近,在完成FreezesBeta版的开发,我就遇到发布问题,在微软平台上轻车熟路的做法现在得重新适应。Python 之所以诱惑人可能是她总是能给人惊喜吧。
  多年强制实践敏捷的好处是可以彻底改掉不写测试的坏毛病,当测试写多了会自然萌生一种“不写测试就不安心”的感觉。 Python 世界有很优秀的的测试框架,例如:unittest, pyTest, nose, doctest 等等。由于 unittest 是内置框架,而且本人也比较懒所以很长时间内我也没用采用其它的测试框架,直至最近才发现 nose 在这诸多测试框架中的便利性,而且可以完全与 unittest 兼容还带有大量的代码断言工具,实在是很不错。关于 nose 的使用心得可以参考我发布在自己博客上的使用笔记:Nose 测试框架
  。
  我认为实践持续集成的核心就是TDD而不是小版本,因为通过测试就是验证小版本可发布的唯一标准。在体验 python TDD过程中不得不为 pyCharm 4 这个工具点赞!由其是当全面运行覆盖率检测时,pyCharm4已将UI与 coverage 很好的集成在一齐,可以很方便地查看项目中代码的测试覆盖情况:
DSC0000.png
  另外在构建python测试有几个十分实用的工具


  • coverage - 生成代码测试覆盖率  

  • ForgeryPy - 数据伪造工具  

  • selenium - 著名的E2E测试服务器  

  • mock - 对象、接口伪装工具
  关于 TDD 的基础理论在此不作赘述有兴趣的朋友可以去度娘或者谷歌。


Python 发布包
  当所有的测试通过后,一下步就是小版本的发布了。现在几乎没有什么开发语言体系是不具备官方的依赖包引用库的了,用 python 的话当然需要将可运行代码发布到 pypi 上,其它用户就能通过本地的命令行工具 pip 直接安装了(相当于做C#开发时直接从Nuget直接下载依赖包一样)。
  python 的安装包是需要通过 setuptools 工具打包,生成 egg-info 和 发布包的。在代码中唯一需要做的工作就是编写 setup.py 文件。这个过程其实是瞒坑爹的,因为在python的包管理工具除了 setuptools 这个历史最为悠久的还有新一代的 distutils 工具,而官方说明非常地详细,具体可以参考Python Packaging User Guide 。 我花了老半天才将这份官方文档全部读完,但坑爹的是实作过程根本没有这么复杂,所以我在此总结了一下:
  首先,在编写setup.py 之前需要一份依赖包引用文件 requirements.txt,(如果有就跳到下一步),在当前目录下进入命令行执行:
  

$ pip freeze   

  这里是 pip 的详细使用文档
  执行成功后将会自动产生 requirements.txt。如果你不做这一步那么只能在 setup.py 内手工写 install_requires 了。
  是在项目根目录下建立 setup.py文件,最简单的做法如下:
  

import os  
import re
  
from setuptools import setup, find_packages
  

  
# 读入依赖引用文件
  
with open('requirements.txt') as reqs_file:
  REQS = reqs_file.read()
  

  
setup(
  name='项目名', # Pypi 显示的项目名
  version='1.0',
  packages=find_packages(exclude=['tests']),
  install_requires=REQS, # 指定 setup 运行时的依赖包
  
)
  

  这里有两个重点,一个是 find_packages,这个方法会在 setup.py 执行时将所有的 python 包(必须带有__init__.py)和包内的 .py 文件添加到打包目录中, 另一个就是 install_requires 这是 setup.py 在运行时自动检查环境内是否具备指定的依赖,如果没有就会自动下载安装。
  写完 setup.py 就可以在命令行执行测试了
  

$ python setup.py build  

  注意,此处我并没有直接执行install 而只是使用 build 先将发布包生成至 build 目录并且输出 egg-info。通过这一步可以先检查最终发布包中是否有文件缺失。
  如果 python 项目中包含有源码文件以外的资源需要打包发布,那么可以使用package_data属性,这个属性是一个“字典”类型,键(Key)值用于指定路径(当前项目路径是空串)。值(Value) 是一个文件数组,指定包含的文件资源的匹配表达式。如果是 Flask 的标准项目结构,要将 static 和 templates 的内容包含于发布包内,那么:
  

#...  

  
setup(
  #...
  package_data={
  '': ['*.*', # 根目录下所有的文件类型
  'static/**/*.*',   # static 目录下所有的子目录及所有文件
  'templates/*.*',   # tempaltes 目录下所有的文件
  'templates/**/*.*' # tempaltes 目录下所有子目录及所有文件
  ]
  },
  #...
  
)
  

  

  以下是整个项目的完整 setup.py 文件
  

import os  
import re
  
from setuptools import setup, find_packages
  

  
## 从源码目录中读取顶层包的 __version__ ,以便以后版本的统一更改
  
with open(os.path.join(os.path.dirname(__file__),
  '这里是源码目录名', '__init__.py')) as init_py:
  VERSION = re.search("VERSION = '([^']+)'", init_py.read()).group(1)
  

  
# 读入依赖引用文件
  
with open('requirements.txt') as reqs_file:
  REQS = reqs_file.read()
  

  
setup(
  name='Freezes',
  version=VERSION,
  packages=find_packages(exclude=['tests']),
  install_requires=REQS, # 指定 setup 运行时的依赖包
  package_data={
  '': ['*.yml',
  '*.json',
  '*.cfg',
  'layouts/*',
  'seeds/*',
  'static/**/*.*',
  'templates/*.*',
  'templates/**/*.*',
  'translations/*.*'
  ]
  },
  entry_points={
  'console_scripts': [
  'freezes=freezes.server:main'
  ],
  'setuptools.installation': [
  'eggsecutable = freezes.server:main'
  ]
  },
  ## 以下内容是可选的,用于生成 egg-info 的内容
  url='http://freezes.dotnetage.com',
  license='BSD',
  author='Ray',
  author_email='csharp2002@hotmail.com',
  description="这里是项目简述,会在pipy的项目列表中显示",
  long_description="这里是项目的详细描述,会在pypi项目详细页面中显示",
  zip_safe=False,
  platforms='any',
  keywords=('static', 'flask'),
  classifiers=['Development Status :: 4 - Beta',
  'Intended Audience :: Developers',
  'License :: OSI Approved :: BSD License',
  'Natural Language :: English',
  'Operating System :: OS Independent',
  'Programming Language :: Python :: 2.7',
  'Topic :: Software Development :: Libraries',
  'Topic :: Utilities']
  
)
  

  

  到此,貌似所有的准备工作已准备完成,但恰恰这里可能就有个坑,我多次生成发布发现发布包缺少了文件,那么请加上 MANIFEST.in 并将项目根目录下的文件包含在内
  ** MANIFEST.in **
  

include requirements.txt  

  我在园子内找到一园友写的一篇博文就是关于 MANIFEST.in 的,详细参考 Python distribute到底使用package_data还是MANIFEST.in?
  现在只要在pypi上注册一个帐户,然后回到项目的命令行状态运行:
  

$ python setup.py sdist upload  

  就可以生成安装包并直接上传到pypi上了,接下来就可以用 pip install  检验你的发布成果了。

Github
  在进行持续集成之前更重要的部署当然是源码控理了,关于 github 在此就不多说了,估计不会有人不知道它的大名的了。在发布到 Github 之前这里一份 .gitignore 文件可供参考,避免将无用的文件上传到Github:
  .gitignore
  

.idea  
.webassets-cache
  
*.pyc
  
*.pypirc
  

  
# Packages
  
*.egg
  
*.egg-info
  
dist
  
build
  
eggs
  
parts
  
bin
  
var
  
sdist
  
develop-eggs
  
.installed.cfg
  
lib
  
lib64
  
__pycache__
  

  
# Installer logs
  
pip-log.txt
  

  
# Unit test / coverage reports
  
.coverage
  
.tox
  
nosetests.xml
  

  
# SQLite databases
  
*.sqlite
  

  
# Virtual environment
  
venv
  

  

  如果你在使用pyCharm 那么推荐安装 .ignore 这个插件,可以直接支持多种的 ignore 文件。


自动构建
  最后一公里就是就是自动构建,我们要达到的效果就是以后每次向 Github 提交变更时能自动执行部署和测试。如果条件允许可以使用Docker自建一台构建服务器完成这个过程。而另一个更佳做法是使用 Travis 的自动构建服务,只要用Github的账号注册,并将Reposiotry加入到Travis的跟踪项后,当Github上的项目发生变更时Travis就会自动从Github上将最新版本的源代码拉到一个独立的Docker环境内直接进行部署和测试,每次测试结束还会向你的邮箱发送测试报告。如果在项目的Readme文件内引入自动更新的状态标签PyPI Shields/Pins就将发布与最终用户之间的最后屏障打通。
  Travis 可以支持很多的语言,文本以python为例,只要在项目的根目录内加入.travis.yml的配置文件,配置Travis的自动构建行为(这是可选的,如果没有此文件Travis 会执行默认构建)
  以下是 .travis.yml 的完整内容:
  

language: python  #指定源码的语言  
python:           #指定python的版本
  - "2.7"
  - "pypy"
  

  
# 执行安装前安装必要的依赖环境
  
before_install:
  - sudo apt-get install node-less
  - sudo apt-get install coffeescript
  

  
# 执行安装指令
  
install:
  - pip install -r tests/requirements.txt
  - python setup.py -q install
  

  
# 安装成功后执行的指令集,此处为自动执行测试
  
script: python tests/test_suites.py
  

  最后就是将状态标签加入到的Readme文件内,让用户即时了解当前源码的状态,效果如下图:
DSC0001.png
  要达到此效果只要在项目内加入readme.rst 文件并加以下以代码:
  将以下变量替换为您的项目注册信息:


  • - Github 用户名  

  • - Github 源码项目名称  

  • - 在Pypi上发布的可执行包名
  

项目名称  
=======
  

  
.. image:: https://secure.travis-ci.org//.png?branch=master
  :alt: Build Status
  :target: http://travis-ci.org//
  

  
.. image:: https://pypip.in/py_versions//badge.svg

  :target: https://pypi.python.org/pypi/

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-144755-1-1.html 上篇帖子: [Python] 网络爬虫和正则表达式学习总结 下篇帖子: python 装饰器
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表