社区化产品的长久生存之道可能莫过于对迭代周期的控制。还记得之前采用老土的阶段开发的年代,将软件生命周期分为各个阶段,当到达每一个阶段的里程碑则集中全部的资源、人力做全面冲刺。每次到了里程碑的检查点冲过了就能够集体庆功,冲爬下了就集体加班。然后者发生的机率老是比前者要多,如今回想起来真有种大浪淘沙,不堪回首之感。html
如今 敏捷开发 用顺溜了,回过头来看这种做坊式的开发甚是感触。阶段式的开发自己并没有问题,而是迭代周期的控制很容易出错。每每都会将阶段周期拉得很长,尽可能在每一个阶段内将全部的工做完善以后再进入下一周期。然而,千里之堤,溃于蚁穴,过长的周期每每不会按咱们预期的想法而进行,老是出现各类的问题,归结缘由更多的是由于风险叠加的结果。优秀的PM会有N种处理风险的手段与经验,并且关于风险控制的理论层出不穷,这类的课程也是一扫一大堆。不过再强的PM再优秀的PM也架不住风险在里程碑的集中性爆发。node
这多是也是 敏捷开发 最吸引人的地方,由于风险的集中性爆发的影响被 持续集中 CI 给最小化了。本文的主题并非全面地讨论敏捷的理论,我相信有敏捷开发实践的人并不在少数,真正驱动我写下本文的动力是自从.net 移居到 linux 这个大世界后发现持续集成是如此的简单,执行的成本是如此之低,各类敏捷的工具可谓包罗万象,很想将这个过程记录下来以供分享。python
最近,在完成FreezesBeta版的开发,我就遇到发布问题,在微软平台上轻车熟路的作法如今得从新适应。Python 之因此诱惑人多是她老是能给人惊喜吧。linux
多年强制实践敏捷的好处是能够完全改掉不写测试的坏毛病,当测试写多了会天然萌生一种“不写测试就不安心”的感受。 Python 世界有很优秀的的测试框架,例如:unittest, pyTest, nose, doctest 等等。因为 unittest 是内置框架,并且本人也比较懒因此很长时间内我也没用采用其它的测试框架,直至最近才发现 nose 在这诸多测试框架中的便利性,并且能够彻底与 unittest 兼容还带有大量的代码断言工具,实在是很不错。关于 nose 的使用心得能够参考我发布在本身博客上的使用笔记:Nose 测试框架。git
我认为实践持续集成的核心就是TDD而不是小版本,由于经过测试就是验证小版本可发布的惟一标准。在体验 python TDD过程当中不得不为 pyCharm 4 这个工具点赞!由其是当全面运行覆盖率检测时,pyCharm4已将UI与 coverage 很好的集成在一齐,能够很方便地查看项目中代码的测试覆盖状况:github
另外在构建python测试有几个十分实用的工具web
关于 TDD 的基础理论在此不做赘述有兴趣的朋友能够去度娘或者谷歌。sql
当全部的测试经过后,一下步就是小版本的发布了。如今几乎没有什么开发语言体系是不具有官方的依赖包引用库的了,用 python 的话固然须要将可运行代码发布到 pypi 上,其它用户就能经过本地的命令行工具 pip
直接安装了(至关于作C#开发时直接从Nuget直接下载依赖包同样)。shell
python 的安装包是须要经过 setuptools 工具打包,生成 egg-info 和 发布包的。在代码中惟一须要作的工做就是编写 setup.py
文件。这个过程实际上是瞒坑爹的,由于在python的包管理工具除了 setuptools 这个历史最为悠久的还有新一代的 distutils 工具,而官方说明很是地详细,具体能够参考Python Packaging User Guide 。 我花了老半天才将这份官方文档所有读完,但坑爹的是实做过程根本没有这么复杂,因此我在此总结了一下:json
首先,在编写setup.py 以前须要一份依赖包引用文件 requirements.txt,(若是有就跳到下一步),在当前目录下进入命令行执行:
$ pip freeze
执行成功后将会自动产生 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 以前这里一份 .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文件内,让用户即时了解当前源码的状态,效果以下图:
要达到此效果只要在项目内加入readme.rst
文件并加如下以代码:
将如下变量替换为您的项目注册信息:
<github-username>
- Github 用户名<repository>
- Github 源码项目名称<pypi-project-name>
- 在Pypi上发布的可执行包名项目名称 ======= .. image:: https://secure.travis-ci.org/<github-username>/<repository>.png?branch=master :alt: Build Status :target: http://travis-ci.org/<github-username>/<repository> .. image:: https://pypip.in/py_versions/<pypi-project-name>/badge.svg :target: https://pypi.python.org/pypi/<pypi-project-name/ :alt: Supported Python versions .. image:: https://pypip.in/status/<pypi-project-name/badge.svg :target: https://pypi.python.org/pypi/<pypi-project-name/ :alt: Development Status .. image:: https://pypip.in/version/<pypi-project-name/badge.svg :target: https://pypi.python.org/pypi/<pypi-project-name/ :alt: Latest Version .. image:: https://pypip.in/license/<pypi-project-name/badge.svg :target: https://pypi.python.org/pypi/<pypi-project-name/ :alt: License
自此整个项目的持续环境搭建宣告完成,之后每一个版本的迭代就只是管理 pypi 上的可运行版本与github上的源码版本便可。将这个方法延伸,则可应用于任何语言体验下的项目开发。一样只须要作的步骤: