如何在Google App Engine上设置TDD开发流程?
5 个回答
我没有用过App Engine,但我对一些流行的Python测试工具的感觉是这样的:
- unittest/doctest是Python自带的测试工具。unittest可以看作是Python的xUnit。
- nose是一个测试运行器和查找器。它有很多选项,比如
--with-coverage
,这个选项可以使用coverage来生成代码覆盖率报告。 - pylint是功能最强大的Python代码检查工具。它不仅仅是检查语法,还会提醒你哪些变量或函数没有用到,什么时候应该用函数而不是方法等等。
- pester(变异测试)
- buildbot(持续集成)
你可能想参考这个(虽然不完全)列表,里面有Python测试工具的分类。
关于行为驱动开发(BDD),我上次查看时发现相关工具不多。很多真正的BDD工具不能和nose一起使用,或者在语法上限制太多。你可以试试spec,这是一个类似BDD的nose插件。我刚发现pyccuracy,看起来很像cucumber,但我还没试过。
就我个人而言,我现在只用nosetests -v
(带有--verbose选项的nose运行器),这样会在测试运行的输出中显示文档字符串的第一行。也就是说,给定一个测试:
class TestFoo(unittest.TestCase):
def testAnyNameHere(self):
""" Foo should be bar"""
foo = "bar"
self.assertEqual(foo, 'bar')
nosetests会输出:
$ nosetests -v
Foo should be bar... ok
-----------------------------
Ran 1 tests in 0.002s
OK
在Python中,你不一定能找到和Ruby测试工具一一对应的工具,但Python也有一些很不错的测试工具。我发现的一些有用的工具包括:
- unittest - 这是Python标准库中自带的xUnit工具,包含了单元测试的基本功能。
- doctest - 这是标准库中的一个很棒的部分,它允许你在函数、类、模块和方法的文档字符串中写测试。这对于展示API的使用方式非常有帮助。Ian Bicking建议在行为驱动开发中使用doctest。doctest与Sphinx文档系统非常契合(你可以确保文档中的所有示例在每次构建文档时都能通过测试)。
- nose和py.test被认为是unittest的下一代版本。它们可以运行所有现有的unittest案例,但允许更简单的非类基础的单元测试。py.test还支持分布式执行。
- mock是一个很好的库,用于模拟行为。
- tdaemon会监视你的代码目录的更新,并重新执行你的测试套件。(我的个人分支包含了一些未合并的改进)。
- Buildbot、Bitten,甚至Hudson都可以作为Python代码的完整持续集成服务器。
- coverage.py可以计算你的代码覆盖率。
- pylint会对你的代码进行类似lint的分析,确保它遵循常见的编码规范,并且没有常见的错误。还有一个“轻量级”的分析工具PyFlakes。
- 还有一些在Python中很好用的HTTP/浏览器测试工具,包括Twill、Selenium和Windmill。
如果你在App Engine上使用Django,它包含了一些unittest的扩展,可以让你模拟HTTP客户端和数据库持久性。
还有很多我没有使用过的工具(比如PySpec和Behaviour),也可能会很有帮助。我还没有看到Python中的变异测试工具,但我相信一定有(我很想知道是什么)。
祝你测试愉快!
在我的GAE项目中,我使用了:
- NoseGAE——这是把其他所有部分连接在一起的关键工具
- Mock,参考John的优秀回答。我主要用这个来处理AWS和其他网络服务
- Fixtures(这个包,不是这个概念)
我也很喜欢Rails的一些写法。我把我的测试分成单元测试和功能测试,使用Python的包来实现。你可以通过 --tests=unit
或 --tests=functional
来运行一部分测试。虽然这比Rails稍微麻烦一点,但至少我可以对复杂的部分进行单元测试,确保不会出现回归问题。
我还创建了一个简单的 FunctionalTest
类,来处理Rails中一些非常常见的操作,比如 assert_response
和 assert_xpath
(类似于assert_select)。
class FunctionalTest(Test):
def get(self, *args, **kw):
self.response = app.get(*args, **kw)
def post(self, *args, **kw):
self.response = app.post(*args, **kw)
def assert_response(self, expected):
pattern = str(expected) if re.search(r'^\d+$', expected) \
else (r'^\d+ %s' % expected)
assert re.search(pattern, self.response.status, re.IGNORECASE), \
'Response status was not "%s": %s' % (expected, self.response.status)
def assert_xpath(self, path, expected):
element = ElementTree.fromstring(self.response.body)
found_nodes = element.findall('.' + path)
if type(expected) is int:
assert_equal(expected, len(found_nodes))
elif type(expected) is str or type(expected) is unicode:
assert (True in [(node.text == expected) for node in found_nodes])
else:
raise Exception, "Unknown expected value: %r" % type(expected)
如果你经常需要在列表中查找相等的元素,务必要学习 --tests=foo
的用法,因为在列表中查找匹配的元素会非常慢。
有时候我喜欢在我的fixture数据上加载Rails控制台,看看测试环境中发生了什么(也就是 script/console test
)。要在GAE中做类似的事情,可以用 --datastore_path="$TMPDIR/nosegae.datastore"
参数运行 dev_appserver.py(或者可以把 $TMPDIR
替换成 /tmp
)。