Added:
trunk/doc/agile_python/
trunk/doc/agile_python/Makefile
trunk/doc/agile_python/Makefile.stub
trunk/doc/agile_python/ch01-preface.xml
trunk/doc/agile_python/ch02-model-tdd.xml
trunk/doc/agile_python/images/
trunk/doc/agile_python/index.xml
Log:
initial
已增加: trunk/doc/agile_python/Makefile
===================================================================
--- trunk/doc/agile_python/Makefile (rev 0)
+++ trunk/doc/agile_python/Makefile 2008-09-09 14:40:41 UTC (rev 80)
@@ -0,0 +1 @@
+link ../Makefile.tmpl
\ No newline at end of file
Property changes on: trunk/doc/agile_python/Makefile
___________________________________________________________________
已增加: svn:special
+ *
已增加: trunk/doc/agile_python/Makefile.stub
===================================================================
--- trunk/doc/agile_python/Makefile.stub (rev 0)
+++ trunk/doc/agile_python/Makefile.stub 2008-09-09 14:40:41 UTC (rev 80)
@@ -0,0 +1,9 @@
+# XML_SRCDIR defines the place where XMLs present.
+XML_SRCDIR := .
+
+# XML_INDEX_NAME defines the main XML index page without extension.
+XML_INDEX_NAME := index
+
+# MM_SRC is FreeMind .mm filename
+MM_SRC :=
+
Property changes on: trunk/doc/agile_python/Makefile.stub
___________________________________________________________________
已增加: svn:executable
+ *
已增加: svn:eol-style
+ native
已增加: trunk/doc/agile_python/ch01-preface.xml
===================================================================
--- trunk/doc/agile_python/ch01-preface.xml (rev 0)
+++ trunk/doc/agile_python/ch01-preface.xml 2008-09-09 14:40:41 UTC (rev 80)
@@ -0,0 +1,214 @@
+<!-- =================================================================== -->
+<sect1 id="psm.preface">
+ <title>前言</title>
+
+ <para>本文来自于笔者最近完成的一个小项目 pySvnManager,源代码已经贡献到开源项目网站
+ SorceForge.net。该项目从一开始,就采用了测试驱动开发(TDD)技术,通过一系列的迭代
+ 最终敏捷的实现了预期的需求。</para>
+
+ <para>在该项目中采用了Python最新流行的MVC框架:
+ Pylons。并在Web页面中大量使用了AJAX技术。本文涉及到的技术术语有:敏捷,TDD,MVC,
+ 单元测试,代码覆盖测试,AJAX,重构,i18n,开放源代码。</para>
+
+ <!-- ================================================================= -->
+ <sect2 id="psm.preface.background">
+ <title>项目背景</title>
+
+ <para>Subversion使用配置文件进行基于路径的授权,手工配置易于出错。下面是一个
+ 错误百出的配置示例:</para>
+
+ <blockquote>
+ <screen>
+[groups]
+admin = &admin, admin1, admin2
+group1 = @group2, user1
+group2 = user2, @group1
+
+[aliases]
+admin = jiangxin
+
+[/]
+@admin = rw
+
+[/trunk]
+$authenticated = rw
+
+[repos1:/]
+* =
+user1 =
+@group1 = r
+@admin = rw
+
+[repos1:/trunk/src]
+* =
+@group1 = rw
+@visiters = r
+ </screen>
+ </blockquote>
+
+ <para>其中的错误或可能的错误有:</para>
+
+ <orderedlist>
+ <listitem>
+ <para>组的循环引用:group1包含了group2,而group2又反过来包含group1,
+ 造成循环引用。</para>
+ </listitem>
+
+ <listitem>
+ <para>包含未定义的组或者别名:例如在 repos1 版本库的 /trunk/src
+ 的策略中用到了 @visiters 组,而该组没有在[groups]小节中定义;</para>
+ </listitem>
+
+ <listitem>
+ <para>版本库repos1的根路径,欲限制user1的访问,而实际效果并非如此,
+ 因为uer1属于group1组,而group1组被授权。user1实际获得的权限是策略能够给予
+ 的最大权限;</para>
+ </listitem>
+
+ <listitem>
+ <para>访问版本库repos1的 /trunk 目录,会参照缺省的[/trunk]小节设置,
+ 这可能跟管理员本意不符。需要对repos1的/trunk重新定义权限以覆盖缺省的
+ [/trunk]小节的设置。</para>
+ </listitem>
+ </orderedlist>
+
+ <para>其中1和2的错误会造成Subversion服务中断故障!3和4的问题如果不经过测试很难发现!
+ 在我们为客户实施Subversion技术支持服务过程中,发现了用户迫切需要容错性强的
+ 授权管理工具,于是便有了开发图形化管理界面的打算。选择Python是因为Python语言的魅力
+ 以及Python开发过程的高效。</para>
+
+ </sect2>
+
+ <!-- ================================================================= -->
+ <sect2 id="psm.preface.implement">
+ <title>最终的实现</title>
+
+ <para>我们先来看看最终的实现。下面的命令是在Debian Linux下完成,
+ Windows或其他平台,与之类似。</para>
+
+ <!-- =============================================================== -->
+ <sect3 id="psm.preface.implement.install">
+ <title>软件安装</title>
+
+ <para>理论上最简单的安装模式:</para>
+
+ <screen>
+$ sudo easy_install pySvnManager
+Searching for pySvnManager
+Reading http://pypi.python.org/simple/pySvnManager/
+Reading https://sourceforge.net/projects/pysvnmanager
+</screen>
+
+ <para>理论上的简单的东西,现实当中可能会有些复杂:</para>
+
+ <para>安装过程在进行到访问sourceforge.net以获取pySvnManager最新版本列表时被阻塞。
+ 虽然我打算把项目移到别处,但发现一些依赖的包如:
+ python-ldap也是要访问sourceforge.net网站,因此我取消了搬家的打算,默默静等解风。
+ 同时将代码镜像在网址:http://svn.worldhello.net/svn/pysvnmanager 上,
+ 供不能访问SourceForge.net的用户参考。</para>
+
+ <para>可以使用curl命令或者用浏览器连接到 http://pypi.python.org/simple/pySvnManager/,
+ 以查找可下载的pySvnManager软件包:</para>
+
+ <screen>
+$ curl http://pypi.python.org/simple/pySvnManager/
+<html><head><title>Links for pySvnManager</title></head><body><h1>Links for pySvnManager</h1><a href='https://sourceforge.net/projects/pysvnmanager' rel="homepage">0.1.2dev-r9 home_page</a><br/>
+<a href='http://pypi.python.org/packages/2.5/p/pySvnManager/pySvnManager-0.1.2dev_r9-py2.5.egg#md5=f0546558f3974308e0d24bd5195f8694'>pySvnManager-0.1.2dev_r9-py2.5.egg</a><br/>
+<a href='http://pypi.python.org/packages/source/p/pySvnManager/pySvnManager-0.1.2dev-r9.tar.gz#md5=eb54172084c67514251dbcfda29f4d23'>pySvnManager-0.1.2dev-r9.tar.gz</a><br/>
+</screen>
+
+ <para>看到链接了么? http://pypi.python.org/packages/2.5/p/pySvnManager/pySvnManager... </para>
+ <para>如果是Python2.5,可以直接安装egg包,如果不是,需要下载源码包。
+ 无论源码包还是egg包,都可以方便的使用easy_install进行安装:</para>
+ <screen>
+$ wget http://pypi.python.org/packages/source/p/pySvnManager/pySvnManager...
+$ sudo easy_install pySvnManager-...
+</screen>
+
+ </sect3>
+
+ <!-- =============================================================== -->
+ <sect3 id="psm.preface.implement.deploy">
+ <title>网站部署</title>
+ <para>执行make-config和setup-app完成部署。部署过程的细节参见后面软件集成的相关内容。</para>
+
+ <screen>
+$ mkdir deploy
+$ cd deploy
+$ paster make-config pySvnManager config.ini
+Distribution already installed:
+pySvnManager 0.1.2dev-r9 from /home/jiangxin/pyenv/lib/python2.5/site-packages/pySvnManager-0.1.2dev_r9-py2.5.egg
+Creating config.ini
+Now you should edit the config files
+config.ini
+$ paster setup-app config.ini
+Running setup_config() from pysvnmanager.websetup
+</screen>
+
+ </sect3>
+
+ <!-- =============================================================== -->
+ <sect3 id="psm.preface.implement.config">
+ <title>配置</title>
+
+ <para>部署目录下的四个配置文件:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>config.ini :应用默认运行于5000端口,可以在此文件中定制</para>
+ </listitem>
+
+ <listitem>
+ <para>config/localconfig.py :设置应用缺省的认证方式,缺省用config/svn.passwd口令认证</para>
+ </listitem>
+
+ <listitem>
+ <para>config/svn.passwd : 缺省该口令文件内所有用户的口令均为 "guess"</para>
+ </listitem>
+
+ <listitem>
+ <para>config/svn.access : svn路径授权文件,本应用要处理的文件。
+ 注意该文件开头的注释是版本号和版本库管理员帐号设置,不要随意删除!</para>
+ </listitem>
+
+ </itemizedlist>
+ </sect3>
+
+ <!-- =============================================================== -->
+ <sect3 id="psm.preface.implement.start">
+ <title>运行应用</title>
+
+ <para>启动应用,自动开启Web服务于5000端口。用Web浏览器访问。推荐使用Firefox。</para>
+
+ <screen>
+$ paster serve config.ini
+Starting server in PID 28937.
+serving on 0.0.0.0:5000 view at http://127.0.0.1:5000
+</screen>
+
+ </sect3>
+
+
+ <!-- =============================================================== -->
+ <sect3 id="psm.preface.implement.snapshot">
+ <title>软件截屏</title>
+ <para>参见演示网站: http://demo.ossxp.com/svnadmin/</para>
+
+
+ <figure id="psm.preface.implement.snapshot.img1">
+ <title>路径授权验证功能</title>
+ <graphic fileref="images/snapshot_check.png"/>
+ </figure>
+
+ <figure id="psm.preface.implement.snapshot.img2">
+ <title>路径授权设置</title>
+ <graphic fileref="images/snapshot_acl.png"/>
+ </figure>
+
+ <para>下面将整个开发过程予以概要介绍,以了解Python的Web编程框架以及Python的敏捷实践。</para>
+
+ </sect3>
+
+ </sect2>
+
+</sect1>
Property changes on: trunk/doc/agile_python/ch01-preface.xml
___________________________________________________________________
已增加: svn:mergeinfo
+
已增加: trunk/doc/agile_python/ch02-model-tdd.xml
===================================================================
--- trunk/doc/agile_python/ch02-model-tdd.xml (rev 0)
+++ trunk/doc/agile_python/ch02-model-tdd.xml 2008-09-09 14:40:41 UTC (rev 80)
@@ -0,0 +1,396 @@
+<!-- =================================================================== -->
+<sect1 id="psm.tdd">
+ <title>模型的敏捷开发</title>
+
+ <para><emphasis>忘记Web吧:</emphasis></para>
+
+ <para>我们要开发出一套Web应用,但首先要忘掉Web。这看似矛盾,却正是MVC的要求和精髓。
+ 即对核心算法进行抽象,先实现Model,之后再去考虑Controller(控制器)和View(Web展现)。</para>
+
+ <para><emphasis>忘记详细设计吧:</emphasis></para>
+
+ <para>敏捷开发,可不要等到图纸都出来再按图索骥。而是一种小步快跑的开发模式,
+ 将我们伟大的目标分解为一个一个小的目标,小到能够在一天之内就可以完成。</para>
+
+ <para><emphasis>先从测试做起:</emphasis></para>
+
+ <para>敏捷开发的一种是测试先行,让我们在第一个迭代中基于一个最简单的目标:实现单元测试框架。</para>
+
+ <!-- ================================================================= -->
+ <sect2 id="psm.tdd.iter1">
+ <title>迭代1:测试框架的建立</title>
+
+
+ <!-- =============================================================== -->
+ <sect3 id="psm.tdd.iter1.goal">
+ <title>假想任务目标</title>
+
+ <para>首先为我们的模型起个名字:svnauthz。</para>
+
+ <para>Subversion路径授权中,用户对象(用户/别名/组)显然是最重要的基本单位,
+ 每一条授权策略都包含一个用户对象。那么我们第一个迭代就实现用户对象:
+ User类,Alias类,Group类。</para>
+
+ <para>假设svnauthz的 User, Alias, Group 类已经完成,我们期望他们实现的功能是什么呢?
+ 于是在纸上写下(模拟python交互式命令行):</para>
+
+ <screen>
+>>> <emphasis>from svnauthz import User, Group, Alias</emphasis>
+>>> user1=User('Tom')
+>>> user2=User("Jerry")
+>>> print user1
+Tom # 显示 user1 内容(字符串化)
+
+>>> alias1=Alias('admin')
+>>> alias1.user = user1
+>>> print alias1
+admin = Tom # 显示 alias1 内容(字符串化)
+
+>>> group1 = Group('team1')
+>>> group2 = Group('team2')
+>>> group1.append(group2, user2, alias1, user1)
+>>> print group1
+team1 = &admin, @team2, Jerry, Tom # group1 的成员列表要进行排序
+>>> group2.append(group1, user1)
+Exception: ... # 抛出异常! group1 引起了组间的循环引用
+>>> group2.append(group1, user1, autodrop=True)
+>>> print group2
+team2 = Tom # 使用 autodrop 参数,自动抛弃冲突的组成员,而不引发异常。(即容错性)
+</screen>
+
+ </sect3>
+
+ <!-- =============================================================== -->
+ <sect3 id="psm.tdd.iter1.unittest.failed">
+ <title>建立测试用例</title>
+
+ <para>将假想的任务目标翻译为测试用例。建立单元测试文件 test_svnauthz.py 如下:</para>
+
+ <screen>
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import unittest
+from svnauthz import *
+
+class TestStage1(unittest.TestCase):
+
+ def testUser(self):
+ user1 = User('Tom')
+ self.assert_(str(user1) == 'Tom')
+
+ def testAlias(self):
+ user1 = User('Tom')
+ alias1=Alias('admin')
+ alias1.user = user1
+ self.assert_(str(alias1) == 'admin = Tom', str(alias1))
+
+ def testGroup(self):
+ user1 = User('Tom')
+ user2 = User('Jerry')
+ alias1=Alias('admin')
+ alias1.user = user1
+ group1 = Group('team1')
+ group2 = Group('team2')
+ group1.append(group2, user2, alias1, user1)
+ self.assert_(str(group1) == 'team1 = &admin, @team2, Jerry, Tom')
+ self.assertRaises(Exception, group2.append, group1, user1)
+ group2.append(group1, user1, autodrop=True)
+ self.assert_(str(group2) == 'team2 = Tom')
+
+if __name__ == '__main__': unittest.main()
+</screen>
+
+ <para>执行测试用例:</para>
+
+ <screen>
+$ python test_svnauthz.py
+Traceback (most recent call last):
+ File "test_svnauthz.py", line 8, in <module>
+ from svnauthz import *
+ImportError: No module named svnauthz
+</screen>
+
+ <para>测试失败!不要紧,因为我们还没有写代码呢。</para>
+ </sect3>
+
+ <!-- =============================================================== -->
+ <sect3 id="psm.tdd.iter1.unittest.pass">
+ <title>编写模组,使测试用例通过</title>
+
+ <para>之前执行测试用例失败,报告:找不到 svnauthz 模组。因为模组还没有创建,当然找不到了。
+ 于是创建一个空的模组文件 svnauthz.py。</para>
+
+ <screen>
+$ touch svnauthz.py
+</screen>
+
+ <para>执行测试用例:</para>
+
+ <screen>
+$ python test_svnauthz.py
+EEE
+======================================================================
+ERROR: testAlias (__main__.TestStage1)
+----------------------------------------------------------------------
+Traceback (most recent call last):
+File "test_svnauthz.py", line 17, in testAlias
+user1 = User('Tom')
+NameError: global name 'User' is not defined
+...
+</screen>
+
+ <para>太棒了,我们前进了一步,因为失败的原因已经不同了。错误报告说:User类未定义。
+ 于是我们写一些代码,让测试用例通过。</para>
+
+ <para>svnauthz.py第一个版本的代码如下:</para>
+
+ <screen>
+1 #!/usr/bin/env python
+2 # -*- coding: utf-8 -*-
+3
+4 """Subversion authz config file management.
+5
+6 Basic classes used for Subversion authz management.
+7 """
+8
+9 class User(object):
+10
+11 def __init__(self, name):
+12 name = name.strip()
+13
+14 if not name:
+15 raise Exception, 'Username is not provided'
+16
+17 self.__name = name
+18
+19 def __str__(self):
+20 return self.__name
+</screen>
+
+ <para>再次执行测试用例:</para>
+
+ <screen>
+$ python test_svnauthz.py -v
+testAlias (__main__.TestStage1) ... ERROR
+testGroup (__main__.TestStage1) ... ERROR
+testUser (__main__.TestStage1) ... ok
+
+======================================================================
+ERROR: testAlias (__main__.TestStage1)
+----------------------------------------------------------------------
+Traceback (most recent call last):
+File "test_svnauthz.py", line 18, in testAlias
+alias1=Alias('admin')
+NameError: global name 'Alias' is not defined
+...
+</screen>
+
+ <para>好的,我们已经有一个测试用例(testUser)通过了!其他的测试用例呢?
+ 先把他们注释掉,以便提前感受一下完全通过测试的滋味。</para>
+
+ <para>注意:我所说的注释掉不是删除代码,也不是把每一行变为注释,
+ 而是非常简单的将暂不考虑的测试用例改名。</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>将 def testAlias(self) 改为 def _testAlias(self)</para>
+ </listitem>
+
+ <listitem>
+ <para>将 def testGroup(self) 改为 def _testGroup(self)</para>
+ </listitem>
+
+ <listitem>
+ <para>注:只要不是以 test 开头都好。</para>
+ </listitem>
+
+ </itemizedlist>
+
+ <para>再次执行测试用例,太棒了完全通过!</para>
+
+ <screen>
+$ python test_svnauthz.py -v
+testUser (__main__.TestStage1) ... ok
+
+----------------------------------------------------------------------
+Ran 1 test in 0.000s
+
+OK
+</screen>
+
+ </sect3>
+
+ <!-- =============================================================== -->
+ <sect3 id="psm.tdd.iter1.code.coverage">
+ <title>完善测试用例</title>
+
+ <para>检查代码覆盖度,在Python下有coverage包可用。用easy_install安装之后,
+ 就可以使用coverage命令了。</para>
+
+ <screen>
+$ coverage -x test_svnauthz.py
+.
+----------------------------------------------------------------------
+Ran 1 test in 0.001s
+
+OK
+$ ls .coverage
+.coverage
+$ coverage -r -m svnauthz.py
+Name Stmts Exec Cover Missing
+----------------------------------------
+svnauthz 8 7 87% 15
+</screen>
+
+ <para>哦,看来我们离完美还是差了一点。从coverage的输出中可以看出,
+ 我们的测试用例并没有对svnauthz.py的代码测试完全:第15行没有测试到。
+ 也就是用空的用户名创建User对象,应该抛出异常。</para>
+
+ <para>我们在testUser用例的最后补充一条断言:</para>
+
+ <screen>
+def testUser(self):
+ user1 = User('Tom')
+ self.assert_(str(user1) == 'Tom')
+ self.assertRaises(Exception, User, " ")
+</screen>
+
+ <para>再次检查一下测试用例对代码的覆盖度。哇,100% 通过!</para>
+
+ <screen>
+$ coverage -x test_svnauthz.py
+.
+----------------------------------------------------------------------
+Ran 1 test in 0.002s
+
+OK
+$ coverage -r -m svnauthz.py
+Name Stmts Exec Cover Missing
+----------------------------------------
+svnauthz 8 8 100%
+</screen>
+
+ </sect3>
+
+ <!-- =============================================================== -->
+ <sect3 id="psm.tdd.iter1.nosetests">
+ <title>用例管理和nosetests</title>
+
+ <para>目前来讲,代码和测试用例共存于同一个目录。我们重构一下,将模组代码放在
+ src目录,将测试用例放在 tests 目录。</para>
+
+ <para>执行测试用例:</para>
+
+ <screen>
+$ python tests/test_svnauthz.py
+Traceback (most recent call last):
+ File "tests/test_svnauthz.py", line 8, in <module>
+ from svnauthz import *
+ImportError: No module named svnauthz
+</screen>
+
+ <para>在 test_svnauthz.py 文件头增加如下语句,设置Python模组查询路径:</para>
+
+ <screen>
+import sys
+sys.path.insert(0,'src')
+</screen>
+
+ <para>测试用例又可以成功执行了。</para>
+
+ <para>tests目录下如果有多个测试用例文件,难道要一个一个去调用么?或者用
+ unittest.TestSuite去组织测试用例?其实不用这么麻烦,nosetests
+ 可以自动发现目录下的测试用例,并执行。</para>
+
+ <para>鼻子测试(nosetests)是一个主动发现测试用例的unittest扩展。
+ 可以用easy_install 来安装:</para>
+
+ <screen>
+$ easy_install nose
+执行nosetests:
+$ nosetests
+.
+----------------------------------------------------------------------
+Ran 1 test in 0.008s
+
+OK
+</screen>
+
+ <para>代码覆盖度测试</para>
+
+ <screen>
+$ nosetests --with-coverage --cover-package=svnauthz
+.
+Name Stmts Exec Cover Missing
+----------------------------------------
+svnauthz 8 8 100%
+----------------------------------------------------------------------
+Ran 1 test in 0.030s
+
+OK
+</screen>
+
+ </sect3>
+
+ </sect2>
+
+
+ <!-- ================================================================= -->
+ <sect2 id="psm.tdd.continued">
+ <title>持续迭代</title>
+
+ <para>持续迭代,完成User, Group, Alias, Rules, Module, Repos, SvnAuthz等模组。</para>
+
+ </sect2>
+
+ <!-- ================================================================= -->
+ <sect2 id="psm.tdd.final">
+ <title>最终完成的svnauthz</title>
+
+ <para>在Python交互模式下测试svnauthz模组:</para>
+
+ <screen>
+>>> buff = '''# admin: / = administrator
+... [groups]
+... group1=user1,user2
+... [/]
+... $authenticated=r
+... [/trunk]
+... @group1 = r
+... user3 = rw'''
+>>> import StringIO
+>>> file = StringIO.StringIO(buff)
+>>> authz=SvnAuthz()
+>>> authz.load(file)
+>>> [x.name for x in authz.reposlist]
+['/']
+>>> [x.uname for x in authz.userlist]
+[u'administrator', u'user1', u'user2', u'user3']
+>>> [x.uname for x in authz.userlist]
+[u'administrator', u'user1', u'user2', u'user3']
+>>> [x.uname for x in authz.grouplist]
+[u'@group1', u'$authenticated']
+>>> [x.uname for x in authz.aliaslist]
+[]
+>>> print authz.grouplist
+[groups]
+group1 = user1, user2
+
+>>> print authz.aliaslist
+[aliases]
+>>> authz.is_admin('administrator','/')
+True
+>>> authz.is_admin('administrator','repos1')
+True
+>>> authz.add_rules('/', '/trunk', '&admin=rw; $authenticated=')
+>>> module1 = authz.get_module('/', '/trunk')
+>>> [str(x) for x in module1]
+['@group1 = r', 'user3 = rw', '$authenticated = ', '&admin = rw']
+</screen>
+
+ <para>现在是时候给svnauthz套上一个华丽一点的外衣了。</para>
+
+ </sect2>
+
+</sect1>
Property changes on: trunk/doc/agile_python/ch02-model-tdd.xml
___________________________________________________________________
已增加: svn:mime-type
+ text/xml
已增加: svn:eol-style
+ native
已增加: trunk/doc/agile_python/index.xml
===================================================================
--- trunk/doc/agile_python/index.xml (rev 0)
+++ trunk/doc/agile_python/index.xml 2008-09-09 14:40:41 UTC (rev 80)
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
+<!ENTITY author "<ulink url='mailto:worldhello.net .AT. gmail.com'>蒋鑫</ulink>">
+<!ENTITY orgname "<ulink url='http://www.ossxp.com'>ossxp.com</ulink>">
+<!ENTITY % vers SYSTEM "version.xml">
+%vers;
+<!ENTITY foreword SYSTEM "foreword.xml">
+<!ENTITY ch01 SYSTEM "ch01-preface.xml">
+<!ENTITY ch02 SYSTEM "ch02-model-tdd.xml">
+<!ENTITY ch03 SYSTEM "ch03-pylons.xml">
+<!ENTITY ch04 SYSTEM "ch04-auth.xml">
+<!ENTITY ch05 SYSTEM "ch05-config.xml">
+<!ENTITY ch06 SYSTEM "ch06-i18n.xml">
+<!ENTITY ch07 SYSTEM "ch07-package.xml">
+<!ENTITY ch08 SYSTEM "ch08-submit.xml">
+<!ENTITY ch09 SYSTEM "ch09-appendix.xml">
+]>
+
+<article id="index">
+ <articleinfo>
+ <title>Python/Pylons 敏捷实践</title>
+ <author><firstname>http://www.ossxp.com</firstname></author>
+
+ <affiliation>
+ <orgname>&orgname;</orgname>
+ <address><email>worldhello.net@gmail.com</email></address>
+ </affiliation>
+
+ <revhistory>
+ <revision>
+ <revnumber>0.1</revnumber>
+ <date>2008/07/20</date>
+ <authorinitials>&author;</authorinitials>
+ <revremark>创建。</revremark>
+ </revision>
+ </revhistory>
+
+ <abstract>
+ <para>本文来自于笔者最近完成的一个小项目 pySvnManager,源代码已经贡献到开源项目网站 SorceForge.net。该项目从一开始,就采用了测试驱动开发(TDD)技术,通过一系列的迭代最终敏捷的实现了预期的需求。</para>
+
+ <para>在该项目中采用了Python最新流行的MVC框架: Pylons。并在Web页面中大量使用了AJAX技术。本文涉及到的技术术语有:敏捷,TDD,MVC,单元测试,代码覆盖测试,AJAX,重构,i18n,开放源代码。</para>
+
+ <para>(版本号: 0.1.&doc.revision;,最后更新时间: &doc.lastchange;)</para>
+ </abstract>
+
+</articleinfo>
+
+&ch01;
+&ch02;
+
+</article>
Property changes on: trunk/doc/agile_python/index.xml
___________________________________________________________________
已增加: svn:mime-type
+ text/xml
已增加: svn:eol-style
+ native
_______________________________________________
Svn mailing list
S...@list.worldhello.net
http://www.worldhello.net/mailman/listinfo/svn