社區化產品的長久生存之道可能莫過於對迭代週期的控制。還記得之前採用老土的階段開發的年代,將軟件生命週期分爲各個階段,當到達每一個階段的里程碑則集中全部的資源、人力做全面衝刺。每次到了里程碑的檢查點衝過了就能夠集體慶功,衝爬下了就集體加班。然後者發生的機率老是比前者要多,如今回想起來真有種大浪淘沙,不堪回首之感。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上的源碼版本便可。將這個方法延伸,則可應用於任何語言體驗下的項目開發。一樣只須要作的步驟: