TDD 与 CI 在 Python 中的实践
社区化产品的长久生存之道可能莫过于对迭代周期的控制。还记得以前采用老土的阶段开发的年代,将软件生命周期分为各个阶段,当到达每个阶段的里程碑则集中所有的资源、人力作全面冲刺。每次到了里程碑的检查点冲过了就可以集体庆功,冲爬下了就集体加班。而后者发生的机率总是比前者要多,现在回想起来真有种大浪淘沙,不堪回首之感。
现在 敏捷开发 用顺溜了,回过头来看这种作坊式的开发甚是感触。阶段式的开发本身并无问题,而是迭代周期的控制很容易出错。往往都会将阶段周期拉得很长,尽量在每个阶段内将所有的工作完善之后再进入下一周期。然而,千里之堤,溃于蚁穴,过长的周期往往不会按我们预期的想法而进行,总是出现各种的问题,归结原因更多的是因为风险叠加的结果。优秀的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 很好的集成在一齐,可以很方便地查看项目中代码的测试覆盖情况:
另外在构建python测试有几个十分实用的工具
关于 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
执行成功后将会自动产生 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文件内,让用户即时了解当前源码的状态,效果如下图:
要达到此效果只要在项目内加入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上的源码版本即可。将这个方法延伸,则可应用于任何语言体验下的项目开发。同样只需要做的步骤:
- 选定测试框架
- 编写各种测试
- 将运行版本发布至公共包管理库
- 将源码发布至源码服务器 (github)
- 接入构建服务器 (travis)
附:本文相关资源连接
TDD 与 CI 在 Python 中的实践的更多相关文章
- TDD在Unity3D游戏项目开发中的实践
0x00 前言 关于TDD测试驱动开发的文章已经有很多了,但是在游戏开发尤其是使用Unity3D开发游戏时,却听不到特别多关于TDD的声音.那么本文就来简单聊一聊TDD如何在U3D项目中使用以及如何使 ...
- 谈谈Python中元类Metaclass(二):ORM实践
什么是ORM? ORM的英文全称是“Object Relational Mapping”,即对象-关系映射,从字面上直接理解,就是把“关系”给“对象”化. 对应到数据库,我们知道关系数据库(例如Mys ...
- Gitlab CI 持续集成的完整实践
Gitlab CI 持续集成的完整实践 本着公司团队初创,又在空档期想搞点事情,搭建了私有Gitlab的契机,顺便把持续集成搭建起,实现了对Python服务端代码的单元测试.静态代码分析和接口测试的持 ...
- 如何在 Jenkins CI/CD 流水线中保护密钥?
CI/CD 流水线是 DevOps 团队软件交付过程的基本组成部分.该流水线利用自动化和持续监控来实现软件的无缝交付.通过持续自动化,确保 CI/CD 流水线每一步的安全性非常重要.在流水线的各个阶段 ...
- Redis的Python实践,以及四中常用应用场景详解——学习董伟明老师的《Python Web开发实践》
首先,简单介绍:Redis是一个基于内存的键值对存储系统,常用作数据库.缓存和消息代理. 支持:字符串,字典,列表,集合,有序集合,位图(bitmaps),地理位置,HyperLogLog等多种数据结 ...
- Python中的字符串与字符编码
本节内容: 前言 相关概念 Python中的默认编码 Python2与Python3中对字符串的支持 字符编码转换 一.前言 Python中的字符编码是个老生常谈的话题,同行们都写过很多这方面的文章. ...
- python中的正则表达式(re模块)
一.简介 正则表达式本身是一种小型的.高度专业化的编程语言,而在python中,通过内嵌集成re模块,程序媛们可以直接调用来实现正则匹配.正则表达式模式被编译成一系列的字节码,然后由用C编写的匹配引擎 ...
- paip.复制文件 文件操作 api的设计uapi java python php 最佳实践
paip.复制文件 文件操作 api的设计uapi java python php 最佳实践 =====uapi copy() =====java的无,要自己写... ====php copy ...
- Python应用与实践【转】
转自:http://www.cnblogs.com/skynet/archive/2013/05/06/3063245.html 目录 1. Python是什么? 1.1. Pyt ...
随机推荐
- MySQL案例08:MySQL Scheduler Events带来的风险
定时任务是我们开发.运维人员经常用到的,比如cron,job,schedule,events scheduler等都是为了方便我们重复执行某项工作而无需人工参与而设计,这里我要说的是MySQL数据库本 ...
- oracle like模糊查询简单用法
like 用法介绍: 1.“_”:匹配单个任意字符 select * from bqh3 where name like '_崔'; 2.“%”:匹配0个或多个任意字符.但有三种情况如下: like ...
- kettle性能优化
普通开发电脑,如果没有网络查询步骤,kettle正常的速度应该在3000~20000条/秒.如果速度在2000条/秒一下,就可能需要调优. 性能优化的方式包括如下几种: 1.通过改变开始复制的数量(针 ...
- CentOS7 配置静态 ip
1. 为 CentOS7 配置静态 ip 1.1 修改文件/etc/sysconfig/network-scripts/ifcfg-ens33 sudo vi /etc/sysconfig/netwo ...
- Win7下设置护眼的电脑豆沙绿界面
控制面板\所有控制面板项\个性化\窗口颜色和外观 "色调"(Hue)设为85,"饱和度"(Sat)设为90,"亮度" (Lum)设为205. ...
- 阿里云centos7.2 lamp配置
安装apache 1.安装yum -y install httpd 2.设置apache服务开机启动systemctl enable httpd.service 3.开启apache服务systemc ...
- 写Ansible playbook添加zabbix被监控的对象
本主题达到的效果是能通过编写Ansible Playbook,创建zabbix主机组,把被监控的对象加入到zabbix监控系统中,同时链接到对象的模板. 1.准备工作 在zabbix服务器上面,我们需 ...
- 关于Javascript的des加密
参考文章:https://www.cnblogs.com/MSMXQ/p/4484348.html 需要先下载CryptoJS文件,然后引入其中的两个文件,可以在github中找到. 直接上代码 &l ...
- console.time方法与console.timeEnd方法
在Node.js中,当需要统计一段代码的执行时间时,可以使用console.time方法与console.timeEnd方法,其中console.time方法用于标记开始时间,console.time ...
- es6安装babel包
1.前面下载node.js及安装淘宝镜像可以查看我写的vue.js环境搭建 2.安装完node后,安装babel npm install -g babel-cli 3.检验babel是否安装成功: b ...